ulogger-server/js/mapapi/api_gmaps.js

334 lines
8.2 KiB
JavaScript
Raw Normal View History

2019-06-29 12:54:32 +02:00
/*
* μlogger
2019-05-15 11:32:36 +02:00
*
2019-06-29 12:54:32 +02:00
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
2019-05-15 11:32:36 +02:00
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import { config, lang } from '../constants.js';
2019-06-29 12:54:32 +02:00
import uEvent from '../event.js';
2019-05-15 11:32:36 +02:00
import uUI from '../ui.js';
import uUtils from '../utils.js';
// google maps
2019-06-29 12:54:32 +02:00
/**
* Google Maps API module
* @module gmApi
* @implements {uMap.api}
*/
2019-05-15 11:32:36 +02:00
/** @type {google.maps.Map} */
let map = null;
2019-06-29 12:54:32 +02:00
/** @type {uBinder} */
let binder = null;
2019-05-15 11:32:36 +02:00
/** @type {google.maps.Polyline[]} */
const polies = [];
/** @type {google.maps.Marker[]} */
const markers = [];
/** @type {google.maps.InfoWindow[]} */
const popups = [];
/** @type {google.maps.InfoWindow} */
2019-06-29 12:54:32 +02:00
let openPopup = null;
2019-05-15 11:32:36 +02:00
/** @type {google.maps.PolylineOptions} */
let polyOptions = null;
/** @type {google.maps.MapOptions} */
let mapOptions = null;
/** @type {number} */
let timeoutHandle = 0;
const name = 'gmaps';
let isLoaded = false;
let authError = false;
/**
* Initialize map
2019-06-29 12:54:32 +02:00
* @param {uBinder} b
2019-05-15 11:32:36 +02:00
* @param {HTMLElement} el
*/
2019-06-29 12:54:32 +02:00
function init(b, el) {
binder = b;
2019-05-15 11:32:36 +02:00
const url = '//maps.googleapis.com/maps/api/js?' + ((config.gkey != null) ? ('key=' + config.gkey + '&') : '') + 'callback=gm_loaded';
uUtils.addScript(url, 'mapapi_gmaps');
if (!isLoaded) {
throw new Error('Google Maps API not ready');
}
start(el);
}
/**
* Start map engine when loaded
* @param {HTMLElement} el
*/
function start(el) {
if (authError) {
window.gm_authFailure();
return;
}
google.maps.visualRefresh = true;
// noinspection JSValidateTypes
polyOptions = {
strokeColor: config.strokeColor,
strokeOpacity: config.strokeOpacity,
strokeWeight: config.strokeWeight
};
// noinspection JSValidateTypes
mapOptions = {
center: new google.maps.LatLng(config.init_latitude, config.init_longitude),
zoom: 8,
mapTypeId: google.maps.MapTypeId.ROADMAP,
scaleControl: true
};
map = new google.maps.Map(el, mapOptions);
}
/**
* Clean up API
*/
function cleanup() {
polies.length = 0;
markers.length = 0;
popups.length = 0;
polyOptions = null;
mapOptions = null;
2019-06-29 12:54:32 +02:00
openPopup = null;
if (map && map.getDiv()) {
map.getDiv().innerHTML = '';
}
map = null;
2019-05-15 11:32:36 +02:00
}
/**
* Display track
2019-06-29 12:54:32 +02:00
* @param {uTrack} track
2019-05-15 11:32:36 +02:00
* @param {boolean} update Should fit bounds if true
*/
2019-06-29 12:54:32 +02:00
function displayTrack(track, update) {
2019-05-15 11:32:36 +02:00
if (!track) {
return;
}
// init polyline
const poly = new google.maps.Polyline(polyOptions);
poly.setMap(map);
const path = poly.getPath();
const latlngbounds = new google.maps.LatLngBounds();
let i = 0;
for (const position of track.positions) {
// set marker
2019-06-29 12:54:32 +02:00
setMarker(i++, track);
2019-05-15 11:32:36 +02:00
// update polyline
const coordinates = new google.maps.LatLng(position.latitude, position.longitude);
2019-06-29 12:54:32 +02:00
if (track.continuous) {
path.push(coordinates);
}
2019-05-15 11:32:36 +02:00
latlngbounds.extend(coordinates);
}
if (update) {
map.fitBounds(latlngbounds);
if (i === 1) {
// only one point, zoom out
const zListener =
google.maps.event.addListenerOnce(map, 'bounds_changed', function () {
if (this.getZoom()) {
this.setZoom(15);
}
});
setTimeout(function () { google.maps.event.removeListener(zListener) }, 2000);
}
}
polies.push(poly);
}
/**
* Clear map
*/
function clearMap() {
if (polies) {
for (let i = 0; i < polies.length; i++) {
polies[i].setMap(null);
}
}
if (markers) {
for (let i = 0; i < markers.length; i++) {
2019-06-29 12:54:32 +02:00
// google.maps.event.removeListener(popups[i].listener);
google.maps.event.clearInstanceListeners(popups[i]);
2019-05-15 11:32:36 +02:00
popups[i].setMap(null);
markers[i].setMap(null);
}
}
markers.length = 0;
polies.length = 0;
popups.length = 0;
}
2019-09-03 21:45:40 +02:00
/**
* @param {string} fill Fill color
* @param {boolean} isLarge Is large icon
* @param {boolean} isExtra Is styled with extra mark
* @return {google.maps.Icon}
*/
function getMarkerIcon(fill, isLarge, isExtra) {
return {
anchor: new google.maps.Point(15, 35),
url: uUI.getSvgSrc(fill, isLarge, isExtra)
};
}
2019-05-15 11:32:36 +02:00
/**
* Set marker
2019-06-29 12:54:32 +02:00
* @param {uTrack} track
2019-05-15 11:32:36 +02:00
* @param {number} id
*/
2019-06-29 12:54:32 +02:00
function setMarker(id, track) {
2019-05-15 11:32:36 +02:00
// marker
2019-06-29 12:54:32 +02:00
const position = track.positions[id];
const posLen = track.length;
2019-05-15 11:32:36 +02:00
// noinspection JSCheckFunctionSignatures
const marker = new google.maps.Marker({
position: new google.maps.LatLng(position.latitude, position.longitude),
title: (new Date(position.timestamp * 1000)).toLocaleString(),
map: map
});
2019-09-03 21:45:40 +02:00
const isExtra = position.hasComment() || position.hasImage();
let icon = getMarkerIcon(isExtra ? config.colorExtra : config.colorNormal, false, isExtra);
if (id === posLen - 1) {
icon = getMarkerIcon(config.colorStop, true, isExtra);
2019-05-15 11:32:36 +02:00
} else if (id === 0) {
2019-09-03 21:45:40 +02:00
icon = getMarkerIcon(config.colorStart, true, isExtra);
2019-05-15 11:32:36 +02:00
}
2019-09-03 21:45:40 +02:00
marker.setIcon(icon);
2019-05-15 11:32:36 +02:00
// popup
2019-06-29 12:54:32 +02:00
const popup = new google.maps.InfoWindow();
marker.addListener('click',
((i) => () => {
popup.setContent(uUI.getPopupHtml(i));
popup.open(map, marker);
binder.dispatchEvent(uEvent.MARKER_SELECT, i);
openPopup = popup;
popup.addListener('closeclick', () => {
binder.dispatchEvent(uEvent.MARKER_SELECT);
google.maps.event.clearListeners(popup, 'closeclick');
openPopup = null;
});
})(id));
marker.addListener('mouseover',
((i) => () => {
binder.dispatchEvent(uEvent.MARKER_OVER, i);
})(id));
marker.addListener('mouseout',
() => {
binder.dispatchEvent(uEvent.MARKER_OVER);
});
2019-05-15 11:32:36 +02:00
markers.push(marker);
popups.push(popup);
}
2019-06-29 12:54:32 +02:00
function animateMarker(id) {
if (openPopup) {
openPopup.close();
clearTimeout(timeoutHandle);
}
const icon = markers[id].getIcon();
2019-09-03 21:45:40 +02:00
markers[id].setIcon(getMarkerIcon(config.colorHilite, false, false));
2019-06-29 12:54:32 +02:00
markers[id].setAnimation(google.maps.Animation.BOUNCE);
timeoutHandle = setTimeout(() => {
markers[id].setIcon(icon);
markers[id].setAnimation(null);
}, 2000);
2019-05-15 11:32:36 +02:00
}
/**
* Get map bounds
* eg. ((52.20105108685229, 20.789387865580238), (52.292069558807135, 21.172192736185707))
* @returns {number[]} Bounds
*/
function getBounds() {
const bounds = map.getBounds();
const lat_sw = bounds.getSouthWest().lat();
const lon_sw = bounds.getSouthWest().lng();
const lat_ne = bounds.getNorthEast().lat();
const lon_ne = bounds.getNorthEast().lng();
return [ lon_sw, lat_sw, lon_ne, lat_ne ];
}
/**
* Zoom to track extent
*/
function zoomToExtent() {
const latlngbounds = new google.maps.LatLngBounds();
for (let i = 0; i < markers.length; i++) {
const coordinates = new google.maps.LatLng(markers[i].position.lat(), markers[i].position.lng());
latlngbounds.extend(coordinates);
}
map.fitBounds(latlngbounds);
}
/**
* Zoom to bounds
* @param {number[]} bounds
*/
function zoomToBounds(bounds) {
const sw = new google.maps.LatLng(bounds[1], bounds[0]);
const ne = new google.maps.LatLng(bounds[3], bounds[2]);
const latLngBounds = new google.maps.LatLngBounds(sw, ne);
map.fitBounds(latLngBounds);
}
/**
* Update size
*/
function updateSize() {
// ignore for google API
}
function setAuthError() { authError = true; }
function setLoaded() { isLoaded = true; }
export {
name,
init,
cleanup,
displayTrack,
clearMap,
2019-06-29 12:54:32 +02:00
animateMarker,
2019-05-15 11:32:36 +02:00
getBounds,
zoomToExtent,
zoomToBounds,
2019-06-29 12:54:32 +02:00
updateSize
2019-05-15 11:32:36 +02:00
}
/**
* Callback for Google Maps API
* It will be called when authentication fails
*/
window.gm_authFailure = function () {
setAuthError();
let message = uUtils.sprintf(lang.strings['apifailure'], 'Google Maps');
message += '<br><br>' + lang.strings['gmauthfailure'];
message += '<br><br>' + lang.strings['gmapilink'];
uUI.resolveModal(message);
};
/**
* Callback for Google Maps API
* It will be called when API is loaded
*/
window.gm_loaded = function () {
setLoaded();
};