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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
});
|
|
|
|
if (config.showLatest) {
|
|
|
|
marker.setIcon('images/marker-red.png');
|
|
|
|
} else if (id === 0) {
|
|
|
|
marker.setIcon('images/marker-green.png');
|
|
|
|
} else if (id === posLen - 1) {
|
|
|
|
marker.setIcon('images/marker-red.png');
|
2019-07-27 19:38:00 +02:00
|
|
|
} else if (position.hasComment() || position.hasImage()) {
|
|
|
|
marker.setIcon('images/marker-gray.png');
|
2019-05-15 11:32:36 +02:00
|
|
|
} else {
|
|
|
|
marker.setIcon('images/marker-white.png');
|
|
|
|
}
|
|
|
|
// 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();
|
|
|
|
markers[id].setIcon('images/marker-gold.png');
|
|
|
|
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();
|
|
|
|
};
|