ulogger-server/js/mapapi/api_openlayers.js

537 lines
13 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 } 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';
// openlayers 3+
2019-06-29 12:54:32 +02:00
/**
* OpenLayers API module
* @module olApi
* @implements {uMap.api}
*/
2019-05-15 11:32:36 +02:00
/** @type {ol.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 {ol.layer.Vector} */
let layerTrack = null;
/** @type {ol.layer.Vector} */
let layerMarkers = null;
/** @type {ol.layer.Base} */
let selectedLayer = null;
/** @type {ol.style.Style|{}} */
let olStyles = {};
2019-06-29 12:54:32 +02:00
/** @type {string} */
2019-05-15 11:32:36 +02:00
const name = 'openlayers';
2019-06-29 12:54:32 +02:00
/** @type {?number} */
let pointOver = null;
2019-05-15 11:32:36 +02:00
/**
* Initialize map
2019-06-29 12:54:32 +02:00
* @param {uBinder} b
* @param {HTMLElement} target
2019-05-15 11:32:36 +02:00
*/
2019-06-29 12:54:32 +02:00
function init(b, target) {
binder = b;
2019-05-15 11:32:36 +02:00
uUtils.addScript('//cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList', 'mapapi_openlayers_polyfill');
uUtils.addScript('js/lib/ol.js', 'mapapi_openlayers');
uUtils.addCss('css/ol.css', 'ol_css');
const controls = [
new ol.control.Zoom(),
new ol.control.Rotate(),
new ol.control.ScaleLine(),
new ol.control.ZoomToExtent({ label: getExtentImg() })
];
const view = new ol.View({
center: ol.proj.fromLonLat([ config.init_longitude, config.init_latitude ]),
zoom: 8
});
map = new ol.Map({
target: target,
controls: controls,
view: view
});
2019-06-29 12:54:32 +02:00
map.on('pointermove', (e) => {
const feature = map.forEachFeatureAtPixel(e.pixel,
(_feature, _layer) => {
if (_layer.get('name') === 'Markers') {
return _feature;
}
return null;
});
// emit mouse over marker event
/** @type {?number} */
const id = feature ? feature.getId() : null;
if (id !== pointOver) {
binder.dispatchEvent(uEvent.MARKER_OVER);
pointOver = id;
if (id) {
binder.dispatchEvent(uEvent.MARKER_OVER, id);
}
}
// change mouse cursor when over marker
if (feature) {
map.getTargetElement().style.cursor = 'pointer';
} else {
map.getTargetElement().style.cursor = '';
}
});
initLayers();
initStyles();
initPopups();
}
/**
* Initialize map layers
*/
function initLayers() {
2019-05-15 11:32:36 +02:00
// default layer: OpenStreetMap
const osm = new ol.layer.Tile({
name: 'OpenStreetMap',
visible: true,
source: new ol.source.OSM()
});
map.addLayer(osm);
selectedLayer = osm;
2019-06-29 12:54:32 +02:00
// add extra tile layers
2019-05-15 11:32:36 +02:00
for (const layerName in config.ol_layers) {
if (config.ol_layers.hasOwnProperty(layerName)) {
const layerUrl = config.ol_layers[layerName];
const ol_layer = new ol.layer.Tile({
name: layerName,
visible: false,
source: new ol.source.XYZ({
url: layerUrl
})
});
map.addLayer(ol_layer);
}
}
2019-06-29 12:54:32 +02:00
// add track and markers layers
2019-05-15 11:32:36 +02:00
const lineStyle = new ol.style.Style({
stroke: new ol.style.Stroke({
color: uUtils.hexToRGBA(config.strokeColor, config.strokeOpacity),
width: config.strokeWeight
})
});
layerTrack = new ol.layer.Vector({
name: 'Track',
type: 'data',
source: new ol.source.Vector(),
style: lineStyle
});
layerMarkers = new ol.layer.Vector({
name: 'Markers',
type: 'data',
source: new ol.source.Vector()
});
map.addLayer(layerTrack);
map.addLayer(layerMarkers);
2019-06-29 12:54:32 +02:00
initLayerSwitcher();
}
function initStyles() {
2019-05-15 11:32:36 +02:00
olStyles = {};
2019-09-03 21:45:40 +02:00
const iconStart = new ol.style.Icon({
anchor: [ 0.5, 1 ],
src: uUI.getSvgSrc(config.colorStart, true)
});
const iconStop = new ol.style.Icon({
anchor: [ 0.5, 1 ],
src: uUI.getSvgSrc(config.colorStop, true)
});
const iconStartExtra = new ol.style.Icon({
2019-05-15 11:32:36 +02:00
anchor: [ 0.5, 1 ],
2019-09-03 21:45:40 +02:00
src: uUI.getSvgSrc(config.colorStart, true, true)
2019-05-15 11:32:36 +02:00
});
2019-09-03 21:45:40 +02:00
const iconStopExtra = new ol.style.Icon({
2019-05-15 11:32:36 +02:00
anchor: [ 0.5, 1 ],
2019-09-03 21:45:40 +02:00
src: uUI.getSvgSrc(config.colorStop, true, true)
2019-05-15 11:32:36 +02:00
});
2019-09-03 21:45:40 +02:00
const iconNormal = new ol.style.Icon({
2019-05-15 11:32:36 +02:00
anchor: [ 0.5, 1 ],
opacity: 0.7,
2019-09-03 21:45:40 +02:00
src: uUI.getSvgSrc(config.colorNormal, false)
2019-05-15 11:32:36 +02:00
});
2019-09-03 21:45:40 +02:00
const iconExtra = new ol.style.Icon({
2019-07-27 19:38:00 +02:00
anchor: [ 0.5, 1 ],
2019-09-03 21:45:40 +02:00
src: uUI.getSvgSrc(config.colorExtra, false, true)
2019-07-27 19:38:00 +02:00
});
2019-09-03 21:45:40 +02:00
const iconHilite = new ol.style.Icon({
2019-05-15 11:32:36 +02:00
anchor: [ 0.5, 1 ],
2019-09-03 21:45:40 +02:00
src: uUI.getSvgSrc(config.colorHilite, false)
2019-05-15 11:32:36 +02:00
});
2019-09-03 21:45:40 +02:00
olStyles['start'] = new ol.style.Style({
image: iconStart
2019-05-15 11:32:36 +02:00
});
2019-09-03 21:45:40 +02:00
olStyles['stop'] = new ol.style.Style({
image: iconStop
2019-05-15 11:32:36 +02:00
});
2019-09-03 21:45:40 +02:00
olStyles['startExtra'] = new ol.style.Style({
image: iconStartExtra
2019-05-15 11:32:36 +02:00
});
2019-09-03 21:45:40 +02:00
olStyles['stopExtra'] = new ol.style.Style({
image: iconStopExtra
2019-07-27 19:38:00 +02:00
});
2019-09-03 21:45:40 +02:00
olStyles['normal'] = new ol.style.Style({
image: iconNormal
});
olStyles['extra'] = new ol.style.Style({
image: iconExtra
});
olStyles['hilite'] = new ol.style.Style({
image: iconHilite
2019-05-15 11:32:36 +02:00
});
2019-06-29 12:54:32 +02:00
}
2019-05-15 11:32:36 +02:00
2019-06-29 12:54:32 +02:00
function initPopups() {
2019-05-15 11:32:36 +02:00
const popupContainer = document.createElement('div');
popupContainer.id = 'popup';
popupContainer.className = 'ol-popup';
document.body.appendChild(popupContainer);
const popupCloser = document.createElement('a');
popupCloser.id = 'popup-closer';
popupCloser.className = 'ol-popup-closer';
popupCloser.href = '#';
popupContainer.appendChild(popupCloser);
const popupContent = document.createElement('div');
popupContent.id = 'popup-content';
popupContainer.appendChild(popupContent);
const popup = new ol.Overlay({
element: popupContainer,
autoPan: true,
autoPanAnimation: {
duration: 250
}
});
popupCloser.onclick = () => {
// eslint-disable-next-line no-undefined
popup.setPosition(undefined);
popupCloser.blur();
return false;
};
// add click handler to map to show popup
map.on('click', (e) => {
const coordinate = e.coordinate;
const feature = map.forEachFeatureAtPixel(e.pixel,
(_feature, _layer) => {
if (_layer.get('name') === 'Markers') {
return _feature;
}
return null;
});
if (feature) {
// popup show
popup.setPosition(coordinate);
popupContent.innerHTML = uUI.getPopupHtml(feature.getId());
map.addOverlay(popup);
2019-06-29 12:54:32 +02:00
binder.dispatchEvent(uEvent.MARKER_SELECT, feature.getId());
2019-05-15 11:32:36 +02:00
} else {
// popup destroy
// eslint-disable-next-line no-undefined
popup.setPosition(undefined);
2019-06-29 12:54:32 +02:00
binder.dispatchEvent(uEvent.MARKER_SELECT);
2019-05-15 11:32:36 +02:00
}
});
2019-06-29 12:54:32 +02:00
}
2019-05-15 11:32:36 +02:00
2019-06-29 12:54:32 +02:00
function initLayerSwitcher() {
2019-05-15 11:32:36 +02:00
const switcher = document.createElement('div');
switcher.id = 'switcher';
switcher.className = 'ol-control';
document.body.appendChild(switcher);
const switcherContent = document.createElement('div');
switcherContent.id = 'switcher-content';
switcherContent.className = 'ol-layerswitcher';
switcher.appendChild(switcherContent);
map.getLayers().forEach((_layer) => {
const layerLabel = document.createElement('label');
layerLabel.innerHTML = _layer.get('name');
switcherContent.appendChild(layerLabel);
const layerRadio = document.createElement('input');
if (_layer.get('type') === 'data') {
layerRadio.type = 'checkbox';
layerLabel.className = 'ol-datalayer';
} else {
layerRadio.type = 'radio';
}
layerRadio.name = 'layer';
layerRadio.value = _layer.get('name');
layerRadio.onclick = switchLayer;
if (_layer.getVisible()) {
layerRadio.checked = true;
}
layerLabel.insertBefore(layerRadio, layerLabel.childNodes[0]);
});
function switchLayer() {
const targetName = this.value;
map.getLayers().forEach((_layer) => {
if (_layer.get('name') === targetName) {
if (_layer.get('type') === 'data') {
if (_layer.getVisible()) {
_layer.setVisible(false);
} else {
_layer.setVisible(true);
}
} else {
selectedLayer.setVisible(false);
selectedLayer = _layer;
_layer.setVisible(true);
}
}
});
}
const switcherButton = document.createElement('button');
const layerImg = document.createElement('img');
layerImg.src = 'images/layers.svg';
layerImg.style.width = '60%';
switcherButton.appendChild(layerImg);
const switcherHandle = () => {
const el = document.getElementById('switcher');
if (el.style.display === 'block') {
el.style.display = 'none';
} else {
el.style.display = 'block';
}
};
switcherButton.addEventListener('click', switcherHandle, false);
switcherButton.addEventListener('touchstart', switcherHandle, false);
const element = document.createElement('div');
element.className = 'ol-switcher-button ol-unselectable ol-control';
element.appendChild(switcherButton);
const switcherControl = new ol.control.Control({
element: element
});
map.addControl(switcherControl);
}
/**
* Clean up API
*/
function cleanup() {
layerTrack = null;
layerMarkers = null;
selectedLayer = null;
olStyles = null;
uUI.removeElementById('popup');
uUI.removeElementById('switcher');
2019-06-29 12:54:32 +02:00
if (map && map.getTargetElement()) {
map.getTargetElement().innerHTML = '';
}
map = null;
2019-05-15 11:32:36 +02:00
}
/**
* Display track
2019-06-29 12:54:32 +02:00
* @param {uTrack} track 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;
}
let i = 0;
2019-06-29 12:54:32 +02:00
const lineString = new ol.geom.LineString([]);
2019-05-15 11:32:36 +02:00
for (const position of track.positions) {
// set marker
2019-06-29 12:54:32 +02:00
setMarker(i++, track);
if (track.continuous) {
// update polyline
lineString.appendCoordinate(ol.proj.fromLonLat([ position.longitude, position.latitude ]));
}
}
if (lineString.getLength() > 0) {
const lineFeature = new ol.Feature({
geometry: lineString
});
layerTrack.getSource().addFeature(lineFeature);
2019-05-15 11:32:36 +02:00
}
2019-06-29 12:54:32 +02:00
let extent = layerMarkers.getSource().getExtent();
2019-05-15 11:32:36 +02:00
map.getControls().forEach((el) => {
if (el instanceof ol.control.ZoomToExtent) {
map.removeControl(el);
}
});
if (update) {
map.getView().fit(extent);
const zoom = map.getView().getZoom();
if (zoom > 20) {
map.getView().setZoom(20);
extent = map.getView().calculateExtent(map.getSize());
}
}
const zoomToExtentControl = new ol.control.ZoomToExtent({
extent,
label: getExtentImg()
});
map.addControl(zoomToExtentControl);
}
/**
* Clear map
*/
function clearMap() {
if (layerTrack) {
layerTrack.getSource().clear();
}
if (layerMarkers) {
layerMarkers.getSource().clear();
}
}
2019-09-03 21:45:40 +02:00
function getMarkerStyle(position, id, posLen) {
let iconStyle = olStyles['normal'];
if (position.hasComment() || position.hasImage()) {
if (id === posLen - 1) {
iconStyle = olStyles['stopExtra'];
} else if (id === 0) {
iconStyle = olStyles['startExtra'];
} else {
iconStyle = olStyles['extra'];
}
} else if (id === posLen - 1) {
iconStyle = olStyles['stop'];
} else if (id === 0) {
iconStyle = olStyles['start'];
}
return iconStyle;
}
2019-05-15 11:32:36 +02:00
/**
* Set marker
* @param {number} id
2019-06-29 12:54:32 +02:00
* @param {uTrack} track
2019-05-15 11:32:36 +02:00
*/
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.positions.length;
2019-05-15 11:32:36 +02:00
const marker = new ol.Feature({
geometry: new ol.geom.Point(ol.proj.fromLonLat([ position.longitude, position.latitude ]))
});
2019-09-03 21:45:40 +02:00
const iconStyle = getMarkerStyle(position, id, posLen);
2019-05-15 11:32:36 +02:00
marker.setStyle(iconStyle);
marker.setId(id);
layerMarkers.getSource().addFeature(marker);
}
/**
2019-06-29 12:54:32 +02:00
* Animate marker
* @param id Marker sequential id
2019-05-15 11:32:36 +02:00
*/
2019-06-29 12:54:32 +02:00
function animateMarker(id) {
const marker = layerMarkers.getSource().getFeatureById(id);
const initStyle = marker.getStyle();
2019-09-03 21:45:40 +02:00
const iconStyle = olStyles['hilite'];
2019-06-29 12:54:32 +02:00
marker.setStyle(iconStyle);
setTimeout(() => marker.setStyle(initStyle), 2000);
2019-05-15 11:32:36 +02:00
}
/**
* Get map bounds
* eg. (20.597985430276808, 52.15547181298076, 21.363595171488573, 52.33750879522563)
* @returns {number[]} Bounds
*/
function getBounds() {
const extent = map.getView().calculateExtent(map.getSize());
const bounds = ol.proj.transformExtent(extent, 'EPSG:900913', 'EPSG:4326');
const lon_sw = bounds[0];
const lat_sw = bounds[1];
const lon_ne = bounds[2];
const lat_ne = bounds[3];
return [ lon_sw, lat_sw, lon_ne, lat_ne ];
}
/**
* Zoom to track extent
*/
function zoomToExtent() {
map.getView().fit(layerMarkers.getSource().getExtent());
}
/**
* Zoom to bounds
* @param {number[]} bounds
*/
function zoomToBounds(bounds) {
const extent = ol.proj.transformExtent(bounds, 'EPSG:4326', 'EPSG:900913');
map.getView().fit(extent);
}
/**
* Update size
*/
function updateSize() {
map.updateSize();
}
/**
* Get extent image
* @returns {HTMLImageElement}
*/
function getExtentImg() {
const extentImg = document.createElement('img');
extentImg.src = 'images/extent.svg';
extentImg.style.width = '60%';
return extentImg;
}
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,
updateSize
}