nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

commit 9679d57d26d956c976d198bd06fdae793b19d622
parent 5a9335725946652db8788ebbc3061c331f0d3be3
Author: Sergej Orlov <wladimirych@gmail.com>
Date:   Fri,  9 Dec 2016 01:23:52 +0300

renamed main files in all modules to index.js

Diffstat:
Msrc/App.js | 18+++++++++---------
Msrc/layers.js | 12++++++------
Rsrc/lib/CORSProxy/proxy.js -> src/lib/CORSProxy/index.js | 0
Rsrc/lib/clipboardCopy/clipboardCopy.js -> src/lib/clipboardCopy/index.js | 0
Rsrc/lib/contextmenu/contextmenu.js -> src/lib/contextmenu/index.js | 0
Rsrc/lib/file-read/file-read.js -> src/lib/file-read/index.js | 0
Rsrc/lib/googleMapsApi/googleMapsApi.js -> src/lib/googleMapsApi/index.js | 0
Rsrc/lib/iconFromBackgroundImage/iconFromBackgroundImage.js -> src/lib/iconFromBackgroundImage/index.js | 0
Rsrc/lib/leaflet.control.caption/caption.js -> src/lib/leaflet.control.caption/index.js | 0
Dsrc/lib/leaflet.control.coordinates/coordinates.js | 175-------------------------------------------------------------------------------
Asrc/lib/leaflet.control.coordinates/index.js | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/leaflet.control.elevation-profile/elevation-profile.js | 792-------------------------------------------------------------------------------
Asrc/lib/leaflet.control.elevation-profile/index.js | 792+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/lib/leaflet.control.layers.adaptive-height/adaptive-height.js -> src/lib/leaflet.control.layers.adaptive-height/index.js | 0
Asrc/lib/leaflet.control.layers.configure/index.js | 211+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/leaflet.control.layers.configure/layers-configure.js | 211-------------------------------------------------------------------------------
Rsrc/lib/leaflet.control.layers.hotkeys/control.Layers-hotkeys.js -> src/lib/leaflet.control.layers.hotkeys/index.js | 0
Asrc/lib/leaflet.control.layers.minimize/index.js | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/leaflet.control.layers.minimize/minimize.js | 54------------------------------------------------------
Rsrc/lib/leaflet.control.layers.top-row/top-row.js -> src/lib/leaflet.control.layers.top-row/index.js | 0
Asrc/lib/leaflet.control.panoramas/index.js | 247+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/leaflet.control.panoramas/panoramas.js | 247-------------------------------------------------------------------------------
Msrc/lib/leaflet.control.printPages/control.js | 2+-
Msrc/lib/leaflet.control.track-list/track-list.js | 22+++++++++++-----------
Rsrc/lib/leaflet.controls.raise-on-focus/raise-on-focus.js -> src/lib/leaflet.controls.raise-on-focus/index.js | 0
Rsrc/lib/leaflet.fixAnimationBug/leaflet.fixAnimationBug.js -> src/lib/leaflet.fixAnimationBug/index.js | 0
Rsrc/lib/leaflet.layer.bing/bing.js -> src/lib/leaflet.layer.bing/index.js | 0
Rsrc/lib/leaflet.layer.canvasMarkers/canvasMarkers.js -> src/lib/leaflet.layer.canvasMarkers/index.js | 0
Dsrc/lib/leaflet.layer.geojson-ajax/geojson-ajax.js | 33---------------------------------
Asrc/lib/leaflet.layer.geojson-ajax/index.js | 33+++++++++++++++++++++++++++++++++
Dsrc/lib/leaflet.layer.google/google.js | 173-------------------------------------------------------------------------------
Asrc/lib/leaflet.layer.google/index.js | 173+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/leaflet.layer.nordeskart/index.js | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/leaflet.layer.nordeskart/norderskart.js | 89-------------------------------------------------------------------------------
Asrc/lib/leaflet.layer.soviet-topomaps-grid/index.js | 322+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/leaflet.layer.soviet-topomaps-grid/soviet-topomaps-grid.js | 321-------------------------------------------------------------------------------
Asrc/lib/leaflet.layer.westraPasses/index.js | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/leaflet.layer.westraPasses/westraPasses.js | 99-------------------------------------------------------------------------------
Msrc/lib/leaflet.layer.westraPasses/westraPassesMarkers.js | 10+++++-----
Rsrc/lib/leaflet.layer.yandex/yandex.js -> src/lib/leaflet.layer.yandex/index.js | 0
Rsrc/lib/leaflet.lineutil.simplifyLatLngs/simplify.js -> src/lib/leaflet.lineutil.simplifyLatLngs/index.js | 0
Rsrc/lib/leaflet.polyline-edit/edit_line.js -> src/lib/leaflet.polyline-edit/index.js | 0
Rsrc/lib/leaflet.polyline-measure/measured_line.js -> src/lib/leaflet.polyline-measure/index.js | 0
Rsrc/lib/notifications/notifications.js -> src/lib/notifications/index.js | 0
Rsrc/lib/popupWindow/popupWindow.js -> src/lib/popupWindow/index.js | 0
Rsrc/lib/xhr-promise/xhr-promise.js -> src/lib/xhr-promise/index.js | 0
46 files changed, 2227 insertions(+), 2226 deletions(-)

diff --git a/src/App.js b/src/App.js @@ -3,24 +3,24 @@ import './leaflet-fixes.css'; import L from 'leaflet'; import 'leaflet/dist/leaflet.css'; import 'lib/leaflet.control.printPages/control' -import 'lib/leaflet.control.caption/caption' +import 'lib/leaflet.control.caption' import config from './config' -import 'lib/leaflet.control.coordinates/coordinates'; -import enableLayersControlHotKeys from 'lib/leaflet.control.layers.hotkeys/control.Layers-hotkeys'; +import 'lib/leaflet.control.coordinates'; +import enableLayersControlHotKeys from 'lib/leaflet.control.layers.hotkeys'; import 'lib/leaflet.hashState/Leaflet.Map'; import 'lib/leaflet.hashState/Leaflet.Control.Layers'; -import fixAnimationBug from 'lib/leaflet.fixAnimationBug/leaflet.fixAnimationBug' +import fixAnimationBug from 'lib/leaflet.fixAnimationBug' import './adaptive.css'; -import 'lib/leaflet.control.panoramas/panoramas'; +import 'lib/leaflet.control.panoramas'; import 'lib/leaflet.control.track-list/track-list'; import 'lib/leaflet.control.track-list/control-ruler'; import 'lib/leaflet.control.track-list/track-list.hash-state'; import 'lib/leaflet.control.track-list/track-list.localstorage'; -import enableLayersControlAdaptiveHeight from 'lib/leaflet.control.layers.adaptive-height/adaptive-height'; -import enableLayersMinimize from 'lib/leaflet.control.layers.minimize/minimize'; -import enableLayersConfig from 'lib/leaflet.control.layers.configure/layers-configure'; +import enableLayersControlAdaptiveHeight from 'lib/leaflet.control.layers.adaptive-height'; +import enableLayersMinimize from 'lib/leaflet.control.layers.minimize'; +import enableLayersConfig from 'lib/leaflet.control.layers.configure'; import hashState from 'lib/leaflet.hashState/hashState'; -import raiseControlsOnFocus from 'lib/leaflet.controls.raise-on-focus/raise-on-focus'; +import raiseControlsOnFocus from 'lib/leaflet.controls.raise-on-focus'; import getLayers from 'layers'; diff --git a/src/layers.js b/src/layers.js @@ -1,11 +1,11 @@ import L from "leaflet"; -import 'lib/leaflet.layer.yandex/yandex'; -import 'lib/leaflet.layer.google/google'; -import 'lib/leaflet.layer.bing/bing'; +import 'lib/leaflet.layer.yandex'; +import 'lib/leaflet.layer.google'; +import 'lib/leaflet.layer.bing'; import config from './config'; -import 'lib/leaflet.layer.soviet-topomaps-grid/soviet-topomaps-grid'; -import 'lib/leaflet.layer.westraPasses/westraPasses'; -import 'lib/leaflet.layer.nordeskart/norderskart'; +import 'lib/leaflet.layer.soviet-topomaps-grid'; +import 'lib/leaflet.layer.westraPasses'; +import 'lib/leaflet.layer.nordeskart'; export default function getLayers() { const layers = [ diff --git a/src/lib/CORSProxy/proxy.js b/src/lib/CORSProxy/index.js diff --git a/src/lib/clipboardCopy/clipboardCopy.js b/src/lib/clipboardCopy/index.js diff --git a/src/lib/contextmenu/contextmenu.js b/src/lib/contextmenu/index.js diff --git a/src/lib/file-read/file-read.js b/src/lib/file-read/index.js diff --git a/src/lib/googleMapsApi/googleMapsApi.js b/src/lib/googleMapsApi/index.js diff --git a/src/lib/iconFromBackgroundImage/iconFromBackgroundImage.js b/src/lib/iconFromBackgroundImage/index.js diff --git a/src/lib/leaflet.control.caption/caption.js b/src/lib/leaflet.control.caption/index.js diff --git a/src/lib/leaflet.control.coordinates/coordinates.js b/src/lib/leaflet.control.coordinates/coordinates.js @@ -1,175 +0,0 @@ -import L from 'leaflet' -import './coordinates.css'; -import copyToClipboard from 'lib/clipboardCopy/clipboardCopy'; -import Contextmenu from 'lib/contextmenu/contextmenu'; - -function pad(s, n) { - var j = s.indexOf('.'); - if (j === -1) { - j = s.length; - } - var zeroes = (n - j); - if (zeroes > 0) { - s = Array(zeroes + 1).join('0') + s; - } - return s; -} - -L.Control.Coordinates = L.Control.extend({ - options: { - position: 'bottomleft' - }, - - onAdd: function(map) { - this._map = map; - var container = this._container = L.DomUtil.create('div', 'leaflet-control leaflet-control-button leaflet-control-coordinates'); - L.DomEvent.disableClickPropagation(container); - if (!L.Browser.touch) { - L.DomEvent.disableScrollPropagation(container); - } - this._field_lat = L.DomUtil.create('div', 'leaflet-control-coordinates-text', container); - this._field_lon = L.DomUtil.create('div', 'leaflet-control-coordinates-text', container); - L.DomEvent - .on(container, { - 'dblclick': L.DomEvent.stop, - 'click': this.onClick - }, this); - map.on('mousemove', this.onMouseMove, this); - this.menu = new Contextmenu([ - {text: 'Click to copy to clipboard', callback: this.prepareForClickOnMap.bind(this)}, - '-', - {text: '&plusmn;ddd.ddddd', callback: this.onMenuSelect.bind(this, 'd')}, - {text: 'ddd.ddddd&deg;', callback: this.onMenuSelect.bind(this, 'D')}, - {text: 'ddd&deg;mm.mmm\'', callback: this.onMenuSelect.bind(this, 'DM')}, - {text: 'ddd&deg;mm\'ss.s"', callback: this.onMenuSelect.bind(this, 'DMS')} - ] - ); - this.loadStateFromStorage(); - this.onMouseMove(); - L.DomEvent.on(container, 'contextmenu', this.onRightClick, this); - return container; - }, - - loadStateFromStorage: function() { - var active = false, - fmt = 'D'; - if (window.Storage && window.localStorage) { - active = localStorage.leafletCoordinatesActive === '1'; - fmt = localStorage.leafletCoordinatesFmt || fmt; - } - this.setEnabled(active); - this.setFormat(fmt); - }, - - saveStateToStorage: function() { - if (!(window.Storage && window.localStorage)) { - return; - } - localStorage.leafletCoordinatesActive = this.isEnabled() ? '1' : '0'; - localStorage.leafletCoordinatesFmt = this.fmt; - }, - - formatCoodinate: function(value, isLat) { - if (value === undefined) { - return '-------'; - } - - var h, d, m, s; - if (isLat) { - h = (value < 0) ? 'S' : 'N'; - } else { - h = (value < 0) ? 'W' : 'E'; - } - if (this.fmt === 'd') { - d = value.toFixed(5); - d = pad(d, isLat ? 2 : 3); - return d; - } - - value = Math.abs(value); - if (this.fmt === 'D') { - d = value.toFixed(5); - d = pad(d, isLat ? 2 : 3); - return `${h} ${d}&deg;`; - } - if (this.fmt === 'DM') { - d = Math.floor(value).toString(); - d = pad(d, isLat ? 2 : 3); - m = ((value - d) * 60).toFixed(3); - m = pad(m, 2); - return `${h} ${d}&deg;${m}'` - } - if (this.fmt === 'DMS') { - d = Math.floor(value).toString(); - d = pad(d, isLat ? 2 : 3); - m = Math.floor((value - d) * 60).toString(); - m = pad(m, 2); - s = ((value - d - m / 60) * 3600).toFixed(2); - s = pad(s, 2); - return `${h} ${d}&deg;${m}'${s}"`; - } - }, - - onMenuSelect: function(fmt) { - this.setFormat(fmt); - this.saveStateToStorage(); - }, - - setFormat: function(fmt) { - this.fmt = fmt; - this.onMouseMove(); - }, - - onMouseMove: function(e) { - if (!this.isEnabled()) { - return; - } - var lat, lng; - if (e) { - ({lat, lng} = e.latlng); - } - this._field_lat.innerHTML = this.formatCoodinate(lat, true); - this._field_lon.innerHTML = this.formatCoodinate(lng, false); - }, - - setEnabled: function(enabled) { - if (enabled) { - L.DomUtil.addClass(this._container, 'expanded'); - L.DomUtil.addClass(this._map._container, 'coordinates-control-active'); - } else { - L.DomUtil.removeClass(this._container, 'expanded'); - L.DomUtil.removeClass(this._map._container, 'coordinates-control-active'); - } - }, - - isEnabled: function() { - return L.DomUtil.hasClass(this._container, 'expanded'); - }, - - onClick: function(e) { - this.setEnabled(!this.isEnabled()); - this.saveStateToStorage(); - this.onMouseMove(); - }, - - onRightClick: function(e) { - this.menu.show(e); - }, - - onMapClick: function(e) { - var s = this.formatCoodinate(e.latlng.lat, true) + ' ' + this.formatCoodinate(e.latlng.lng, false); - s = s.replace(/&deg;/g, '°'); - copyToClipboard(s, e.originalEvent); - }, - - prepareForClickOnMap: function() { - this._map.once('click', this.onMapClick, this); - } - - - - // TODO: onRemove - - } -); - diff --git a/src/lib/leaflet.control.coordinates/index.js b/src/lib/leaflet.control.coordinates/index.js @@ -0,0 +1,175 @@ +import L from 'leaflet' +import './coordinates.css'; +import copyToClipboard from 'lib/clipboardCopy'; +import Contextmenu from 'lib/contextmenu'; + +function pad(s, n) { + var j = s.indexOf('.'); + if (j === -1) { + j = s.length; + } + var zeroes = (n - j); + if (zeroes > 0) { + s = Array(zeroes + 1).join('0') + s; + } + return s; +} + +L.Control.Coordinates = L.Control.extend({ + options: { + position: 'bottomleft' + }, + + onAdd: function(map) { + this._map = map; + var container = this._container = L.DomUtil.create('div', 'leaflet-control leaflet-control-button leaflet-control-coordinates'); + L.DomEvent.disableClickPropagation(container); + if (!L.Browser.touch) { + L.DomEvent.disableScrollPropagation(container); + } + this._field_lat = L.DomUtil.create('div', 'leaflet-control-coordinates-text', container); + this._field_lon = L.DomUtil.create('div', 'leaflet-control-coordinates-text', container); + L.DomEvent + .on(container, { + 'dblclick': L.DomEvent.stop, + 'click': this.onClick + }, this); + map.on('mousemove', this.onMouseMove, this); + this.menu = new Contextmenu([ + {text: 'Click to copy to clipboard', callback: this.prepareForClickOnMap.bind(this)}, + '-', + {text: '&plusmn;ddd.ddddd', callback: this.onMenuSelect.bind(this, 'd')}, + {text: 'ddd.ddddd&deg;', callback: this.onMenuSelect.bind(this, 'D')}, + {text: 'ddd&deg;mm.mmm\'', callback: this.onMenuSelect.bind(this, 'DM')}, + {text: 'ddd&deg;mm\'ss.s"', callback: this.onMenuSelect.bind(this, 'DMS')} + ] + ); + this.loadStateFromStorage(); + this.onMouseMove(); + L.DomEvent.on(container, 'contextmenu', this.onRightClick, this); + return container; + }, + + loadStateFromStorage: function() { + var active = false, + fmt = 'D'; + if (window.Storage && window.localStorage) { + active = localStorage.leafletCoordinatesActive === '1'; + fmt = localStorage.leafletCoordinatesFmt || fmt; + } + this.setEnabled(active); + this.setFormat(fmt); + }, + + saveStateToStorage: function() { + if (!(window.Storage && window.localStorage)) { + return; + } + localStorage.leafletCoordinatesActive = this.isEnabled() ? '1' : '0'; + localStorage.leafletCoordinatesFmt = this.fmt; + }, + + formatCoodinate: function(value, isLat) { + if (value === undefined) { + return '-------'; + } + + var h, d, m, s; + if (isLat) { + h = (value < 0) ? 'S' : 'N'; + } else { + h = (value < 0) ? 'W' : 'E'; + } + if (this.fmt === 'd') { + d = value.toFixed(5); + d = pad(d, isLat ? 2 : 3); + return d; + } + + value = Math.abs(value); + if (this.fmt === 'D') { + d = value.toFixed(5); + d = pad(d, isLat ? 2 : 3); + return `${h} ${d}&deg;`; + } + if (this.fmt === 'DM') { + d = Math.floor(value).toString(); + d = pad(d, isLat ? 2 : 3); + m = ((value - d) * 60).toFixed(3); + m = pad(m, 2); + return `${h} ${d}&deg;${m}'` + } + if (this.fmt === 'DMS') { + d = Math.floor(value).toString(); + d = pad(d, isLat ? 2 : 3); + m = Math.floor((value - d) * 60).toString(); + m = pad(m, 2); + s = ((value - d - m / 60) * 3600).toFixed(2); + s = pad(s, 2); + return `${h} ${d}&deg;${m}'${s}"`; + } + }, + + onMenuSelect: function(fmt) { + this.setFormat(fmt); + this.saveStateToStorage(); + }, + + setFormat: function(fmt) { + this.fmt = fmt; + this.onMouseMove(); + }, + + onMouseMove: function(e) { + if (!this.isEnabled()) { + return; + } + var lat, lng; + if (e) { + ({lat, lng} = e.latlng); + } + this._field_lat.innerHTML = this.formatCoodinate(lat, true); + this._field_lon.innerHTML = this.formatCoodinate(lng, false); + }, + + setEnabled: function(enabled) { + if (enabled) { + L.DomUtil.addClass(this._container, 'expanded'); + L.DomUtil.addClass(this._map._container, 'coordinates-control-active'); + } else { + L.DomUtil.removeClass(this._container, 'expanded'); + L.DomUtil.removeClass(this._map._container, 'coordinates-control-active'); + } + }, + + isEnabled: function() { + return L.DomUtil.hasClass(this._container, 'expanded'); + }, + + onClick: function(e) { + this.setEnabled(!this.isEnabled()); + this.saveStateToStorage(); + this.onMouseMove(); + }, + + onRightClick: function(e) { + this.menu.show(e); + }, + + onMapClick: function(e) { + var s = this.formatCoodinate(e.latlng.lat, true) + ' ' + this.formatCoodinate(e.latlng.lng, false); + s = s.replace(/&deg;/g, '°'); + copyToClipboard(s, e.originalEvent); + }, + + prepareForClickOnMap: function() { + this._map.once('click', this.onMapClick, this); + } + + + + // TODO: onRemove + + } +); + diff --git a/src/lib/leaflet.control.elevation-profile/elevation-profile.js b/src/lib/leaflet.control.elevation-profile/elevation-profile.js @@ -1,792 +0,0 @@ -import L from 'leaflet'; -import './elevation-profile.css'; -import {fetch} from 'lib/xhr-promise/xhr-promise'; -import config from 'config'; - -function createSvg(tagName, attributes, parent) { - var element = document.createElementNS('http://www.w3.org/2000/svg', tagName); - if (attributes) { - var keys = Object.keys(attributes), - key, value; - for (var i = 0; i < keys.length; i++) { - key = keys[i]; - value = attributes[key]; - element.setAttribute(key, value); - } - } - if (parent) { - parent.appendChild(element); - } - return element; -} - -function pointOnSegmentAtDistance(p1, p2, dist) { - //FIXME: we should place markers along projected line to avoid transformation distortions - var q = dist / p1.distanceTo(p2), - x = p1.lng + (p2.lng - p1.lng) * q, - y = p1.lat + (p2.lat - p1.lat) * q; - return L.latLng(y, x); -} - - -function gradientToAngle(g) { - return Math.round(Math.atan(g) * 180 / Math.PI); -} - -function pathRegularSamples(latlngs, step) { - var samples = [], - lastSampleDist = 0, - lastPointDistance = 0, - nextPointDistance = 0, - segmentLength, i; - - samples.push(latlngs[0]); - for (i = 1; i < latlngs.length; i++) { - segmentLength = latlngs[i].distanceTo(latlngs[i - 1]); - nextPointDistance = lastPointDistance + segmentLength; - if (nextPointDistance >= lastSampleDist + step) { - while (lastSampleDist + step <= nextPointDistance) { - lastSampleDist += step; - samples.push( - pointOnSegmentAtDistance(latlngs[i - 1], latlngs[i], lastSampleDist - lastPointDistance) - ); - } - } - lastPointDistance = nextPointDistance; - } - if (samples.length < 2) { - samples.push(latlngs[latlngs.length - 1]); - } - return samples; -} - -function offestFromEvent(e) { - if (e.offsetX === undefined) { - var rect = e.target.getBoundingClientRect(); - return { - offsetX: e.clientX - rect.left, - offestY: e.clientY - rect.top - } - } else { - return { - offsetX: e.offsetX, - offestY: e.offsetY - } - } -} - -function movementFromEvents(e1, e2) { - return { - movementX: e2.clientX - e1.clientX, - movementY: e2.clientY - e1.clientY - } -} - -var DragEvents = L.Class.extend({ - options: { - dragTolerance: 2, - dragButtons: {0: true} - }, - - includes: L.Mixin.Events, - - initialize: function(eventsSource, eventsTarget, options) { - options = L.setOptions(this, options); - if (eventsTarget) { - this.eventsTarget = eventsTarget; - } else { - this.eventsTarget = this; - } - this.dragStartPos = []; - this.prevEvent = []; - this.isDragging = []; - - L.DomEvent.on(eventsSource, 'mousemove', this.onMouseMove, this); - L.DomEvent.on(eventsSource, 'mouseup', this.onMouseUp, this); - L.DomEvent.on(eventsSource, 'mousedown', this.onMouseDown, this); - L.DomEvent.on(eventsSource, 'mouseleave', this.onMouseLeave, this); - }, - - onMouseDown: function(e) { - if (this.options.dragButtons[e.button]) { - e._offset = offestFromEvent(e); - this.dragStartPos[e.button] = e; - this.prevEvent[e.button] = e; - L.DomUtil.disableImageDrag(); - L.DomUtil.disableTextSelection(); - } - }, - - onMouseUp: function(e) { - L.DomUtil.enableImageDrag(); - L.DomUtil.enableTextSelection(); - - if (this.options.dragButtons[e.button]) { - this.dragStartPos[e.button] = null; - if (this.isDragging[e.button]) { - this.isDragging[e.button] = false; - this.fire('dragend', L.extend({dragButton: e.button, origEvent: e}, - offestFromEvent(e), movementFromEvents(this.prevEvent[e.button], e) - ) - ); - } else { - this.fire('click', L.extend({dragButton: e.button, origEvent: e}, - offestFromEvent(e) - ) - ); - } - } - }, - - onMouseMove: function(e) { - var i, button, self = this; - - function exceedsTolerance(button) { - var tolerance = self.options.dragTolerance; - return Math.abs(e.clientX - self.dragStartPos[button].clientX) > tolerance || - Math.abs(e.clientY - self.dragStartPos[button].clientY) > tolerance; - } - - var dragButtons = Object.keys(this.options.dragButtons); - for (i = 0; i < dragButtons.length; i++) { - button = dragButtons[i]; - if (this.isDragging[button]) { - this.eventsTarget.fire('drag', L.extend({dragButton: button, origEvent: e}, - offestFromEvent(e), movementFromEvents(this.prevEvent[button], e) - ) - ); - } else if (this.dragStartPos[button] && exceedsTolerance(button)) { - this.isDragging[button] = true; - this.eventsTarget.fire('dragstart', L.extend( - {dragButton: button, origEvent: this.dragStartPos[button]}, - this.dragStartPos[button]._offset - ) - ); - this.eventsTarget.fire('drag', L.extend({ - dragButton: button, - origEvent: e, - startEvent: self.dragStartPos[button] - }, offestFromEvent(e), movementFromEvents(this.prevEvent[button], e) - ) - ); - } - this.prevEvent[button] = e; - } - }, - - onMouseLeave: function(e) { - var i, button; - var dragButtons = Object.keys(this.options.dragButtons); - for (i = 0; i < dragButtons.length; i++) { - button = dragButtons[i]; - if (this.isDragging[button]) { - this.isDragging[button] = false; - this.fire('dragend', L.extend({dragButton: button, origEvent: e}, - offestFromEvent(e), movementFromEvents(this.prevEvent[button], e) - ) - ); - } - } - this.dragStartPos = {}; - } - } -); - -L.Control.ElevationProfile = L.Class.extend({ - options: { - elevationsServer: config.elevationsServer, - samplingInterval: 50 - }, - - initialize: function(latlngs, options) { - L.setOptions(this, options); - this.path = latlngs; - var samples = this.samples = pathRegularSamples(this.path, this.options.samplingInterval); - var self = this; - this.horizZoom = 1; - this.dragStart = null; - - this._getElevation(samples).then(function(values) { - self.values = values; - self.updateGraph(); - } - ); - this.values = null; - - }, - - addTo: function(map) { - this._map = map; - var container = this._container = L.DomUtil.create('div', 'elevation-profile-container'); - if (!L.Browser.touch) { - L.DomEvent - .disableClickPropagation(container) - .disableScrollPropagation(container); - } else { - L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation); - } - this._map._controlContainer.appendChild(container); - this.setupContainerLayout(); - this.updateGraph(); - this.trackMarker = L.marker([1000, 0], {clickable: false, icon: L.divIcon()}); - this.polyline = L.polyline(this.path, {weight: 30, opacity: 0}).addTo(map); - this.polyline.on('mousemove', this.onLineMouseMove, this); - this.polyline.on('mouseover', this.onLineMouseEnter, this); - this.polyline.on('mouseout', this.onLineMouseLeave, this); - this.polyLineSelection = L.polyline([], {weight: 20, opacity: .5, color: 'yellow', lineCap: 'butt'}); - return this; - }, - - setupContainerLayout: function() { - var horizZoom = this.horizZoom = 1; - var container = this._container; - this.propsContainer = L.DomUtil.create('div', 'elevation-profile-properties', container); - this.leftAxisLables = L.DomUtil.create('div', 'elevation-profile-left-axis', container); - this.closeButton = L.DomUtil.create('div', 'elevation-profile-close', container); - L.DomEvent.on(this.closeButton, 'click', this.onCloseButtonClick, this); - this.drawingContainer = L.DomUtil.create('div', 'elevation-profile-drawingContainer', container); - this.graphCursor = L.DomUtil.create('div', 'elevation-profile-cursor elevation-profile-cursor-hidden', - this.drawingContainer - ); - this.graphCursorLabel = - L.DomUtil.create('div', 'elevation-profile-cursor-label elevation-profile-cursor-hidden', - this.drawingContainer - ); - this.graphSelection = L.DomUtil.create('div', 'elevation-profile-selection elevation-profile-cursor-hidden', - this.drawingContainer - ); - var svgWidth = this.svgWidth = this.drawingContainer.clientWidth * horizZoom, - svgHeight = this.svgHeight = this.drawingContainer.clientHeight; - var svg = this.svg = createSvg('svg', {width: svgWidth, height: svgHeight}, this.drawingContainer); - L.DomEvent.on(svg, 'mousemove', this.onSvgMouseMove, this); - L.DomEvent.on(svg, 'mouseenter', this.onSvgEnter, this); - L.DomEvent.on(svg, 'mouseleave', this.onSvgLeave, this); - L.DomEvent.on(svg, 'mousewheel', this.onSvgMouseWheel, this); - this.svgDragEvents = new DragEvents(this.drawingContainer, null, {dragButtons: {0: true, 2: true}}); - this.svgDragEvents.on('dragstart', this.onSvgDragStart, this); - this.svgDragEvents.on('dragend', this.onSvgDragEnd, this); - this.svgDragEvents.on('drag', this.onSvgDrag, this); - this.svgDragEvents.on('click', this.onSvgClick, this); - L.DomEvent.on(svg, 'dblclick', this.onSvgDblClick, this); - }, - - removeFrom: function(map) { - if (!this._map) { - return; - } - this._map._controlContainer.removeChild(this._container); - map.removeLayer(this.polyline); - map.removeLayer(this.trackMarker); - map.removeLayer(this.polyLineSelection); - this._map = null; - return this; - }, - - onSvgDragStart: function(e) { - - if (e.dragButton === 0) { - // FIXME: restore hiding when we make display of selection on map - // this.cursorHide(); - this.polyLineSelection.addTo(this._map).bringToBack(); - this.dragStart = e.offsetX; - } - }, - - xToIndex: function(x) { - return x / (this.svgWidth - 1) * (this.values.length - 1); - }, - - updateGraphSelection: function(e) { - if (this.dragStart === null) { - return; - } - var selStart, selEnd; - if (e) { - var x = e.offsetX; - selStart = Math.min(x, this.dragStart); - selEnd = Math.max(x, this.dragStart); - this.selStartInd = Math.round(this.xToIndex(selStart)); - this.selEndInd = Math.round(this.xToIndex(selEnd)); - - if (this.selStartInd < 0) { - this.selStartInd = 0; - } - if (this.selEndInd > this.values.length - 1) { - this.selEndInd = this.values.length - 1; - } - - } else { - selStart = this.selStartInd * (this.svgWidth - 1) / (this.values.length - 1); - selEnd = this.selEndInd * (this.svgWidth - 1) / (this.values.length - 1); - } - this.graphSelection.style.left = selStart + 'px'; - this.graphSelection.style.width = (selEnd - selStart) + 'px'; - L.DomUtil.removeClass(this.graphSelection, 'elevation-profile-cursor-hidden'); - }, - - onSvgDragEnd: function(e) { - if (e.dragButton === 0) { - this.cursorShow(); - this.updateGraphSelection(e); - var stats = this.calcProfileStats(this.values.slice(this.selStartInd, this.selEndInd + 1), true); - this.updatePropsDisplay(stats); - L.DomUtil.addClass(this.propsContainer, 'elevation-profile-properties-selected'); - } - if (e.dragButton === 2) { - this.drawingContainer.scrollLeft -= e.movementX; - } - }, - - onSvgDrag: function(e) { - if (e.dragButton === 0) { - this.updateGraphSelection(e); - this.polyLineSelection.setLatLngs(this.samples.slice(this.selStartInd, this.selEndInd + 1)); - } - if (e.dragButton === 2) { - this.drawingContainer.scrollLeft -= e.movementX; - } - }, - - onSvgClick: function(e) { - if (e.dragButton === 0) { - this.dragStart = null; - L.DomUtil.addClass(this.graphSelection, 'elevation-profile-cursor-hidden'); - L.DomUtil.removeClass(this.propsContainer, 'elevation-profile-properties-selected'); - this._map.removeLayer(this.polyLineSelection); - if (this.stats) { - this.updatePropsDisplay(this.stats); - } - } - if (e.dragButton === 2) { - this.setMapPositionAtIndex(Math.round(this.xToIndex(e.offsetX))); - } - }, - - onSvgDblClick: function(e) { - this.setMapPositionAtIndex(Math.round(this.xToIndex(e.offsetX))); - }, - - setMapPositionAtIndex: function(ind) { - var latlng = this.samples[ind]; - if (latlng) { - this._map.panTo(latlng); - } - }, - - onSvgMouseWheel: function(e) { - var oldHorizZoom = this.horizZoom; - this.horizZoom += L.DomEvent.getWheelDelta(e); - if (this.horizZoom < 1) { - this.horizZoom = 1; - } - if (this.horizZoom > 10) { - this.horizZoom = 10; - } - - var x = offestFromEvent(e).offsetX; - var ind = this.xToIndex(x); - - var newScrollLeft = this.drawingContainer.scrollLeft + - offestFromEvent(e).offsetX * (this.horizZoom / oldHorizZoom - 1); - if (newScrollLeft < 0) { - newScrollLeft = 0; - } - - this.svgWidth = this.drawingContainer.clientWidth * this.horizZoom; - this.svg.setAttribute('width', this.svgWidth + 'px'); - this.setupGraph(); - if (newScrollLeft > this.svgWidth - this.drawingContainer.clientWidth) { - newScrollLeft = this.svgWidth - this.drawingContainer.clientWidth; - } - this.drawingContainer.scrollLeft = newScrollLeft; - - this.cursorHide(); - this.setCursorPosition(ind); - this.cursorShow(); - this.updateGraphSelection(); - }, - - - updateGraph: function() { - if (!this._map || !this.values) { - return; - } - - this.stats = this.calcProfileStats(this.values); - this.updatePropsDisplay(this.stats); - this.setupGraph(); - }, - - updatePropsDisplay: function(stats) { - if (!this._map) { - return; - } - this.propsContainer.innerHTML = ''; - var ascentAngleStr = isNaN(stats.angleAvgAscent) ? '-' : L.Util.template('{avg} / {max}&deg;', - {avg: stats.angleAvgAscent, max: stats.angleMaxAscent} - ); - var descentAngleStr = isNaN(stats.angleAvgDescent) ? '-' : L.Util.template('{avg} / {max}&deg;', - {avg: stats.angleAvgDescent, max: stats.angleMaxDescent} - ); - - this.propsContainer.innerHTML = - '<table>' + - '<tr><td>Max elevation:</td><td>' + Math.round(stats.max) + '</td></tr>' + - '<tr><td>Min elevation:</td><td>' + Math.round(stats.min) + '</td></tr>' + - '<tr class="start-group"><td>Start elevation:</td><td>' + Math.round(stats.start) + '</td></tr>' + - '<tr><td>Finish elevation:</td><td>' + Math.round(stats.end) + '</td></tr>' + - '<tr><td>Start to finish elevation change:</td><td>' + Math.round(stats.finalAscent) + '</td></tr>' + - '<tr class="start-group"><td>Avg / Max ascent inclination:</td><td>' + ascentAngleStr + '</td></tr>' + - '<tr><td>Avg / Max descent inclination:</td><td>' + descentAngleStr + '</td></tr>' + - '<tr class="start-group"><td>Total ascent:</td><td>' + Math.round(stats.ascent) + '</td></tr>' + - '<tr><td>Total descent:</td><td>' + Math.round(stats.descent) + '</td></tr>' + - '<tr class="start-group"><td>Distance:</td><td>' + (stats.distance / 1000).toFixed(1) + ' km</td></tr>' + - '</table>' - }, - - calcGridValues: function(minValue, maxValue) { - var ticksNs = [3, 4, 5], - tickSteps = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000], - ticks = [], - i, j, k, ticksN, tickStep, tick1, tick2; - for (i = 0; i < tickSteps.length; i++) { - tickStep = tickSteps[i]; - for (j = 0; j < ticksNs.length; j++) { - ticksN = ticksNs[j]; - tick1 = Math.floor(minValue / tickStep); - tick2 = Math.ceil(maxValue / tickStep); - if ((tick2 - tick1) < ticksN) { - for (k = tick1; k < tick1 + ticksN; k++) { - ticks.push(k * tickStep); - } - return ticks; - } - } - } - }, - - filterElevations: function(values, tolerance) { - var filtered = values.slice(0); - if (filtered.length < 3) { - return filtered; - } - var scanStart, scanEnd, job, linearValue, linearDelta, maxError, maxErrorInd, i, error; - var queue = [[0, filtered.length - 1]]; - while (queue.length) { - job = queue.pop(); - scanStart = job[0]; - scanEnd = job[1]; - linearValue = filtered[scanStart]; - linearDelta = (filtered[scanEnd] - filtered[scanStart]) / (scanEnd - scanStart); - maxError = null; - maxErrorInd = null; - for (i = scanStart + 1; i < scanEnd; i++) { - linearValue += linearDelta; - error = Math.abs(filtered[i] - linearValue); - if (error === null || error > maxError) { - maxError = error; - maxErrorInd = i; - } - } - if (maxError > tolerance) { - if (scanEnd > scanStart + 2) { - queue.push([scanStart, maxErrorInd]); - queue.push([maxErrorInd, scanEnd]); - } - } else { - filtered.splice(scanStart + 1, scanEnd - scanStart - 1); - } - } - return filtered; - }, - - calcProfileStats: function(values, partial) { - var stats = {}, - gradient, i; - stats.min = Math.min.apply(null, values); - stats.max = Math.max.apply(null, values); - stats.finalAscent = values[values.length - 1] - values[0]; - var ascents = [], - descents = []; - for (i = 1; i < values.length; i++) { - gradient = (values[i] - values[i - 1]); - if (gradient > 0) { - ascents.push(gradient); - } else if (gradient < 0) { - descents.push(-gradient); - } - } - function sum(a, b) { - return a + b; - } - - stats.gradientAvgAscent = ascents.reduce(sum, 0) / ascents.length / this.options.samplingInterval; - stats.gradientMinAscent = Math.min.apply(null, ascents) / this.options.samplingInterval; - stats.gradientMaxAscent = Math.max.apply(null, ascents) / this.options.samplingInterval; - stats.gradientAvgDescent = descents.reduce(sum, 0) / descents.length / this.options.samplingInterval; - stats.gradientMinDescent = Math.min.apply(null, descents) / this.options.samplingInterval; - stats.gradientMaxDescent = Math.max.apply(null, descents) / this.options.samplingInterval; - - stats.angleAvgAscent = gradientToAngle(stats.gradientAvgAscent); - stats.angleMinAscent = gradientToAngle(stats.gradientMinAscent); - stats.angleMaxAscent = gradientToAngle(stats.gradientMaxAscent); - stats.angleAvgDescent = gradientToAngle(stats.gradientAvgDescent); - stats.angleMinDescent = gradientToAngle(stats.gradientMinDescent); - stats.angleMaxDescent = gradientToAngle(stats.gradientMaxDescent); - - stats.start = values[0]; - stats.end = values[values.length - 1]; - stats.distance = (values.length - 1) * this.options.samplingInterval; - - var filterTolerance = 5; - var filtered = this.filterElevations(values, filterTolerance); - var ascent = 0, - descent = 0, - delta; - for (i = 1; i < filtered.length; i++) { - delta = filtered[i] - filtered[i - 1]; - if (delta < 0) { - descent += -delta; - } else { - ascent += delta; - } - } - stats.ascent = ascent; - stats.descent = descent; - - return stats; - - }, - - setCursorPosition: function(ind) { - var distance = this.options.samplingInterval * ind; - distance = (distance / 1000).toFixed(2); - var gradient = (this.values[Math.ceil(ind)] - this.values[Math.floor(ind)]) / this.options.samplingInterval; - var angle = Math.round(Math.atan(gradient) * 180 / Math.PI); - gradient = Math.round(gradient * 100); - - var x = Math.round(ind / (this.values.length - 1) * (this.svgWidth - 1)); - var indInt = Math.round(ind); - var elevation = this.values[indInt]; - this.graphCursorLabel.innerHTML = L.Util.template('{ele} m<br>{dist} km<br>{angle}&deg;', - {ele: Math.round(elevation), dist: distance, grad: gradient, angle: angle} - ); - - this.graphCursor.style.left = x + 'px'; - this.graphCursorLabel.style.left = x + 'px'; - if (this.drawingContainer.getBoundingClientRect().left - this.drawingContainer.scrollLeft + x + - this.graphCursorLabel.offsetWidth >= this._container.getBoundingClientRect().right) { - L.DomUtil.addClass(this.graphCursorLabel, 'elevation-profile-cursor-label-left'); - } else { - L.DomUtil.removeClass(this.graphCursorLabel, 'elevation-profile-cursor-label-left'); - } - - var markerPos; - if (ind <= 0) { - markerPos = this.samples[0]; - } else if (ind >= this.samples.length - 1) { - markerPos = this.samples[this.samples.length - 1]; - } else { - var p1 = this.samples[Math.floor(ind)], - p2 = this.samples[Math.ceil(ind)], - indFrac = ind - Math.floor(ind); - markerPos = [p1.lat + (p2.lat - p1.lat) * indFrac, p1.lng + (p2.lng - p1.lng) * indFrac]; - } - this.trackMarker.setLatLng(markerPos); - var label = L.Util.template('{ele} m<br>{dist} km<br>{angle}&deg;', - {ele: Math.round(elevation), dist: distance, grad: gradient, angle: angle} - ); - var icon = L.divIcon({ - className: 'elevation-profile-marker', - html: '<div class="elevation-profile-marker-icon"></div><div class="elevation-profile-marker-label">' + - label + '</div>' - } - ); - this.trackMarker.setIcon(icon); - }, - - onSvgMouseMove: function(e) { - if (!this.values) { - return; - } - var x = offestFromEvent(e).offsetX; - var ind = (x / (this.svgWidth - 1) * (this.values.length - 1)); - this.setCursorPosition(ind); - }, - - cursorShow: function() { - L.DomUtil.removeClass(this.graphCursor, 'elevation-profile-cursor-hidden'); - L.DomUtil.removeClass(this.graphCursorLabel, 'elevation-profile-cursor-hidden'); - this._map.addLayer(this.trackMarker); - }, - - cursorHide: function() { - L.DomUtil.addClass(this.graphCursor, 'elevation-profile-cursor-hidden'); - L.DomUtil.addClass(this.graphCursorLabel, 'elevation-profile-cursor-hidden'); - this._map.removeLayer(this.trackMarker); - }, - - onSvgEnter: function() { - this.cursorShow(); - }, - - onSvgLeave: function() { - this.cursorHide(); - }, - - onLineMouseEnter: function() { - this.cursorShow(); - }, - - onLineMouseLeave: function() { - this.cursorHide(); - }, - - onLineMouseMove: function(e) { - function sqrDist(latlng1, latlng2) { - var dx = (latlng1.lng - latlng2.lng); - var dy = (latlng1.lat - latlng2.lat); - return dx * dx + dy * dy; - } - - var nearestInd = null, ind, - minDist = null, - mouseLatlng = e.latlng, - i, sampleLatlng, dist, di; - for (i = 0; i < this.samples.length; i++) { - sampleLatlng = this.samples[i]; - dist = sqrDist(sampleLatlng, mouseLatlng); - if (nearestInd === null || dist < minDist) { - nearestInd = i; - minDist = dist; - } - } - - if (nearestInd !== null) { - ind = nearestInd; - if (nearestInd > 0) { - var prevDist = sqrDist(mouseLatlng, this.samples[nearestInd - 1]), - prevSampleDist = sqrDist(this.samples[nearestInd], this.samples[nearestInd - 1]); - } - if (nearestInd < this.samples.length - 1) { - var nextDist = sqrDist(mouseLatlng, this.samples[nearestInd + 1]), - nextSampleDist = sqrDist(this.samples[nearestInd], this.samples[nearestInd + 1]); - } - - if (nearestInd === 0) { - if (nextDist < minDist + nextSampleDist) { - di = (minDist - nextDist) / 2 / nextSampleDist + 1 / 2; - } else { - di = .001; - } - } else if (nearestInd === this.samples.length - 1) { - if (prevDist < minDist + prevSampleDist) { - di = -((minDist - prevDist) / 2 / prevSampleDist + 1 / 2); - } else { - di = -0.001 - } - } else { - if (prevDist < nextDist) { - di = -((minDist - prevDist) / 2 / prevSampleDist + 1 / 2); - } else { - di = (minDist - nextDist) / 2 / nextSampleDist + 1 / 2; - } - } - if (di < -1) { - di = -1; - } - if (di > 1) { - di = 1; - } - this.setCursorPosition(ind + di); - } - - }, - - - setupGraph: function() { - if (!this._map) { - return; - } - - while (this.svg.hasChildNodes()) { - this.svg.removeChild(this.svg.lastChild); - } - while (this.leftAxisLables.hasChildNodes()) { - this.leftAxisLables.removeChild(this.leftAxisLables.lastChild); - } - - var maxValue = Math.max.apply(null, this.values), - minValue = Math.min.apply(null, this.values), - svg = this.svg, - path, i, horizStep, verticalMultiplier, x, y, gridValues, label; - - - var paddingBottom = 8 + 16, - paddingTop = 8; - - gridValues = this.calcGridValues(minValue, maxValue); - var gridStep = (this.svgHeight - paddingBottom - paddingTop) / (gridValues.length - 1); - for (i = 0; i < gridValues.length; i++) { - y = Math.round(i * gridStep - 0.5) + 0.5 + paddingTop; - path = L.Util.template('M{x1} {y} L{x2} {y}', {x1: 0, x2: this.svgWidth * this.horizZoom, y: y}); - createSvg('path', {d: path, 'stroke-width': '1px', stroke: 'green', fill: 'none'}, svg); - - label = L.DomUtil.create('div', 'elevation-profile-grid-label', this.leftAxisLables); - label.innerHTML = gridValues[gridValues.length - i - 1]; - label.style.top = (gridStep * i + paddingTop) + 'px'; - } - - horizStep = this.svgWidth / (this.values.length - 1); - verticalMultiplier = - (this.svgHeight - paddingTop - paddingBottom) / (gridValues[gridValues.length - 1] - gridValues[0]); - - path = []; - for (i = 0; i < this.values.length; i++) { - path.push(i ? 'L' : 'M'); - x = i * horizStep; - y = (this.values[i] - gridValues[0]) * verticalMultiplier; - y = this.svgHeight - y - paddingBottom; - path.push(x + ' ' + y + ' '); - } - path = path.join(''); - createSvg('path', {d: path, 'stroke-width': '1px', stroke: 'brown', fill: 'none'}, svg); - }, - - _getElevation: function(latlngs) { - function parseResponse(s) { - var values = [], v; - s = s.split('\n'); - for (var i = 0; i < s.length; i++) { - if (s[i]) { - if (s[i] === 'NULL') { - v = 0; - } else { - v = parseFloat(s[i]); - } - values.push(v); - } - } - return values; - } - - var req = []; - for (var i = 0; i < latlngs.length; i++) { - req.push(latlngs[i].lat.toFixed(6) + ' ' + latlngs[i].lng.toFixed(5)); - } - req = req.join('\n'); - return fetch(this.options.elevationsServer, {method: 'POST', data: req}) - .then( - function(xhr) { - return parseResponse(xhr.responseText); - }, - function() { - alert('Failed to plot elevation profile, server error'); - } - ); - }, - onCloseButtonClick: function() { - this.removeFrom(this._map); - } - } -); diff --git a/src/lib/leaflet.control.elevation-profile/index.js b/src/lib/leaflet.control.elevation-profile/index.js @@ -0,0 +1,792 @@ +import L from 'leaflet'; +import './elevation-profile.css'; +import {fetch} from 'lib/xhr-promise'; +import config from 'config'; + +function createSvg(tagName, attributes, parent) { + var element = document.createElementNS('http://www.w3.org/2000/svg', tagName); + if (attributes) { + var keys = Object.keys(attributes), + key, value; + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + value = attributes[key]; + element.setAttribute(key, value); + } + } + if (parent) { + parent.appendChild(element); + } + return element; +} + +function pointOnSegmentAtDistance(p1, p2, dist) { + //FIXME: we should place markers along projected line to avoid transformation distortions + var q = dist / p1.distanceTo(p2), + x = p1.lng + (p2.lng - p1.lng) * q, + y = p1.lat + (p2.lat - p1.lat) * q; + return L.latLng(y, x); +} + + +function gradientToAngle(g) { + return Math.round(Math.atan(g) * 180 / Math.PI); +} + +function pathRegularSamples(latlngs, step) { + var samples = [], + lastSampleDist = 0, + lastPointDistance = 0, + nextPointDistance = 0, + segmentLength, i; + + samples.push(latlngs[0]); + for (i = 1; i < latlngs.length; i++) { + segmentLength = latlngs[i].distanceTo(latlngs[i - 1]); + nextPointDistance = lastPointDistance + segmentLength; + if (nextPointDistance >= lastSampleDist + step) { + while (lastSampleDist + step <= nextPointDistance) { + lastSampleDist += step; + samples.push( + pointOnSegmentAtDistance(latlngs[i - 1], latlngs[i], lastSampleDist - lastPointDistance) + ); + } + } + lastPointDistance = nextPointDistance; + } + if (samples.length < 2) { + samples.push(latlngs[latlngs.length - 1]); + } + return samples; +} + +function offestFromEvent(e) { + if (e.offsetX === undefined) { + var rect = e.target.getBoundingClientRect(); + return { + offsetX: e.clientX - rect.left, + offestY: e.clientY - rect.top + } + } else { + return { + offsetX: e.offsetX, + offestY: e.offsetY + } + } +} + +function movementFromEvents(e1, e2) { + return { + movementX: e2.clientX - e1.clientX, + movementY: e2.clientY - e1.clientY + } +} + +var DragEvents = L.Class.extend({ + options: { + dragTolerance: 2, + dragButtons: {0: true} + }, + + includes: L.Mixin.Events, + + initialize: function(eventsSource, eventsTarget, options) { + options = L.setOptions(this, options); + if (eventsTarget) { + this.eventsTarget = eventsTarget; + } else { + this.eventsTarget = this; + } + this.dragStartPos = []; + this.prevEvent = []; + this.isDragging = []; + + L.DomEvent.on(eventsSource, 'mousemove', this.onMouseMove, this); + L.DomEvent.on(eventsSource, 'mouseup', this.onMouseUp, this); + L.DomEvent.on(eventsSource, 'mousedown', this.onMouseDown, this); + L.DomEvent.on(eventsSource, 'mouseleave', this.onMouseLeave, this); + }, + + onMouseDown: function(e) { + if (this.options.dragButtons[e.button]) { + e._offset = offestFromEvent(e); + this.dragStartPos[e.button] = e; + this.prevEvent[e.button] = e; + L.DomUtil.disableImageDrag(); + L.DomUtil.disableTextSelection(); + } + }, + + onMouseUp: function(e) { + L.DomUtil.enableImageDrag(); + L.DomUtil.enableTextSelection(); + + if (this.options.dragButtons[e.button]) { + this.dragStartPos[e.button] = null; + if (this.isDragging[e.button]) { + this.isDragging[e.button] = false; + this.fire('dragend', L.extend({dragButton: e.button, origEvent: e}, + offestFromEvent(e), movementFromEvents(this.prevEvent[e.button], e) + ) + ); + } else { + this.fire('click', L.extend({dragButton: e.button, origEvent: e}, + offestFromEvent(e) + ) + ); + } + } + }, + + onMouseMove: function(e) { + var i, button, self = this; + + function exceedsTolerance(button) { + var tolerance = self.options.dragTolerance; + return Math.abs(e.clientX - self.dragStartPos[button].clientX) > tolerance || + Math.abs(e.clientY - self.dragStartPos[button].clientY) > tolerance; + } + + var dragButtons = Object.keys(this.options.dragButtons); + for (i = 0; i < dragButtons.length; i++) { + button = dragButtons[i]; + if (this.isDragging[button]) { + this.eventsTarget.fire('drag', L.extend({dragButton: button, origEvent: e}, + offestFromEvent(e), movementFromEvents(this.prevEvent[button], e) + ) + ); + } else if (this.dragStartPos[button] && exceedsTolerance(button)) { + this.isDragging[button] = true; + this.eventsTarget.fire('dragstart', L.extend( + {dragButton: button, origEvent: this.dragStartPos[button]}, + this.dragStartPos[button]._offset + ) + ); + this.eventsTarget.fire('drag', L.extend({ + dragButton: button, + origEvent: e, + startEvent: self.dragStartPos[button] + }, offestFromEvent(e), movementFromEvents(this.prevEvent[button], e) + ) + ); + } + this.prevEvent[button] = e; + } + }, + + onMouseLeave: function(e) { + var i, button; + var dragButtons = Object.keys(this.options.dragButtons); + for (i = 0; i < dragButtons.length; i++) { + button = dragButtons[i]; + if (this.isDragging[button]) { + this.isDragging[button] = false; + this.fire('dragend', L.extend({dragButton: button, origEvent: e}, + offestFromEvent(e), movementFromEvents(this.prevEvent[button], e) + ) + ); + } + } + this.dragStartPos = {}; + } + } +); + +L.Control.ElevationProfile = L.Class.extend({ + options: { + elevationsServer: config.elevationsServer, + samplingInterval: 50 + }, + + initialize: function(latlngs, options) { + L.setOptions(this, options); + this.path = latlngs; + var samples = this.samples = pathRegularSamples(this.path, this.options.samplingInterval); + var self = this; + this.horizZoom = 1; + this.dragStart = null; + + this._getElevation(samples).then(function(values) { + self.values = values; + self.updateGraph(); + } + ); + this.values = null; + + }, + + addTo: function(map) { + this._map = map; + var container = this._container = L.DomUtil.create('div', 'elevation-profile-container'); + if (!L.Browser.touch) { + L.DomEvent + .disableClickPropagation(container) + .disableScrollPropagation(container); + } else { + L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation); + } + this._map._controlContainer.appendChild(container); + this.setupContainerLayout(); + this.updateGraph(); + this.trackMarker = L.marker([1000, 0], {clickable: false, icon: L.divIcon()}); + this.polyline = L.polyline(this.path, {weight: 30, opacity: 0}).addTo(map); + this.polyline.on('mousemove', this.onLineMouseMove, this); + this.polyline.on('mouseover', this.onLineMouseEnter, this); + this.polyline.on('mouseout', this.onLineMouseLeave, this); + this.polyLineSelection = L.polyline([], {weight: 20, opacity: .5, color: 'yellow', lineCap: 'butt'}); + return this; + }, + + setupContainerLayout: function() { + var horizZoom = this.horizZoom = 1; + var container = this._container; + this.propsContainer = L.DomUtil.create('div', 'elevation-profile-properties', container); + this.leftAxisLables = L.DomUtil.create('div', 'elevation-profile-left-axis', container); + this.closeButton = L.DomUtil.create('div', 'elevation-profile-close', container); + L.DomEvent.on(this.closeButton, 'click', this.onCloseButtonClick, this); + this.drawingContainer = L.DomUtil.create('div', 'elevation-profile-drawingContainer', container); + this.graphCursor = L.DomUtil.create('div', 'elevation-profile-cursor elevation-profile-cursor-hidden', + this.drawingContainer + ); + this.graphCursorLabel = + L.DomUtil.create('div', 'elevation-profile-cursor-label elevation-profile-cursor-hidden', + this.drawingContainer + ); + this.graphSelection = L.DomUtil.create('div', 'elevation-profile-selection elevation-profile-cursor-hidden', + this.drawingContainer + ); + var svgWidth = this.svgWidth = this.drawingContainer.clientWidth * horizZoom, + svgHeight = this.svgHeight = this.drawingContainer.clientHeight; + var svg = this.svg = createSvg('svg', {width: svgWidth, height: svgHeight}, this.drawingContainer); + L.DomEvent.on(svg, 'mousemove', this.onSvgMouseMove, this); + L.DomEvent.on(svg, 'mouseenter', this.onSvgEnter, this); + L.DomEvent.on(svg, 'mouseleave', this.onSvgLeave, this); + L.DomEvent.on(svg, 'mousewheel', this.onSvgMouseWheel, this); + this.svgDragEvents = new DragEvents(this.drawingContainer, null, {dragButtons: {0: true, 2: true}}); + this.svgDragEvents.on('dragstart', this.onSvgDragStart, this); + this.svgDragEvents.on('dragend', this.onSvgDragEnd, this); + this.svgDragEvents.on('drag', this.onSvgDrag, this); + this.svgDragEvents.on('click', this.onSvgClick, this); + L.DomEvent.on(svg, 'dblclick', this.onSvgDblClick, this); + }, + + removeFrom: function(map) { + if (!this._map) { + return; + } + this._map._controlContainer.removeChild(this._container); + map.removeLayer(this.polyline); + map.removeLayer(this.trackMarker); + map.removeLayer(this.polyLineSelection); + this._map = null; + return this; + }, + + onSvgDragStart: function(e) { + + if (e.dragButton === 0) { + // FIXME: restore hiding when we make display of selection on map + // this.cursorHide(); + this.polyLineSelection.addTo(this._map).bringToBack(); + this.dragStart = e.offsetX; + } + }, + + xToIndex: function(x) { + return x / (this.svgWidth - 1) * (this.values.length - 1); + }, + + updateGraphSelection: function(e) { + if (this.dragStart === null) { + return; + } + var selStart, selEnd; + if (e) { + var x = e.offsetX; + selStart = Math.min(x, this.dragStart); + selEnd = Math.max(x, this.dragStart); + this.selStartInd = Math.round(this.xToIndex(selStart)); + this.selEndInd = Math.round(this.xToIndex(selEnd)); + + if (this.selStartInd < 0) { + this.selStartInd = 0; + } + if (this.selEndInd > this.values.length - 1) { + this.selEndInd = this.values.length - 1; + } + + } else { + selStart = this.selStartInd * (this.svgWidth - 1) / (this.values.length - 1); + selEnd = this.selEndInd * (this.svgWidth - 1) / (this.values.length - 1); + } + this.graphSelection.style.left = selStart + 'px'; + this.graphSelection.style.width = (selEnd - selStart) + 'px'; + L.DomUtil.removeClass(this.graphSelection, 'elevation-profile-cursor-hidden'); + }, + + onSvgDragEnd: function(e) { + if (e.dragButton === 0) { + this.cursorShow(); + this.updateGraphSelection(e); + var stats = this.calcProfileStats(this.values.slice(this.selStartInd, this.selEndInd + 1), true); + this.updatePropsDisplay(stats); + L.DomUtil.addClass(this.propsContainer, 'elevation-profile-properties-selected'); + } + if (e.dragButton === 2) { + this.drawingContainer.scrollLeft -= e.movementX; + } + }, + + onSvgDrag: function(e) { + if (e.dragButton === 0) { + this.updateGraphSelection(e); + this.polyLineSelection.setLatLngs(this.samples.slice(this.selStartInd, this.selEndInd + 1)); + } + if (e.dragButton === 2) { + this.drawingContainer.scrollLeft -= e.movementX; + } + }, + + onSvgClick: function(e) { + if (e.dragButton === 0) { + this.dragStart = null; + L.DomUtil.addClass(this.graphSelection, 'elevation-profile-cursor-hidden'); + L.DomUtil.removeClass(this.propsContainer, 'elevation-profile-properties-selected'); + this._map.removeLayer(this.polyLineSelection); + if (this.stats) { + this.updatePropsDisplay(this.stats); + } + } + if (e.dragButton === 2) { + this.setMapPositionAtIndex(Math.round(this.xToIndex(e.offsetX))); + } + }, + + onSvgDblClick: function(e) { + this.setMapPositionAtIndex(Math.round(this.xToIndex(e.offsetX))); + }, + + setMapPositionAtIndex: function(ind) { + var latlng = this.samples[ind]; + if (latlng) { + this._map.panTo(latlng); + } + }, + + onSvgMouseWheel: function(e) { + var oldHorizZoom = this.horizZoom; + this.horizZoom += L.DomEvent.getWheelDelta(e); + if (this.horizZoom < 1) { + this.horizZoom = 1; + } + if (this.horizZoom > 10) { + this.horizZoom = 10; + } + + var x = offestFromEvent(e).offsetX; + var ind = this.xToIndex(x); + + var newScrollLeft = this.drawingContainer.scrollLeft + + offestFromEvent(e).offsetX * (this.horizZoom / oldHorizZoom - 1); + if (newScrollLeft < 0) { + newScrollLeft = 0; + } + + this.svgWidth = this.drawingContainer.clientWidth * this.horizZoom; + this.svg.setAttribute('width', this.svgWidth + 'px'); + this.setupGraph(); + if (newScrollLeft > this.svgWidth - this.drawingContainer.clientWidth) { + newScrollLeft = this.svgWidth - this.drawingContainer.clientWidth; + } + this.drawingContainer.scrollLeft = newScrollLeft; + + this.cursorHide(); + this.setCursorPosition(ind); + this.cursorShow(); + this.updateGraphSelection(); + }, + + + updateGraph: function() { + if (!this._map || !this.values) { + return; + } + + this.stats = this.calcProfileStats(this.values); + this.updatePropsDisplay(this.stats); + this.setupGraph(); + }, + + updatePropsDisplay: function(stats) { + if (!this._map) { + return; + } + this.propsContainer.innerHTML = ''; + var ascentAngleStr = isNaN(stats.angleAvgAscent) ? '-' : L.Util.template('{avg} / {max}&deg;', + {avg: stats.angleAvgAscent, max: stats.angleMaxAscent} + ); + var descentAngleStr = isNaN(stats.angleAvgDescent) ? '-' : L.Util.template('{avg} / {max}&deg;', + {avg: stats.angleAvgDescent, max: stats.angleMaxDescent} + ); + + this.propsContainer.innerHTML = + '<table>' + + '<tr><td>Max elevation:</td><td>' + Math.round(stats.max) + '</td></tr>' + + '<tr><td>Min elevation:</td><td>' + Math.round(stats.min) + '</td></tr>' + + '<tr class="start-group"><td>Start elevation:</td><td>' + Math.round(stats.start) + '</td></tr>' + + '<tr><td>Finish elevation:</td><td>' + Math.round(stats.end) + '</td></tr>' + + '<tr><td>Start to finish elevation change:</td><td>' + Math.round(stats.finalAscent) + '</td></tr>' + + '<tr class="start-group"><td>Avg / Max ascent inclination:</td><td>' + ascentAngleStr + '</td></tr>' + + '<tr><td>Avg / Max descent inclination:</td><td>' + descentAngleStr + '</td></tr>' + + '<tr class="start-group"><td>Total ascent:</td><td>' + Math.round(stats.ascent) + '</td></tr>' + + '<tr><td>Total descent:</td><td>' + Math.round(stats.descent) + '</td></tr>' + + '<tr class="start-group"><td>Distance:</td><td>' + (stats.distance / 1000).toFixed(1) + ' km</td></tr>' + + '</table>' + }, + + calcGridValues: function(minValue, maxValue) { + var ticksNs = [3, 4, 5], + tickSteps = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000], + ticks = [], + i, j, k, ticksN, tickStep, tick1, tick2; + for (i = 0; i < tickSteps.length; i++) { + tickStep = tickSteps[i]; + for (j = 0; j < ticksNs.length; j++) { + ticksN = ticksNs[j]; + tick1 = Math.floor(minValue / tickStep); + tick2 = Math.ceil(maxValue / tickStep); + if ((tick2 - tick1) < ticksN) { + for (k = tick1; k < tick1 + ticksN; k++) { + ticks.push(k * tickStep); + } + return ticks; + } + } + } + }, + + filterElevations: function(values, tolerance) { + var filtered = values.slice(0); + if (filtered.length < 3) { + return filtered; + } + var scanStart, scanEnd, job, linearValue, linearDelta, maxError, maxErrorInd, i, error; + var queue = [[0, filtered.length - 1]]; + while (queue.length) { + job = queue.pop(); + scanStart = job[0]; + scanEnd = job[1]; + linearValue = filtered[scanStart]; + linearDelta = (filtered[scanEnd] - filtered[scanStart]) / (scanEnd - scanStart); + maxError = null; + maxErrorInd = null; + for (i = scanStart + 1; i < scanEnd; i++) { + linearValue += linearDelta; + error = Math.abs(filtered[i] - linearValue); + if (error === null || error > maxError) { + maxError = error; + maxErrorInd = i; + } + } + if (maxError > tolerance) { + if (scanEnd > scanStart + 2) { + queue.push([scanStart, maxErrorInd]); + queue.push([maxErrorInd, scanEnd]); + } + } else { + filtered.splice(scanStart + 1, scanEnd - scanStart - 1); + } + } + return filtered; + }, + + calcProfileStats: function(values, partial) { + var stats = {}, + gradient, i; + stats.min = Math.min.apply(null, values); + stats.max = Math.max.apply(null, values); + stats.finalAscent = values[values.length - 1] - values[0]; + var ascents = [], + descents = []; + for (i = 1; i < values.length; i++) { + gradient = (values[i] - values[i - 1]); + if (gradient > 0) { + ascents.push(gradient); + } else if (gradient < 0) { + descents.push(-gradient); + } + } + function sum(a, b) { + return a + b; + } + + stats.gradientAvgAscent = ascents.reduce(sum, 0) / ascents.length / this.options.samplingInterval; + stats.gradientMinAscent = Math.min.apply(null, ascents) / this.options.samplingInterval; + stats.gradientMaxAscent = Math.max.apply(null, ascents) / this.options.samplingInterval; + stats.gradientAvgDescent = descents.reduce(sum, 0) / descents.length / this.options.samplingInterval; + stats.gradientMinDescent = Math.min.apply(null, descents) / this.options.samplingInterval; + stats.gradientMaxDescent = Math.max.apply(null, descents) / this.options.samplingInterval; + + stats.angleAvgAscent = gradientToAngle(stats.gradientAvgAscent); + stats.angleMinAscent = gradientToAngle(stats.gradientMinAscent); + stats.angleMaxAscent = gradientToAngle(stats.gradientMaxAscent); + stats.angleAvgDescent = gradientToAngle(stats.gradientAvgDescent); + stats.angleMinDescent = gradientToAngle(stats.gradientMinDescent); + stats.angleMaxDescent = gradientToAngle(stats.gradientMaxDescent); + + stats.start = values[0]; + stats.end = values[values.length - 1]; + stats.distance = (values.length - 1) * this.options.samplingInterval; + + var filterTolerance = 5; + var filtered = this.filterElevations(values, filterTolerance); + var ascent = 0, + descent = 0, + delta; + for (i = 1; i < filtered.length; i++) { + delta = filtered[i] - filtered[i - 1]; + if (delta < 0) { + descent += -delta; + } else { + ascent += delta; + } + } + stats.ascent = ascent; + stats.descent = descent; + + return stats; + + }, + + setCursorPosition: function(ind) { + var distance = this.options.samplingInterval * ind; + distance = (distance / 1000).toFixed(2); + var gradient = (this.values[Math.ceil(ind)] - this.values[Math.floor(ind)]) / this.options.samplingInterval; + var angle = Math.round(Math.atan(gradient) * 180 / Math.PI); + gradient = Math.round(gradient * 100); + + var x = Math.round(ind / (this.values.length - 1) * (this.svgWidth - 1)); + var indInt = Math.round(ind); + var elevation = this.values[indInt]; + this.graphCursorLabel.innerHTML = L.Util.template('{ele} m<br>{dist} km<br>{angle}&deg;', + {ele: Math.round(elevation), dist: distance, grad: gradient, angle: angle} + ); + + this.graphCursor.style.left = x + 'px'; + this.graphCursorLabel.style.left = x + 'px'; + if (this.drawingContainer.getBoundingClientRect().left - this.drawingContainer.scrollLeft + x + + this.graphCursorLabel.offsetWidth >= this._container.getBoundingClientRect().right) { + L.DomUtil.addClass(this.graphCursorLabel, 'elevation-profile-cursor-label-left'); + } else { + L.DomUtil.removeClass(this.graphCursorLabel, 'elevation-profile-cursor-label-left'); + } + + var markerPos; + if (ind <= 0) { + markerPos = this.samples[0]; + } else if (ind >= this.samples.length - 1) { + markerPos = this.samples[this.samples.length - 1]; + } else { + var p1 = this.samples[Math.floor(ind)], + p2 = this.samples[Math.ceil(ind)], + indFrac = ind - Math.floor(ind); + markerPos = [p1.lat + (p2.lat - p1.lat) * indFrac, p1.lng + (p2.lng - p1.lng) * indFrac]; + } + this.trackMarker.setLatLng(markerPos); + var label = L.Util.template('{ele} m<br>{dist} km<br>{angle}&deg;', + {ele: Math.round(elevation), dist: distance, grad: gradient, angle: angle} + ); + var icon = L.divIcon({ + className: 'elevation-profile-marker', + html: '<div class="elevation-profile-marker-icon"></div><div class="elevation-profile-marker-label">' + + label + '</div>' + } + ); + this.trackMarker.setIcon(icon); + }, + + onSvgMouseMove: function(e) { + if (!this.values) { + return; + } + var x = offestFromEvent(e).offsetX; + var ind = (x / (this.svgWidth - 1) * (this.values.length - 1)); + this.setCursorPosition(ind); + }, + + cursorShow: function() { + L.DomUtil.removeClass(this.graphCursor, 'elevation-profile-cursor-hidden'); + L.DomUtil.removeClass(this.graphCursorLabel, 'elevation-profile-cursor-hidden'); + this._map.addLayer(this.trackMarker); + }, + + cursorHide: function() { + L.DomUtil.addClass(this.graphCursor, 'elevation-profile-cursor-hidden'); + L.DomUtil.addClass(this.graphCursorLabel, 'elevation-profile-cursor-hidden'); + this._map.removeLayer(this.trackMarker); + }, + + onSvgEnter: function() { + this.cursorShow(); + }, + + onSvgLeave: function() { + this.cursorHide(); + }, + + onLineMouseEnter: function() { + this.cursorShow(); + }, + + onLineMouseLeave: function() { + this.cursorHide(); + }, + + onLineMouseMove: function(e) { + function sqrDist(latlng1, latlng2) { + var dx = (latlng1.lng - latlng2.lng); + var dy = (latlng1.lat - latlng2.lat); + return dx * dx + dy * dy; + } + + var nearestInd = null, ind, + minDist = null, + mouseLatlng = e.latlng, + i, sampleLatlng, dist, di; + for (i = 0; i < this.samples.length; i++) { + sampleLatlng = this.samples[i]; + dist = sqrDist(sampleLatlng, mouseLatlng); + if (nearestInd === null || dist < minDist) { + nearestInd = i; + minDist = dist; + } + } + + if (nearestInd !== null) { + ind = nearestInd; + if (nearestInd > 0) { + var prevDist = sqrDist(mouseLatlng, this.samples[nearestInd - 1]), + prevSampleDist = sqrDist(this.samples[nearestInd], this.samples[nearestInd - 1]); + } + if (nearestInd < this.samples.length - 1) { + var nextDist = sqrDist(mouseLatlng, this.samples[nearestInd + 1]), + nextSampleDist = sqrDist(this.samples[nearestInd], this.samples[nearestInd + 1]); + } + + if (nearestInd === 0) { + if (nextDist < minDist + nextSampleDist) { + di = (minDist - nextDist) / 2 / nextSampleDist + 1 / 2; + } else { + di = .001; + } + } else if (nearestInd === this.samples.length - 1) { + if (prevDist < minDist + prevSampleDist) { + di = -((minDist - prevDist) / 2 / prevSampleDist + 1 / 2); + } else { + di = -0.001 + } + } else { + if (prevDist < nextDist) { + di = -((minDist - prevDist) / 2 / prevSampleDist + 1 / 2); + } else { + di = (minDist - nextDist) / 2 / nextSampleDist + 1 / 2; + } + } + if (di < -1) { + di = -1; + } + if (di > 1) { + di = 1; + } + this.setCursorPosition(ind + di); + } + + }, + + + setupGraph: function() { + if (!this._map) { + return; + } + + while (this.svg.hasChildNodes()) { + this.svg.removeChild(this.svg.lastChild); + } + while (this.leftAxisLables.hasChildNodes()) { + this.leftAxisLables.removeChild(this.leftAxisLables.lastChild); + } + + var maxValue = Math.max.apply(null, this.values), + minValue = Math.min.apply(null, this.values), + svg = this.svg, + path, i, horizStep, verticalMultiplier, x, y, gridValues, label; + + + var paddingBottom = 8 + 16, + paddingTop = 8; + + gridValues = this.calcGridValues(minValue, maxValue); + var gridStep = (this.svgHeight - paddingBottom - paddingTop) / (gridValues.length - 1); + for (i = 0; i < gridValues.length; i++) { + y = Math.round(i * gridStep - 0.5) + 0.5 + paddingTop; + path = L.Util.template('M{x1} {y} L{x2} {y}', {x1: 0, x2: this.svgWidth * this.horizZoom, y: y}); + createSvg('path', {d: path, 'stroke-width': '1px', stroke: 'green', fill: 'none'}, svg); + + label = L.DomUtil.create('div', 'elevation-profile-grid-label', this.leftAxisLables); + label.innerHTML = gridValues[gridValues.length - i - 1]; + label.style.top = (gridStep * i + paddingTop) + 'px'; + } + + horizStep = this.svgWidth / (this.values.length - 1); + verticalMultiplier = + (this.svgHeight - paddingTop - paddingBottom) / (gridValues[gridValues.length - 1] - gridValues[0]); + + path = []; + for (i = 0; i < this.values.length; i++) { + path.push(i ? 'L' : 'M'); + x = i * horizStep; + y = (this.values[i] - gridValues[0]) * verticalMultiplier; + y = this.svgHeight - y - paddingBottom; + path.push(x + ' ' + y + ' '); + } + path = path.join(''); + createSvg('path', {d: path, 'stroke-width': '1px', stroke: 'brown', fill: 'none'}, svg); + }, + + _getElevation: function(latlngs) { + function parseResponse(s) { + var values = [], v; + s = s.split('\n'); + for (var i = 0; i < s.length; i++) { + if (s[i]) { + if (s[i] === 'NULL') { + v = 0; + } else { + v = parseFloat(s[i]); + } + values.push(v); + } + } + return values; + } + + var req = []; + for (var i = 0; i < latlngs.length; i++) { + req.push(latlngs[i].lat.toFixed(6) + ' ' + latlngs[i].lng.toFixed(5)); + } + req = req.join('\n'); + return fetch(this.options.elevationsServer, {method: 'POST', data: req}) + .then( + function(xhr) { + return parseResponse(xhr.responseText); + }, + function() { + alert('Failed to plot elevation profile, server error'); + } + ); + }, + onCloseButtonClick: function() { + this.removeFrom(this._map); + } + } +); diff --git a/src/lib/leaflet.control.layers.adaptive-height/adaptive-height.js b/src/lib/leaflet.control.layers.adaptive-height/index.js diff --git a/src/lib/leaflet.control.layers.configure/index.js b/src/lib/leaflet.control.layers.configure/index.js @@ -0,0 +1,210 @@ +import L from 'leaflet'; +import './style.css'; +import enableTopRow from 'lib/leaflet.control.layers.top-row'; +import ko from 'knockout'; + + +function enableConfig(control, layers) { + const originalOnAdd = control.onAdd; + const originalUnserializeState = control.unserializeState; + if (control._configEnabled) { + return; + } + enableTopRow(control); + + L.Util.extend(control, { + configEnabled: true, + _allLayersGroups: layers, + _allLayers: [].concat(...layers.map(group => group.layers)), + + onAdd: function(map) { + const container = originalOnAdd.call(this, map); + this.__injectConfigButton(); + return container; + }, + + __injectConfigButton: function() { + const configButton = L.DomUtil.create('div', 'button-config'); + configButton.innerHTML = 'More layers'; + this._topRow.appendChild(configButton); + L.DomEvent.on(configButton, 'click', this._onConfigButtonClick, this); + }, + + _initializeLayersState: function() { + let storedLayersEnabled = {}; + if (window.localStorage) { + const serialized = window.localStorage.getItem('layersEnabled'); + if (serialized) { + try { + storedLayersEnabled = JSON.parse(serialized); + } catch (e) { + } + } + } + for (let layer of this._allLayers) { + // TODO: check if state is stored in localStorage, else + let enabled = storedLayersEnabled[layer.layer.options.code]; + // if storage is empty enable only default layers + // if new default layer appears it will be enabled + if (typeof enabled === 'undefined') { + enabled = layer.isDefault; + } + layer.enabled = enabled; + layer.checked = ko.observable(enabled); + layer.description = layer.description || ''; + } + this.storeEnabledLayers(); + this.updateEnabledLayers(); + }, + + _onConfigButtonClick: function() { + this.showLayersSelectWindow() + }, + + _initLayersSelectWindow: function() { + if (this._configWindow) { + return; + } + + const container = this._configWindow = + L.DomUtil.create('div', 'leaflet-layers-select-window-wrapper'); + L.DomEvent.disableClickPropagation(container); + if (!L.Browser.touch) { + L.DomEvent.disableScrollPropagation(container); + } + container.innerHTML = ` +<div class="leaflet-layers-select-window"> + <form data-bind="foreach: _allLayersGroups"> + <div class="section-header" data-bind="html: group"></div> + <!-- ko foreach: layers --> + <label> + <input type="checkbox" data-bind="checked: checked"/> + <span data-bind="text: title"> + </span><!-- ko if: description -->: + <span data-bind="html: description || ''"></span> + <!-- /ko --> + </label> + <!-- /ko --> + </form> + <div class="buttons-row"> + <div href="#" class="button" data-bind="click: onSelectWindowOkClicked">Ok</div> + <div href="#" class="button" data-bind="click: onSelectWindowCancelClicked">Cancel</div> + <div href="#" class="button" data-bind="click: onSelectWindowResetClicked">Reset</div> + </div> +</div> + `; + ko.applyBindings(this, container); + }, + + showLayersSelectWindow: function() { + if (this._configWindowVisible) { + return; + } + this._allLayers.forEach(layer => layer.checked(layer.enabled)); + this._initLayersSelectWindow(); + this._map._controlContainer.appendChild(this._configWindow); + this._configWindowVisible = true; + }, + + hideSelectWindow: function() { + if (!this._configWindowVisible) { + return; + } + this._map._controlContainer.removeChild(this._configWindow); + this._configWindowVisible = false; + }, + + onSelectWindowCancelClicked: function() { + this.hideSelectWindow(); + }, + + onSelectWindowResetClicked: function() { + if (!this._configWindow) { + return; + } + this._allLayers.forEach(layer => layer.checked(layer.isDefault)) + }, + + onSelectWindowOkClicked: function() { + this._allLayers.forEach(layer => layer.enabled = layer.checked()) + this.updateEnabledLayers(); + this.hideSelectWindow(); + this.storeEnabledLayers(); + }, + + updateEnabledLayers: function() { + const layersOnMap = []; + while (this._layers.length) { + let layer = this._layers[0]; + if (this._map.hasLayer(layer.layer)) { + layersOnMap.push(layer.layer); + } + this.removeLayer(layer.layer); + this._map.removeLayer(layer.layer); + } + + let hasBaselayerOnMap = false; + const enabledLayers = []; + for (let layer of this._allLayers) { + if (layer.enabled) { + enabledLayers.push(layer); + } + } + enabledLayers.sort((l1, l2) => l1.order - l2.order); + enabledLayers.forEach((l) => { + l.isOverlay ? this.addOverlay(l.layer, l.title) : this.addBaseLayer(l.layer, l.title); + if (layersOnMap.includes(l.layer)) { + this._map.addLayer(l.layer); + hasBaselayerOnMap = hasBaselayerOnMap || !l.isOverlay; + } + } + ); + // если нет активного базового слоя, включить первый, если он есть + if (!hasBaselayerOnMap) { + for (let layer of enabledLayers) { + if (!layer.isOverlay) { + this._map.addLayer(layer.layer); + break; + } + } + } + + }, + + storeEnabledLayers: function() { + if (!window.localStorage) { + return; + } + const layersState = {}; + for (let layer of this._allLayers) { + if (layer.isDefault || layer.enabled) { + layersState[layer.layer.options.code] = layer.enabled; + } + } + const serialized = JSON.stringify(layersState); + localStorage.setItem('layersEnabled', serialized); + }, + + unserializeState: function(values) { + if (values) { + for (let layer of this._allLayers) { + if (layer.layer.options && values.includes(layer.layer.options.code)) { + layer.enabled = true; + } + } + this.updateEnabledLayers(); + } + this.storeEnabledLayers(); + return originalUnserializeState.call(this, values); + } + + } + ); + if (control._map) { + control.__injectConfigButton() + } + control._initializeLayersState(); +} + + +export default enableConfig; +\ No newline at end of file diff --git a/src/lib/leaflet.control.layers.configure/layers-configure.js b/src/lib/leaflet.control.layers.configure/layers-configure.js @@ -1,210 +0,0 @@ -import L from 'leaflet'; -import './style.css'; -import enableTopRow from 'lib/leaflet.control.layers.top-row/top-row'; -import ko from 'knockout'; - - -function enableConfig(control, layers) { - const originalOnAdd = control.onAdd; - const originalUnserializeState = control.unserializeState; - if (control._configEnabled) { - return; - } - enableTopRow(control); - - L.Util.extend(control, { - configEnabled: true, - _allLayersGroups: layers, - _allLayers: [].concat(...layers.map(group => group.layers)), - - onAdd: function(map) { - const container = originalOnAdd.call(this, map); - this.__injectConfigButton(); - return container; - }, - - __injectConfigButton: function() { - const configButton = L.DomUtil.create('div', 'button-config'); - configButton.innerHTML = 'More layers'; - this._topRow.appendChild(configButton); - L.DomEvent.on(configButton, 'click', this._onConfigButtonClick, this); - }, - - _initializeLayersState: function() { - let storedLayersEnabled = {}; - if (window.localStorage) { - const serialized = window.localStorage.getItem('layersEnabled'); - if (serialized) { - try { - storedLayersEnabled = JSON.parse(serialized); - } catch (e) { - } - } - } - for (let layer of this._allLayers) { - // TODO: check if state is stored in localStorage, else - let enabled = storedLayersEnabled[layer.layer.options.code]; - // if storage is empty enable only default layers - // if new default layer appears it will be enabled - if (typeof enabled === 'undefined') { - enabled = layer.isDefault; - } - layer.enabled = enabled; - layer.checked = ko.observable(enabled); - layer.description = layer.description || ''; - } - this.storeEnabledLayers(); - this.updateEnabledLayers(); - }, - - _onConfigButtonClick: function() { - this.showLayersSelectWindow() - }, - - _initLayersSelectWindow: function() { - if (this._configWindow) { - return; - } - - const container = this._configWindow = - L.DomUtil.create('div', 'leaflet-layers-select-window-wrapper'); - L.DomEvent.disableClickPropagation(container); - if (!L.Browser.touch) { - L.DomEvent.disableScrollPropagation(container); - } - container.innerHTML = ` -<div class="leaflet-layers-select-window"> - <form data-bind="foreach: _allLayersGroups"> - <div class="section-header" data-bind="html: group"></div> - <!-- ko foreach: layers --> - <label> - <input type="checkbox" data-bind="checked: checked"/> - <span data-bind="text: title"> - </span><!-- ko if: description -->: - <span data-bind="html: description || ''"></span> - <!-- /ko --> - </label> - <!-- /ko --> - </form> - <div class="buttons-row"> - <div href="#" class="button" data-bind="click: onSelectWindowOkClicked">Ok</div> - <div href="#" class="button" data-bind="click: onSelectWindowCancelClicked">Cancel</div> - <div href="#" class="button" data-bind="click: onSelectWindowResetClicked">Reset</div> - </div> -</div> - `; - ko.applyBindings(this, container); - }, - - showLayersSelectWindow: function() { - if (this._configWindowVisible) { - return; - } - this._allLayers.forEach(layer => layer.checked(layer.enabled)); - this._initLayersSelectWindow(); - this._map._controlContainer.appendChild(this._configWindow); - this._configWindowVisible = true; - }, - - hideSelectWindow: function() { - if (!this._configWindowVisible) { - return; - } - this._map._controlContainer.removeChild(this._configWindow); - this._configWindowVisible = false; - }, - - onSelectWindowCancelClicked: function() { - this.hideSelectWindow(); - }, - - onSelectWindowResetClicked: function() { - if (!this._configWindow) { - return; - } - this._allLayers.forEach(layer => layer.checked(layer.isDefault)) - }, - - onSelectWindowOkClicked: function() { - this._allLayers.forEach(layer => layer.enabled = layer.checked()) - this.updateEnabledLayers(); - this.hideSelectWindow(); - this.storeEnabledLayers(); - }, - - updateEnabledLayers: function() { - const layersOnMap = []; - while (this._layers.length) { - let layer = this._layers[0]; - if (this._map.hasLayer(layer.layer)) { - layersOnMap.push(layer.layer); - } - this.removeLayer(layer.layer); - this._map.removeLayer(layer.layer); - } - - let hasBaselayerOnMap = false; - const enabledLayers = []; - for (let layer of this._allLayers) { - if (layer.enabled) { - enabledLayers.push(layer); - } - } - enabledLayers.sort((l1, l2) => l1.order - l2.order); - enabledLayers.forEach((l) => { - l.isOverlay ? this.addOverlay(l.layer, l.title) : this.addBaseLayer(l.layer, l.title); - if (layersOnMap.includes(l.layer)) { - this._map.addLayer(l.layer); - hasBaselayerOnMap = hasBaselayerOnMap || !l.isOverlay; - } - } - ); - // если нет активного базового слоя, включить первый, если он есть - if (!hasBaselayerOnMap) { - for (let layer of enabledLayers) { - if (!layer.isOverlay) { - this._map.addLayer(layer.layer); - break; - } - } - } - - }, - - storeEnabledLayers: function() { - if (!window.localStorage) { - return; - } - const layersState = {}; - for (let layer of this._allLayers) { - if (layer.isDefault || layer.enabled) { - layersState[layer.layer.options.code] = layer.enabled; - } - } - const serialized = JSON.stringify(layersState); - localStorage.setItem('layersEnabled', serialized); - }, - - unserializeState: function(values) { - if (values) { - for (let layer of this._allLayers) { - if (layer.layer.options && values.includes(layer.layer.options.code)) { - layer.enabled = true; - } - } - this.updateEnabledLayers(); - } - this.storeEnabledLayers(); - return originalUnserializeState.call(this, values); - } - - } - ); - if (control._map) { - control.__injectConfigButton() - } - control._initializeLayersState(); -} - - -export default enableConfig; -\ No newline at end of file diff --git a/src/lib/leaflet.control.layers.hotkeys/control.Layers-hotkeys.js b/src/lib/leaflet.control.layers.hotkeys/index.js diff --git a/src/lib/leaflet.control.layers.minimize/index.js b/src/lib/leaflet.control.layers.minimize/index.js @@ -0,0 +1,53 @@ +import L from 'leaflet'; +import './style.css'; +import 'lib/controls-styles/controls-styles.css'; +import enableTopRow from 'lib/leaflet.control.layers.top-row'; + + +function enableMinimize(control) { + const originalOnAdd = control.onAdd; + if (control._minimizeEnabled) { + return; + } + enableTopRow(control); + + L.Util.extend(control, { + _minimizeEnabled: true, + + onAdd: function(map) { + const container = originalOnAdd.call(this, map); + setTimeout(() => this.__injectMinimizeButtons(), 0); + return container; + }, + + __injectMinimizeButtons: function() { + const container = this._container; + const contentsWrapper = L.DomUtil.create('div', 'leaflet-control-content'); + while (container.childNodes.length) { + contentsWrapper.appendChild(container.childNodes[0]); + } + container.appendChild(contentsWrapper); + const minimizeButton = L.DomUtil.create('div', 'button-minimize'); + this._topRow.appendChild(minimizeButton); + const expandButton = L.DomUtil.create('div', 'leaflet-control-button-toggle', container); + L.DomEvent.on(expandButton, 'click', this.setExpanded, this); + L.DomEvent.on(minimizeButton, 'click', this.setMinimized, this); + }, + + setExpanded: function() { + L.DomUtil.removeClass(this._container, 'minimized'); + }, + + setMinimized: function() { + L.DomUtil.addClass(this._container, 'minimized'); + } + } + ); + if (control._map) { + control.__injectMinimizeButtons() + } + + +} + +export default enableMinimize; +\ No newline at end of file diff --git a/src/lib/leaflet.control.layers.minimize/minimize.js b/src/lib/leaflet.control.layers.minimize/minimize.js @@ -1,53 +0,0 @@ -import L from 'leaflet'; -import './style.css'; -import 'lib/controls-styles/controls-styles.css'; -import enableTopRow from 'lib/leaflet.control.layers.top-row/top-row'; - - -function enableMinimize(control) { - const originalOnAdd = control.onAdd; - if (control._minimizeEnabled) { - return; - } - enableTopRow(control); - - L.Util.extend(control, { - _minimizeEnabled: true, - - onAdd: function(map) { - const container = originalOnAdd.call(this, map); - setTimeout(() => this.__injectMinimizeButtons(), 0); - return container; - }, - - __injectMinimizeButtons: function() { - const container = this._container; - const contentsWrapper = L.DomUtil.create('div', 'leaflet-control-content'); - while (container.childNodes.length) { - contentsWrapper.appendChild(container.childNodes[0]); - } - container.appendChild(contentsWrapper); - const minimizeButton = L.DomUtil.create('div', 'button-minimize'); - this._topRow.appendChild(minimizeButton); - const expandButton = L.DomUtil.create('div', 'leaflet-control-button-toggle', container); - L.DomEvent.on(expandButton, 'click', this.setExpanded, this); - L.DomEvent.on(minimizeButton, 'click', this.setMinimized, this); - }, - - setExpanded: function() { - L.DomUtil.removeClass(this._container, 'minimized'); - }, - - setMinimized: function() { - L.DomUtil.addClass(this._container, 'minimized'); - } - } - ); - if (control._map) { - control.__injectMinimizeButtons() - } - - -} - -export default enableMinimize; -\ No newline at end of file diff --git a/src/lib/leaflet.control.layers.top-row/top-row.js b/src/lib/leaflet.control.layers.top-row/index.js diff --git a/src/lib/leaflet.control.panoramas/index.js b/src/lib/leaflet.control.panoramas/index.js @@ -0,0 +1,246 @@ +import L from 'leaflet'; +import './style.css'; +import 'lib/controls-styles/controls-styles.css'; +import getGoogle from 'lib/googleMapsApi'; + + +L.Control.Panoramas = L.Control.extend({ + includes: L.Mixin.Events, + + options: { + position: 'topleft' + }, + + initialize: function(panoramaContainer, options) { + L.Control.prototype.initialize.call(this, options); + this._panoramaContainer = panoramaContainer; + + const icon = L.divIcon({ + className: 'leaflet-panorama-marker-wraper', + html: '<div class="leaflet-panorama-marker"></div>' + } + ); + this.marker = L.marker([0, 0], {icon: icon}); + }, + + onAdd: function(map) { + this._map = map; + const container = L.DomUtil.create('a', 'leaflet-control leaflet-control-button leaflet-contol-panoramas'); + container.title = 'Show panoramas'; + L.DomEvent.disableClickPropagation(container); + if (!L.Browser.touch) { + L.DomEvent.disableScrollPropagation(container); + } + L.DomEvent.on(container, 'click', this.onButtonClick, this); + + map.createPane('rasterOverlay').style.zIndex = 300; + this._coverageLayer = L.tileLayer( + 'https://maps.googleapis.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m8!1e2!2ssvv!4m2!1scb_client!2sapiv3!4m2!1scc!2s*211m3*211e3*212b1*213e2*211m3*211e2*212b1*213e2!3m5!3sUS!12m1!1e40!12m1!1e18!4e0', + {pane: 'rasterOverlay'} + ); + + return container; + }, + + onRemove: function() { + this._map = null; + this.hideCoverage(); + this.hidePanorama(); + }, + + showPanorama: function() { + if (this.panoramaVisible) { + return; + } + L.DomUtil.addClass(this._panoramaContainer, 'enabled'); + this.getGoogleApi().then((api) => api.panorama.setVisible(true)); + window.dispatchEvent(new Event('resize')); + this.marker.addTo(this._map); + this.panoramaVisible = true; + this.notifyChanged(); + }, + + hidePanorama: function() { + if (!this.panoramaVisible) { + return; + } + this.getGoogleApi().then((api) => api.panorama.setVisible(false)); + L.DomUtil.removeClass(this._panoramaContainer, 'enabled'); + window.dispatchEvent(new Event('resize')); + this._map.removeLayer(this.marker); + this.panoramaVisible = false; + this.notifyChanged(); + }, + + showCoverage: function() { + if (this.coverageVisible) { + return; + } + L.DomUtil.addClass(this.getContainer(), 'enabled'); + this._coverageLayer.addTo(this._map); + this._map.on('click', this.onMapClick, this); + this.coverageVisible = true; + this.notifyChanged(); + }, + + onMapClick: function(e) { + this.showPanoramaAtPos(e.latlng); + }, + + showPanoramaAtPos: function(latlng, pov) { + this.showPanorama(); + const searchRadiusPx = 24; + const p = this._map.project(latlng).add([searchRadiusPx, 0]); + const searchRadiusMeters = latlng.distanceTo(this._map.unproject(p)); + + function setPanoramaPosition(api, panoData, status) { + if (status === api.google.maps.StreetViewStatus.OK) { + api.panorama.setPosition(panoData.location.latLng); + } + if (pov) { + api.panorama.setPov(pov); + } + } + + this.getGoogleApi().then((api) => { + api.service.getPanorama({ + location: latlng, + radius: searchRadiusMeters, + preference: api.google.maps.StreetViewPreference.NEAREST + }, setPanoramaPosition.bind(null, api) + ); + } + ); + }, + + hideCoverage: function() { + if (!this.coverageVisible) { + return; + } + L.DomUtil.removeClass(this.getContainer(), 'enabled'); + this._coverageLayer.removeFrom(this._map); + this._map.off('click', this.onMapClick, this); + this.coverageVisible = false; + this.notifyChanged(); + }, + + onButtonClick: function() { + if (!this.coverageVisible) { + this.showCoverage(); + } else { + this.hideCoverage(); + this.hidePanorama(); + } + }, + + onPanoramaChangePosition: function() { + this.getGoogleApi().then((api) => { + let pos = api.panorama.getPosition(); + if (pos) { + pos = L.latLng([pos.lat(), pos.lng()]); + this.marker.setLatLng(pos); + if (!this._map.getBounds().contains(pos)) { + this._map.panTo(pos); + } + this.panoramaPosition = pos; + } else { + this.panoramaPosition = null; + } + this.notifyChanged(); + } + ); + }, + + onPanoramaChangeView: function() { + let markerIcon = this.marker.getElement(); + if (markerIcon) { + markerIcon = markerIcon.children[0] + } + this.getGoogleApi().then((api) => { + const pov = api.panorama.getPov(); + if (markerIcon) { + markerIcon.style.transform = `rotate(${pov.heading}deg)`; + } + this.panoramaAngle = pov; + this.notifyChanged(); + } + ); + }, + + notifyChanged: function() { + this.fire('panoramachanged'); + }, + + getGoogleApi: function() { + if (!this._googleApi) { + this._googleApi = getGoogle().then((google) => { + const panorama = new google.maps.StreetViewPanorama(this._panoramaContainer, { + enableCloseButton: true, + imageDateControl: true + } + ); + panorama.addListener('position_changed', this.onPanoramaChangePosition.bind(this)); + panorama.addListener('pov_changed', this.onPanoramaChangeView.bind(this)); + panorama.addListener('closeclick', this.hidePanorama.bind(this)); + + return { + google, + service: new google.maps.StreetViewService(), + panorama + + } + } + ); + } + return this._googleApi; + + } + } +); + +L.Control.Panoramas.include(L.Mixin.HashState); +L.Control.Panoramas.include({ + stateChangeEvents: ['panoramachanged'], + + serializeState: function() { + if (!this.coverageVisible) { + return null; + } + const state = []; + if (this.panoramaVisible && this.panoramaPosition && this.panoramaAngle !== undefined) { + state.push(this.panoramaPosition.lat.toFixed(5)); + state.push(this.panoramaPosition.lng.toFixed(5)); + state.push(Math.round(this.panoramaAngle.heading).toFixed()); + state.push(Math.round(this.panoramaAngle.pitch).toFixed()); + state.push(Math.round(this.panoramaAngle.zoom).toFixed(2)); + } + return state; + }, + + unserializeState: function(state) { + if (!state) { + this.hidePanorama(); + this.hideCoverage(); + return true; + } + if (state.length === 0) { + this.hidePanorama(); + this.showCoverage(); + return true; + } + + const lat = parseFloat(state[0]); + const lng = parseFloat(state[1]); + const heading = parseFloat(state[2]); + const pitch = parseFloat(state[3]); + const zoom = parseFloat(state[4]); + if (!isNaN(lat) && !isNaN(lng) && !isNaN(heading) && !isNaN(pitch)) { + this.showCoverage(); + this.showPanoramaAtPos(L.latLng(lat, lng), {heading, pitch, zoom}); + return true; + } + + return false; + } + } +); +\ No newline at end of file diff --git a/src/lib/leaflet.control.panoramas/panoramas.js b/src/lib/leaflet.control.panoramas/panoramas.js @@ -1,246 +0,0 @@ -import L from 'leaflet'; -import './style.css'; -import 'lib/controls-styles/controls-styles.css'; -import getGoogle from 'lib/googleMapsApi/googleMapsApi'; - - -L.Control.Panoramas = L.Control.extend({ - includes: L.Mixin.Events, - - options: { - position: 'topleft' - }, - - initialize: function(panoramaContainer, options) { - L.Control.prototype.initialize.call(this, options); - this._panoramaContainer = panoramaContainer; - - const icon = L.divIcon({ - className: 'leaflet-panorama-marker-wraper', - html: '<div class="leaflet-panorama-marker"></div>' - } - ); - this.marker = L.marker([0, 0], {icon: icon}); - }, - - onAdd: function(map) { - this._map = map; - const container = L.DomUtil.create('a', 'leaflet-control leaflet-control-button leaflet-contol-panoramas'); - container.title = 'Show panoramas'; - L.DomEvent.disableClickPropagation(container); - if (!L.Browser.touch) { - L.DomEvent.disableScrollPropagation(container); - } - L.DomEvent.on(container, 'click', this.onButtonClick, this); - - map.createPane('rasterOverlay').style.zIndex = 300; - this._coverageLayer = L.tileLayer( - 'https://maps.googleapis.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m8!1e2!2ssvv!4m2!1scb_client!2sapiv3!4m2!1scc!2s*211m3*211e3*212b1*213e2*211m3*211e2*212b1*213e2!3m5!3sUS!12m1!1e40!12m1!1e18!4e0', - {pane: 'rasterOverlay'} - ); - - return container; - }, - - onRemove: function() { - this._map = null; - this.hideCoverage(); - this.hidePanorama(); - }, - - showPanorama: function() { - if (this.panoramaVisible) { - return; - } - L.DomUtil.addClass(this._panoramaContainer, 'enabled'); - this.getGoogleApi().then((api) => api.panorama.setVisible(true)); - window.dispatchEvent(new Event('resize')); - this.marker.addTo(this._map); - this.panoramaVisible = true; - this.notifyChanged(); - }, - - hidePanorama: function() { - if (!this.panoramaVisible) { - return; - } - this.getGoogleApi().then((api) => api.panorama.setVisible(false)); - L.DomUtil.removeClass(this._panoramaContainer, 'enabled'); - window.dispatchEvent(new Event('resize')); - this._map.removeLayer(this.marker); - this.panoramaVisible = false; - this.notifyChanged(); - }, - - showCoverage: function() { - if (this.coverageVisible) { - return; - } - L.DomUtil.addClass(this.getContainer(), 'enabled'); - this._coverageLayer.addTo(this._map); - this._map.on('click', this.onMapClick, this); - this.coverageVisible = true; - this.notifyChanged(); - }, - - onMapClick: function(e) { - this.showPanoramaAtPos(e.latlng); - }, - - showPanoramaAtPos: function(latlng, pov) { - this.showPanorama(); - const searchRadiusPx = 24; - const p = this._map.project(latlng).add([searchRadiusPx, 0]); - const searchRadiusMeters = latlng.distanceTo(this._map.unproject(p)); - - function setPanoramaPosition(api, panoData, status) { - if (status === api.google.maps.StreetViewStatus.OK) { - api.panorama.setPosition(panoData.location.latLng); - } - if (pov) { - api.panorama.setPov(pov); - } - } - - this.getGoogleApi().then((api) => { - api.service.getPanorama({ - location: latlng, - radius: searchRadiusMeters, - preference: api.google.maps.StreetViewPreference.NEAREST - }, setPanoramaPosition.bind(null, api) - ); - } - ); - }, - - hideCoverage: function() { - if (!this.coverageVisible) { - return; - } - L.DomUtil.removeClass(this.getContainer(), 'enabled'); - this._coverageLayer.removeFrom(this._map); - this._map.off('click', this.onMapClick, this); - this.coverageVisible = false; - this.notifyChanged(); - }, - - onButtonClick: function() { - if (!this.coverageVisible) { - this.showCoverage(); - } else { - this.hideCoverage(); - this.hidePanorama(); - } - }, - - onPanoramaChangePosition: function() { - this.getGoogleApi().then((api) => { - let pos = api.panorama.getPosition(); - if (pos) { - pos = L.latLng([pos.lat(), pos.lng()]); - this.marker.setLatLng(pos); - if (!this._map.getBounds().contains(pos)) { - this._map.panTo(pos); - } - this.panoramaPosition = pos; - } else { - this.panoramaPosition = null; - } - this.notifyChanged(); - } - ); - }, - - onPanoramaChangeView: function() { - let markerIcon = this.marker.getElement(); - if (markerIcon) { - markerIcon = markerIcon.children[0] - } - this.getGoogleApi().then((api) => { - const pov = api.panorama.getPov(); - if (markerIcon) { - markerIcon.style.transform = `rotate(${pov.heading}deg)`; - } - this.panoramaAngle = pov; - this.notifyChanged(); - } - ); - }, - - notifyChanged: function() { - this.fire('panoramachanged'); - }, - - getGoogleApi: function() { - if (!this._googleApi) { - this._googleApi = getGoogle().then((google) => { - const panorama = new google.maps.StreetViewPanorama(this._panoramaContainer, { - enableCloseButton: true, - imageDateControl: true - } - ); - panorama.addListener('position_changed', this.onPanoramaChangePosition.bind(this)); - panorama.addListener('pov_changed', this.onPanoramaChangeView.bind(this)); - panorama.addListener('closeclick', this.hidePanorama.bind(this)); - - return { - google, - service: new google.maps.StreetViewService(), - panorama - - } - } - ); - } - return this._googleApi; - - } - } -); - -L.Control.Panoramas.include(L.Mixin.HashState); -L.Control.Panoramas.include({ - stateChangeEvents: ['panoramachanged'], - - serializeState: function() { - if (!this.coverageVisible) { - return null; - } - const state = []; - if (this.panoramaVisible && this.panoramaPosition && this.panoramaAngle !== undefined) { - state.push(this.panoramaPosition.lat.toFixed(5)); - state.push(this.panoramaPosition.lng.toFixed(5)); - state.push(Math.round(this.panoramaAngle.heading).toFixed()); - state.push(Math.round(this.panoramaAngle.pitch).toFixed()); - state.push(Math.round(this.panoramaAngle.zoom).toFixed(2)); - } - return state; - }, - - unserializeState: function(state) { - if (!state) { - this.hidePanorama(); - this.hideCoverage(); - return true; - } - if (state.length === 0) { - this.hidePanorama(); - this.showCoverage(); - return true; - } - - const lat = parseFloat(state[0]); - const lng = parseFloat(state[1]); - const heading = parseFloat(state[2]); - const pitch = parseFloat(state[3]); - const zoom = parseFloat(state[4]); - if (!isNaN(lat) && !isNaN(lng) && !isNaN(heading) && !isNaN(pitch)) { - this.showCoverage(); - this.showPanoramaAtPos(L.latLng(lat, lng), {heading, pitch, zoom}); - return true; - } - - return false; - } - } -); -\ No newline at end of file diff --git a/src/lib/leaflet.control.printPages/control.js b/src/lib/leaflet.control.printPages/control.js @@ -4,7 +4,7 @@ import 'lib/knockout.component.progress/progress'; import 'lib/controls-styles/controls-styles.css'; import './control.css'; import PageFeature from './pageFeature'; -import Contextmenu from 'lib/contextmenu/contextmenu'; +import Contextmenu from 'lib/contextmenu'; import {renderMap} from './map-render' import formHtml from './form.html'; diff --git a/src/lib/leaflet.control.track-list/track-list.js b/src/lib/leaflet.control.track-list/track-list.js @@ -1,22 +1,22 @@ import L from 'leaflet'; import ko from 'knockout'; -import Contextmenu from 'lib/contextmenu/contextmenu'; +import Contextmenu from 'lib/contextmenu'; import 'lib/knockout.component.progress/progress'; import './track-list.css'; -import {selectFiles, readFiles} from 'lib/file-read/file-read'; +import {selectFiles, readFiles} from 'lib/file-read'; import {parseGeoFile} from './lib/geo_file_formats'; -import urlViaCorsProxy from 'lib/CORSProxy/proxy'; -import {fetch} from 'lib/xhr-promise/xhr-promise'; +import urlViaCorsProxy from 'lib/CORSProxy'; +import {fetch} from 'lib/xhr-promise'; import geoExporters from './lib/geo_file_exporters'; -import copyToClipboard from 'lib/clipboardCopy/clipboardCopy'; +import copyToClipboard from 'lib/clipboardCopy'; import {saveAs} from 'browser-filesaver'; -import 'lib/leaflet.polyline-edit/edit_line'; -import 'lib/leaflet.polyline-measure/measured_line'; -import 'lib/leaflet.layer.canvasMarkers/canvasMarkers'; -import 'lib/leaflet.lineutil.simplifyLatLngs/simplify'; -import iconFromBackgroundImage from 'lib/iconFromBackgroundImage/iconFromBackgroundImage'; +import 'lib/leaflet.polyline-edit'; +import 'lib/leaflet.polyline-measure'; +import 'lib/leaflet.layer.canvasMarkers'; +import 'lib/leaflet.lineutil.simplifyLatLngs'; +import iconFromBackgroundImage from 'lib/iconFromBackgroundImage'; import 'lib/controls-styles/controls-styles.css'; -import 'lib/leaflet.control.elevation-profile/elevation-profile'; +import 'lib/leaflet.control.elevation-profile'; var MeasuredEditableLine = L.MeasuredLine.extend({}); MeasuredEditableLine.include(L.Polyline.EditMixin); diff --git a/src/lib/leaflet.controls.raise-on-focus/raise-on-focus.js b/src/lib/leaflet.controls.raise-on-focus/index.js diff --git a/src/lib/leaflet.fixAnimationBug/leaflet.fixAnimationBug.js b/src/lib/leaflet.fixAnimationBug/index.js diff --git a/src/lib/leaflet.layer.bing/bing.js b/src/lib/leaflet.layer.bing/index.js diff --git a/src/lib/leaflet.layer.canvasMarkers/canvasMarkers.js b/src/lib/leaflet.layer.canvasMarkers/index.js diff --git a/src/lib/leaflet.layer.geojson-ajax/geojson-ajax.js b/src/lib/leaflet.layer.geojson-ajax/geojson-ajax.js @@ -1,33 +0,0 @@ -import L from 'leaflet'; -import {fetch} from 'lib/xhr-promise/xhr-promise'; -import {notifyXhrError} from 'lib/notifications/notifications'; - -L.Layer.GeoJSONAjax = L.GeoJSON.extend({ - options: { - requestTimeout: 30000 - }, - - initialize: function(url, options) { - L.GeoJSON.prototype.initialize.call(this, null, options); - this.url = url; - }, - - loadData: function() { - if (this._loadStarted) { - return; - } - this._loadStarted = true; - fetch(this.url, {responseType: 'json', timeout: this.options.requestTimeout}) - .then( - (xhr) => this.addData(xhr.response), - (xhr) => notifyXhrError(xhr, `GeoJSON data from ${this.url}`) - ) - }, - - onAdd: function(map) { - L.GeoJSON.prototype.onAdd.call(this, map); - this.loadData(); - } - } -); - diff --git a/src/lib/leaflet.layer.geojson-ajax/index.js b/src/lib/leaflet.layer.geojson-ajax/index.js @@ -0,0 +1,33 @@ +import L from 'leaflet'; +import {fetch} from 'lib/xhr-promise'; +import {notifyXhrError} from 'lib/notifications'; + +L.Layer.GeoJSONAjax = L.GeoJSON.extend({ + options: { + requestTimeout: 30000 + }, + + initialize: function(url, options) { + L.GeoJSON.prototype.initialize.call(this, null, options); + this.url = url; + }, + + loadData: function() { + if (this._loadStarted) { + return; + } + this._loadStarted = true; + fetch(this.url, {responseType: 'json', timeout: this.options.requestTimeout}) + .then( + (xhr) => this.addData(xhr.response), + (xhr) => notifyXhrError(xhr, `GeoJSON data from ${this.url}`) + ) + }, + + onAdd: function(map) { + L.GeoJSON.prototype.onAdd.call(this, map); + this.loadData(); + } + } +); + diff --git a/src/lib/leaflet.layer.google/google.js b/src/lib/leaflet.layer.google/google.js @@ -1,173 +0,0 @@ -import L from 'leaflet'; -import getGoogle from 'lib/googleMapsApi/googleMapsApi'; - -L.Layer.Google = L.GridLayer.extend({ - options: {}, - - // Possible types: SATELLITE, ROADMAP, HYBRID, TERRAIN - initialize: function(mapType, options) { - L.GridLayer.prototype.initialize.call(this, options); - L.Layer.prototype.constructor(options); - this.mapType = mapType; - }, - - onAdd: function(map) { - this._clearTiles(); - L.GridLayer.prototype.onAdd.call(this, map); - this._googleMapContainer = L.DomUtil.create('div', '', map.getContainer()); - this._googleMapContainer.style.width = '100%'; - this._googleMapContainer.style.height = '100%'; - // this._googleMapContainer.style.opacity = 0; - this._googleMapContainer.style.visibility = 'hidden'; - this._googleMapContainer.style.pointerEvents = 'none'; - getGoogle().then((google) => { - this.google = google; - this._googleMap = new google.maps.Map(this._googleMapContainer, { - center: new google.maps.LatLng(0, 0), - zoom: 0, - tilt: 0, - mapTypeId: google.maps.MapTypeId[this.mapType], - disableDefaultUI: true, - keyboardShortcuts: false, - draggable: false, - disableDoubleClickZoom: true, - scrollwheel: false, - streetViewControl: false, - clickableIcons: false - } - ); - google.maps.event.addListener(this._googleMap, 'tilesloaded', this._onTilesLoaded.bind(this)); - setTimeout(this._syncPosition.bind(this), 0); - } - ); - map.on({ - viewreset: this._clearTiles, - resize: this._onResize, - move: this._syncPosition, - zoomanim: this._onZoom - }, this - ); - }, - - onRemove: function(map) { - if (this.google) { - this.google.maps.event.clearInstanceListeners(this._googleMap); - } - this._googleMap = null; - this._clearTiles(); - map.getContainer().removeChild(this._googleMapContainer); - map.off({ - viewreset: this._clearTiles, - resize: this._onResize, - move: this._syncPosition, - zoomanim: this._onZoom - }, this - ); - L.GridLayer.prototype.onRemove.call(this, map); - }, - - _clearTiles: function() { - this._pendingTiles = {}; - this._readyTiles = {}; - }, - - _onResize: function() { - if (!this.google) { - return; - } - this.google.maps.event.trigger(this._googleMap, 'resize'); - }, - - _syncPosition: function() { - if (!this.google) { - return; - } - let center = this._map.getCenter(); - setTimeout(() => { - let googleCenter = new this.google.maps.LatLng(center.lat, center.lng); - this._googleMap.setCenter(googleCenter); - this._googleMap.setZoom(this._map.getZoom()); - }, 0 - ); - }, - - _onZoom: function(e) { - let center = e.center; - setTimeout(() => { - let googleCenter = new window.google.maps.LatLng(center.lat, center.lng); - this._googleMap.setCenter(googleCenter); - this._googleMap.setZoom(Math.round(e.zoom)); - }, 0 - ); - }, - - _roadRegexp: /!1i(\d+)!2i(\d+)!3i(\d+)!/, - _satRegexp: /x=(\d+)&y=(\d+)&z=(\d+)/, - - _onTilesLoaded: function() { - this._readyTiles = this._collectMapImageElements(); - this._fullfillPendingTiles(); - }, - - _collectMapImageElements: function() { - const images = this._googleMapContainer.getElementsByTagName('img'); - const tiles = {}; - for (let image of images) { - let url = image.src; - let match, coords; - match = url.match(this._roadRegexp); - - if (match) { - coords = {z: match[1], x: match[2], y: match[3]} - } else { - match = url.match(this._satRegexp); - if (match) { - coords = {x: match[1], y: match[2], z: match[3]} - } - } - if (coords) { - tiles[this._tileCoordsToKey(coords)] = url; - } - } - return tiles; - }, - - _fullfillPendingTiles: function() { - for (let key of Object.keys(this._readyTiles)) { - if (key in this._pendingTiles) { - for (let [img, cb] of this._pendingTiles[key]) { - L.DomEvent.on(img, 'load', L.bind(this._tileOnLoad, this, cb, img)); - L.DomEvent.on(img, 'error', L.bind(this._tileOnError, this, cb, img)); - img.alt = ''; - img.src = this._readyTiles[key]; - } - delete this._pendingTiles[key]; - } - } - }, - - _tileOnLoad: function(done, tile) { - // For https://github.com/Leaflet/Leaflet/issues/3332 - if (L.Browser.ielt9) { - setTimeout(L.bind(done, this, null, tile), 0); - } else { - done(null, tile); - } - }, - - _tileOnError: function(done, tile, e) { - done(e, tile); - }, - - createTile: function(coords, done) { - const tile = L.DomUtil.create('img'); - const key = this._tileCoordsToKey(coords); - if (!(key in this._pendingTiles)) { - this._pendingTiles[key] = []; - } - this._pendingTiles[key].push([tile, done]); - setTimeout(this._fullfillPendingTiles.bind(this), 0); - return tile; - } - } -); diff --git a/src/lib/leaflet.layer.google/index.js b/src/lib/leaflet.layer.google/index.js @@ -0,0 +1,173 @@ +import L from 'leaflet'; +import getGoogle from 'lib/googleMapsApi'; + +L.Layer.Google = L.GridLayer.extend({ + options: {}, + + // Possible types: SATELLITE, ROADMAP, HYBRID, TERRAIN + initialize: function(mapType, options) { + L.GridLayer.prototype.initialize.call(this, options); + L.Layer.prototype.constructor(options); + this.mapType = mapType; + }, + + onAdd: function(map) { + this._clearTiles(); + L.GridLayer.prototype.onAdd.call(this, map); + this._googleMapContainer = L.DomUtil.create('div', '', map.getContainer()); + this._googleMapContainer.style.width = '100%'; + this._googleMapContainer.style.height = '100%'; + // this._googleMapContainer.style.opacity = 0; + this._googleMapContainer.style.visibility = 'hidden'; + this._googleMapContainer.style.pointerEvents = 'none'; + getGoogle().then((google) => { + this.google = google; + this._googleMap = new google.maps.Map(this._googleMapContainer, { + center: new google.maps.LatLng(0, 0), + zoom: 0, + tilt: 0, + mapTypeId: google.maps.MapTypeId[this.mapType], + disableDefaultUI: true, + keyboardShortcuts: false, + draggable: false, + disableDoubleClickZoom: true, + scrollwheel: false, + streetViewControl: false, + clickableIcons: false + } + ); + google.maps.event.addListener(this._googleMap, 'tilesloaded', this._onTilesLoaded.bind(this)); + setTimeout(this._syncPosition.bind(this), 0); + } + ); + map.on({ + viewreset: this._clearTiles, + resize: this._onResize, + move: this._syncPosition, + zoomanim: this._onZoom + }, this + ); + }, + + onRemove: function(map) { + if (this.google) { + this.google.maps.event.clearInstanceListeners(this._googleMap); + } + this._googleMap = null; + this._clearTiles(); + map.getContainer().removeChild(this._googleMapContainer); + map.off({ + viewreset: this._clearTiles, + resize: this._onResize, + move: this._syncPosition, + zoomanim: this._onZoom + }, this + ); + L.GridLayer.prototype.onRemove.call(this, map); + }, + + _clearTiles: function() { + this._pendingTiles = {}; + this._readyTiles = {}; + }, + + _onResize: function() { + if (!this.google) { + return; + } + this.google.maps.event.trigger(this._googleMap, 'resize'); + }, + + _syncPosition: function() { + if (!this.google) { + return; + } + let center = this._map.getCenter(); + setTimeout(() => { + let googleCenter = new this.google.maps.LatLng(center.lat, center.lng); + this._googleMap.setCenter(googleCenter); + this._googleMap.setZoom(this._map.getZoom()); + }, 0 + ); + }, + + _onZoom: function(e) { + let center = e.center; + setTimeout(() => { + let googleCenter = new window.google.maps.LatLng(center.lat, center.lng); + this._googleMap.setCenter(googleCenter); + this._googleMap.setZoom(Math.round(e.zoom)); + }, 0 + ); + }, + + _roadRegexp: /!1i(\d+)!2i(\d+)!3i(\d+)!/, + _satRegexp: /x=(\d+)&y=(\d+)&z=(\d+)/, + + _onTilesLoaded: function() { + this._readyTiles = this._collectMapImageElements(); + this._fullfillPendingTiles(); + }, + + _collectMapImageElements: function() { + const images = this._googleMapContainer.getElementsByTagName('img'); + const tiles = {}; + for (let image of images) { + let url = image.src; + let match, coords; + match = url.match(this._roadRegexp); + + if (match) { + coords = {z: match[1], x: match[2], y: match[3]} + } else { + match = url.match(this._satRegexp); + if (match) { + coords = {x: match[1], y: match[2], z: match[3]} + } + } + if (coords) { + tiles[this._tileCoordsToKey(coords)] = url; + } + } + return tiles; + }, + + _fullfillPendingTiles: function() { + for (let key of Object.keys(this._readyTiles)) { + if (key in this._pendingTiles) { + for (let [img, cb] of this._pendingTiles[key]) { + L.DomEvent.on(img, 'load', L.bind(this._tileOnLoad, this, cb, img)); + L.DomEvent.on(img, 'error', L.bind(this._tileOnError, this, cb, img)); + img.alt = ''; + img.src = this._readyTiles[key]; + } + delete this._pendingTiles[key]; + } + } + }, + + _tileOnLoad: function(done, tile) { + // For https://github.com/Leaflet/Leaflet/issues/3332 + if (L.Browser.ielt9) { + setTimeout(L.bind(done, this, null, tile), 0); + } else { + done(null, tile); + } + }, + + _tileOnError: function(done, tile, e) { + done(e, tile); + }, + + createTile: function(coords, done) { + const tile = L.DomUtil.create('img'); + const key = this._tileCoordsToKey(coords); + if (!(key in this._pendingTiles)) { + this._pendingTiles[key] = []; + } + this._pendingTiles[key].push([tile, done]); + setTimeout(this._fullfillPendingTiles.bind(this), 0); + return tile; + } + } +); diff --git a/src/lib/leaflet.layer.nordeskart/index.js b/src/lib/leaflet.layer.nordeskart/index.js @@ -0,0 +1,89 @@ +import L from 'leaflet'; +import {fetch} from 'lib/xhr-promise'; +import {formatXhrError, notify} from 'lib/notifications'; + +function parseResponse(s) { + let data; + try { + data = JSON.parse(s); + } catch (e) { + throw new Error('invalid JSON'); + } + if (!data.token) { + throw new Error('no token in response'); + } + return data.token; +} + +function getToken() { + return fetch('https://www.ut.no/kart/HentBaatToken/', {timeout: 10000}) + .then(function(xhr) { + try { + return {token: parseResponse(xhr.responseText)} + } catch (e) { + console.log(e); + return {error: 'Server returned invalid token for Norway map'} + } + }, + function(xhr) { + return {error: formatXhrError(xhr, 'token for Norway map')} + } + ); +} + +L.TileLayer.Nordeskart = L.TileLayer.extend({ + options: { + tokenUpdateInterval: 5 * 60 * 1000 + }, + + initialize: function(url, options) { + this.__url = url; + L.TileLayer.prototype.initialize.call(this, '', options); + }, + + baatTokenUpToDate: function() { + let nextUpdate = 0; + if (window.localStorage) { + nextUpdate = parseInt(localStorage.getItem('baatTokenNextUpdate'), 10) || 0; + } + return Date.now() < nextUpdate && localStorage.getItem('baatToken'); + }, + + storeBaatToken: function(token) { + if (window.localStorage) { + localStorage.setItem('baatToken', token); + localStorage.setItem('baatTokenNextUpdate', Date.now().toString() + this.options.tokenUpdateInterval); + } + }, + + periodicTokenUpdate: function() { + if (!this._map) { + return; + } + getToken().then((data) => { + if (data.error) { + notify(data.error); + } else { + if (data.token !== this.options.baatToken) { + this.options.baatToken = data.token; + if (!this._url) { + this.setUrl(this.__url); + } + } + this.storeBaatToken(data.token); + } + setTimeout(() => this.periodicTokenUpdate(), this.options.tokenUpdateInterval); + }); + }, + + onAdd: function(map) { + const token = this.baatTokenUpToDate(); + if (token) { + this.options.baatToken = token; + this.setUrl(this.__url); + } + this.periodicTokenUpdate(); + return L.TileLayer.prototype.onAdd.call(this, map); + } + } +); diff --git a/src/lib/leaflet.layer.nordeskart/norderskart.js b/src/lib/leaflet.layer.nordeskart/norderskart.js @@ -1,89 +0,0 @@ -import L from 'leaflet'; -import {fetch} from 'lib/xhr-promise/xhr-promise'; -import {formatXhrError, notify} from 'lib/notifications/notifications'; - -function parseResponse(s) { - let data; - try { - data = JSON.parse(s); - } catch (e) { - throw new Error('invalid JSON'); - } - if (!data.token) { - throw new Error('no token in response'); - } - return data.token; -} - -function getToken() { - return fetch('https://www.ut.no/kart/HentBaatToken/', {timeout: 10000}) - .then(function(xhr) { - try { - return {token: parseResponse(xhr.responseText)} - } catch (e) { - console.log(e); - return {error: 'Server returned invalid token for Norway map'} - } - }, - function(xhr) { - return {error: formatXhrError(xhr, 'token for Norway map')} - } - ); -} - -L.TileLayer.Nordeskart = L.TileLayer.extend({ - options: { - tokenUpdateInterval: 5 * 60 * 1000 - }, - - initialize: function(url, options) { - this.__url = url; - L.TileLayer.prototype.initialize.call(this, '', options); - }, - - baatTokenUpToDate: function() { - let nextUpdate = 0; - if (window.localStorage) { - nextUpdate = parseInt(localStorage.getItem('baatTokenNextUpdate'), 10) || 0; - } - return Date.now() < nextUpdate && localStorage.getItem('baatToken'); - }, - - storeBaatToken: function(token) { - if (window.localStorage) { - localStorage.setItem('baatToken', token); - localStorage.setItem('baatTokenNextUpdate', Date.now().toString() + this.options.tokenUpdateInterval); - } - }, - - periodicTokenUpdate: function() { - if (!this._map) { - return; - } - getToken().then((data) => { - if (data.error) { - notify(data.error); - } else { - if (data.token !== this.options.baatToken) { - this.options.baatToken = data.token; - if (!this._url) { - this.setUrl(this.__url); - } - } - this.storeBaatToken(data.token); - } - setTimeout(() => this.periodicTokenUpdate(), this.options.tokenUpdateInterval); - }); - }, - - onAdd: function(map) { - const token = this.baatTokenUpToDate(); - if (token) { - this.options.baatToken = token; - this.setUrl(this.__url); - } - this.periodicTokenUpdate(); - return L.TileLayer.prototype.onAdd.call(this, map); - } - } -); diff --git a/src/lib/leaflet.layer.soviet-topomaps-grid/index.js b/src/lib/leaflet.layer.soviet-topomaps-grid/index.js @@ -0,0 +1,322 @@ +import L from 'leaflet'; +import './style.css'; +import Contextmenu from 'lib/contextmenu'; +import copyToClipboard from 'lib/clipboardCopy'; + +function zeroPad(num, size) { + var s = num + ""; + while (s.length < size) { + s = "0" + s; + } + return s; +} + +const bigLetterReplacers = [ + { + '1': 'А', + '2': 'Б', + '3': 'В', + '4': 'Г' + }, + { + '1': 'A', + '2': 'B', + '3': 'C', + '4': 'D' + } +]; + +var Nomenclature = { + getQuadName1m: function(column, row, join) { + var name = ''; + if (row < 0) { + row = -row - 1; + name += 'x'; + } + column += 31; + name += 'ABCDEFGHIJKLMNOPQRSTUV'[row] + '-' + zeroPad(column, 2); + for (var n = 1; n <= join - 1; n++) { + name += ',' + zeroPad(column + n, 2); + } + return [name]; + }, + + getQuadName500k: function(column, row, join) { + var name1 = this.getQuadName1m(Math.floor(column / 2), Math.floor(row / 2), 1)[0]; + var subquad = 2 - (row & 1) * 2 + (column & 1) + 1; + let name2 = '-' + subquad; + if (join > 1) { + name2 += ',' + (subquad + 1); + } + if (join === 4) { + name2 += + ',' + this.getQuadName1m(Math.floor((column + 2) / 2), Math.floor(row / 2), 1) + '-' + subquad + + ',' + (subquad + 1); + } + const names = [name1 + name2]; + for (let replacer of bigLetterReplacers) { + let name3 = name2.replace(/\b[1-4]\b/g, (s) => { + return replacer[s] + } + ); + names.push(name1 + name3); + } + return names; + }, + + getQuadName100k: function(column, row, join) { + var name = this.getQuadName1m(Math.floor(column / 12), Math.floor(row / 12), 1); + const subRow = row - Math.floor(row / 12) * 12; + const subColumn = column - Math.floor(column / 12) * 12; + var subquad = 132 - subRow * 12 + subColumn + 1; + name = name + '-' + zeroPad(subquad, 3); + if (join > 1) { + name += ',' + zeroPad(subquad + 1, 3); + } + if (join === 4) { + name += ',' + zeroPad(subquad + 2, 3) + ',' + zeroPad(subquad + 3, 3); + } + + return [name]; + }, + + getQuadName050k: function(column, row, join) { + var name1 = this.getQuadName100k(Math.floor(column / 2), Math.floor(row / 2), 1); + var subquad = 2 - (row & 1) * 2 + (column & 1) + 1; + let name2 = '-' + subquad; + if (join > 1) { + name2 += ',' + (subquad + 1); + } + if (join === 4) { + name2 += ',' + this.getQuadName100k(Math.floor((column + 2) / 2), Math.floor(row / 2), 1) + '-' + + subquad + ',' + (subquad + 1); + } + const names = [name1 + name2]; + for (let replacer of bigLetterReplacers) { + let name3 = name2.replace(/\b[1-4]\b/g, (s) => { + return replacer[s] + } + ); + names.push(name1 + name3); + } + return names; + }, + + _getQuads: function(bounds, row_height, column_width, name_factory) { + bounds = L.latLngBounds(bounds); + var quads = []; + var min_lat = Math.max(bounds.getSouth(), -84); + var max_lat = Math.min(bounds.getNorth(), 84); + var min_row = Math.floor(min_lat / row_height); + for (var row = min_row; row * row_height < max_lat; row++) { + var row_south = row * row_height; + var row_north = row_south + row_height; + var joined_quads; + if (row_south >= 76 || row_north <= -76) { + joined_quads = 4; + } else if (row_south >= 60 || row_north <= -60) { + joined_quads = 2; + } else { + joined_quads = 1; + } + var min_lon = Math.max(bounds.getWest(), -180); + var max_lon = Math.min(bounds.getEast(), 180); + var min_column = Math.floor((min_lon + 180) / column_width / joined_quads) * joined_quads - + Math.round(180 / column_width); + for (var column = min_column; column * column_width < max_lon; column += joined_quads) { + var column_west = column * column_width; + var column_east = column_west + column_width * joined_quads; + var quad_bounds = L.latLngBounds([[row_south, column_west], [row_north, column_east]]); + var names = name_factory(column, row, joined_quads); + quads.push({'names': names, 'bounds': quad_bounds}); + } + } + return quads; + }, + + getQuads1m: function(bounds) { + return this._getQuads(bounds, 4, 6, this.getQuadName1m); + }, + + getQuads500k: function(bounds) { + return this._getQuads(bounds, 2, 3, this.getQuadName500k.bind(this)); + }, + + getQuads100k: function(bounds) { + return this._getQuads(bounds, 4 / 12, 6 / 12, this.getQuadName100k.bind(this)); + }, + + getQuads050k: function(bounds) { + return this._getQuads(bounds, 4 / 12 / 2, 6 / 12 / 2, this.getQuadName050k.bind(this)); + } + + +}; + +L.Layer.SovietTopoGrid = L.LayerGroup.extend({ + options: {}, + + initialize: function(options) { + L.LayerGroup.prototype.initialize.call(this); + L.Util.setOptions(this, options); + this.renderer = L.svg({padding: 0.5}); + this._quads = {}; + this._updateRenderer = L.Util.throttle(this._updateRenderer, 100, this); + }, + + onAdd: function(map) { + this._map = map; + map.on('zoomend', this._reset, this); + map.on('move', this._update, this); + map.on('move', this._updateRenderer, this); + this._update(); + }, + + onRemove: function(map) { + map.off('zoomend', this._reset, this); + map.off('move', this._update, this); + this._cleanupQuads(true); + }, + + _addQuad: function(bounds, id, titles, color, layer, scale) { + if (id in this._quads) { + return; + } + var rect_options = { + smoothFactor: 0, + noClip: true, + clickable: false, + fill: false, + opacity: {1: 0.7, 2: 0.4}[layer], + color: color, + weight: {1: 1, 2: 3}[layer], + renderer: this.renderer + }; + + var rect = L.rectangle(bounds, rect_options); + this.addLayer(rect); + if (layer === 1) { + rect.bringToBack(); + } + var objects = [rect]; + const title = titles[0].replace(/-/g, ' &ndash; '); + var html = L.Util.template(`<span style="color:{color}">{title}</span>`, {color: color, title: title}); + var icon = L.divIcon({html: html, className: 'leaflet-sovietgrid-quadtitle-' + layer, iconSize: null}); + var marker = L.marker(L.latLngBounds(bounds).getCenter(), {icon: icon}); + this.addLayer(marker); + objects.push(marker); + this._quads[id] = objects; + marker.on('click', this._showContextMenu.bind(this, scale, titles)); + }, + + _showContextMenu: function(scale, titles, e) { + const scaleString = { + '1m': '1:1 000 000', + '500k': '1:500 000', + '100k': '1:100 000', + '050k': '1:50 000' + }[scale]; + const items = [ + {'text': scaleString, disabled: true}, + {'text': 'Click name to copy to clibpoard', disabled: true}, + { + 'text': titles[0], callback: () => { + copyToClipboard(titles[0], e.originalEvent) + } + } + ]; + if (titles.length > 1) { + items.push({ + 'text': titles[1] + ' <span class="leaflet-sovietgrid-lang">RUS</span>', callback: () => { + copyToClipboard(titles[1], e.originalEvent) + } + } + ); + items.push({ + 'text': titles[2] + ' <span class="leaflet-sovietgrid-lang">LAT</span>', callback: () => { + copyToClipboard(titles[2], e.originalEvent) + } + } + ); + } + const menu = new Contextmenu(items); + menu.show(e); + }, + + _removeQuad: function(id) { + this._quads[id].forEach(this.removeLayer.bind(this)); + delete this._quads[id]; + }, + + + _addQuads: function(quads, scale, color, layer) { + quads.forEach(function(quad) { + var id = scale + quad.names[0]; + this._addQuad(quad.bounds, id, quad.names, color, layer, scale); + }.bind(this) + ); + }, + + _addGrid: function() { + var quads; + var layer; + var map_bbox = this._map.getBounds(); + var zoom = this._map.getZoom(); + + if (zoom >= 10) { + quads = Nomenclature.getQuads050k(map_bbox); + layer = (zoom >= 12) ? 2 : 1; + this._addQuads(quads, '050k', '#50d', layer); + } + + if (zoom >= 8 && zoom < 12) { + quads = Nomenclature.getQuads100k(map_bbox); + layer = (zoom >= 10) ? 2 : 1; + this._addQuads(quads, '100k', '#d50', layer); + } + + + if (zoom >= 6 && zoom < 10) { + quads = Nomenclature.getQuads500k(map_bbox); + layer = (zoom >= 8) ? 2 : 1; + this._addQuads(quads, '500k', '#099', layer); + } + + if (zoom >= 4 && zoom < 8) { + layer = (zoom >= 6) ? 2 : 1; + quads = Nomenclature.getQuads1m(map_bbox); + this._addQuads(quads, '1m', 'blue', layer); + } + }, + + _reset: function() { + this._update(true); + }, + + _cleanupQuads: function(reset) { + if (reset === true) { + this.clearLayers(); + this._quads = {}; + } else { + var map_bbox = this._map.getBounds(); + for (var quad_id of Object.keys(this._quads)) { + var rect = this._quads[quad_id][0]; + if (!map_bbox.intersects(rect.getBounds())) { + this._removeQuad(quad_id); + } + } + } + }, + + _updateRenderer: function() { + if (this.renderer._map) { + this.renderer._update(); + } + }, + + _update: function(reset) { + this._cleanupQuads(reset); + this._addGrid(); + } + } +); diff --git a/src/lib/leaflet.layer.soviet-topomaps-grid/soviet-topomaps-grid.js b/src/lib/leaflet.layer.soviet-topomaps-grid/soviet-topomaps-grid.js @@ -1,321 +0,0 @@ -import L from 'leaflet'; -import './style.css'; -import Contextmenu from 'lib/contextmenu/contextmenu'; -import copyToClipboard from 'lib/clipboardCopy/clipboardCopy'; -function zeroPad(num, size) { - var s = num + ""; - while (s.length < size) { - s = "0" + s; - } - return s; -} - -const bigLetterReplacers = [ - { - '1': 'А', - '2': 'Б', - '3': 'В', - '4': 'Г' - }, - { - '1': 'A', - '2': 'B', - '3': 'C', - '4': 'D' - } -]; - -var Nomenclature = { - getQuadName1m: function(column, row, join) { - var name = ''; - if (row < 0) { - row = -row - 1; - name += 'x'; - } - column += 31; - name += 'ABCDEFGHIJKLMNOPQRSTUV'[row] + '-' + zeroPad(column, 2); - for (var n = 1; n <= join - 1; n++) { - name += ',' + zeroPad(column + n, 2); - } - return [name]; - }, - - getQuadName500k: function(column, row, join) { - var name1 = this.getQuadName1m(Math.floor(column / 2), Math.floor(row / 2), 1)[0]; - var subquad = 2 - (row & 1) * 2 + (column & 1) + 1; - let name2 = '-' + subquad; - if (join > 1) { - name2 += ',' + (subquad + 1); - } - if (join === 4) { - name2 += - ',' + this.getQuadName1m(Math.floor((column + 2) / 2), Math.floor(row / 2), 1) + '-' + subquad + - ',' + (subquad + 1); - } - const names = [name1 + name2]; - for (let replacer of bigLetterReplacers) { - let name3 = name2.replace(/\b[1-4]\b/g, (s) => { - return replacer[s] - } - ); - names.push(name1 + name3); - } - return names; - }, - - getQuadName100k: function(column, row, join) { - var name = this.getQuadName1m(Math.floor(column / 12), Math.floor(row / 12), 1); - const subRow = row - Math.floor(row / 12) * 12; - const subColumn = column - Math.floor(column / 12) * 12; - var subquad = 132 - subRow * 12 + subColumn + 1; - name = name + '-' + zeroPad(subquad, 3); - if (join > 1) { - name += ',' + zeroPad(subquad + 1, 3); - } - if (join === 4) { - name += ',' + zeroPad(subquad + 2, 3) + ',' + zeroPad(subquad + 3, 3); - } - - return [name]; - }, - - getQuadName050k: function(column, row, join) { - var name1 = this.getQuadName100k(Math.floor(column / 2), Math.floor(row / 2), 1); - var subquad = 2 - (row & 1) * 2 + (column & 1) + 1; - let name2 = '-' + subquad; - if (join > 1) { - name2 += ',' + (subquad + 1); - } - if (join === 4) { - name2 += ',' + this.getQuadName100k(Math.floor((column + 2) / 2), Math.floor(row / 2), 1) + '-' + - subquad + ',' + (subquad + 1); - } - const names = [name1 + name2]; - for (let replacer of bigLetterReplacers) { - let name3 = name2.replace(/\b[1-4]\b/g, (s) => { - return replacer[s] - } - ); - names.push(name1 + name3); - } - return names; - }, - - _getQuads: function(bounds, row_height, column_width, name_factory) { - bounds = L.latLngBounds(bounds); - var quads = []; - var min_lat = Math.max(bounds.getSouth(), -84); - var max_lat = Math.min(bounds.getNorth(), 84); - var min_row = Math.floor(min_lat / row_height); - for (var row = min_row; row * row_height < max_lat; row++) { - var row_south = row * row_height; - var row_north = row_south + row_height; - var joined_quads; - if (row_south >= 76 || row_north <= -76) { - joined_quads = 4; - } else if (row_south >= 60 || row_north <= -60) { - joined_quads = 2; - } else { - joined_quads = 1; - } - var min_lon = Math.max(bounds.getWest(), -180); - var max_lon = Math.min(bounds.getEast(), 180); - var min_column = Math.floor((min_lon + 180) / column_width / joined_quads) * joined_quads - - Math.round(180 / column_width); - for (var column = min_column; column * column_width < max_lon; column += joined_quads) { - var column_west = column * column_width; - var column_east = column_west + column_width * joined_quads; - var quad_bounds = L.latLngBounds([[row_south, column_west], [row_north, column_east]]); - var names = name_factory(column, row, joined_quads); - quads.push({'names': names, 'bounds': quad_bounds}); - } - } - return quads; - }, - - getQuads1m: function(bounds) { - return this._getQuads(bounds, 4, 6, this.getQuadName1m); - }, - - getQuads500k: function(bounds) { - return this._getQuads(bounds, 2, 3, this.getQuadName500k.bind(this)); - }, - - getQuads100k: function(bounds) { - return this._getQuads(bounds, 4 / 12, 6 / 12, this.getQuadName100k.bind(this)); - }, - - getQuads050k: function(bounds) { - return this._getQuads(bounds, 4 / 12 / 2, 6 / 12 / 2, this.getQuadName050k.bind(this)); - } - - -}; - -L.Layer.SovietTopoGrid = L.LayerGroup.extend({ - options: {}, - - initialize: function(options) { - L.LayerGroup.prototype.initialize.call(this); - L.Util.setOptions(this, options); - this.renderer = L.svg({padding: 0.5}); - this._quads = {}; - this._updateRenderer = L.Util.throttle(this._updateRenderer, 100, this); - }, - - onAdd: function(map) { - this._map = map; - map.on('zoomend', this._reset, this); - map.on('move', this._update, this); - map.on('move', this._updateRenderer, this); - this._update(); - }, - - onRemove: function(map) { - map.off('zoomend', this._reset, this); - map.off('move', this._update, this); - this._cleanupQuads(true); - }, - - _addQuad: function(bounds, id, titles, color, layer, scale) { - if (id in this._quads) { - return; - } - var rect_options = { - smoothFactor: 0, - noClip: true, - clickable: false, - fill: false, - opacity: {1: 0.7, 2: 0.4}[layer], - color: color, - weight: {1: 1, 2: 3}[layer], - renderer: this.renderer - }; - - var rect = L.rectangle(bounds, rect_options); - this.addLayer(rect); - if (layer === 1) { - rect.bringToBack(); - } - var objects = [rect]; - const title = titles[0].replace(/-/g, ' &ndash; '); - var html = L.Util.template(`<span style="color:{color}">{title}</span>`, {color: color, title: title}); - var icon = L.divIcon({html: html, className: 'leaflet-sovietgrid-quadtitle-' + layer, iconSize: null}); - var marker = L.marker(L.latLngBounds(bounds).getCenter(), {icon: icon}); - this.addLayer(marker); - objects.push(marker); - this._quads[id] = objects; - marker.on('click', this._showContextMenu.bind(this, scale, titles)); - }, - - _showContextMenu: function(scale, titles, e) { - const scaleString = { - '1m': '1:1 000 000', - '500k': '1:500 000', - '100k': '1:100 000', - '050k': '1:50 000' - }[scale]; - const items = [ - {'text': scaleString, disabled: true}, - {'text': 'Click name to copy to clibpoard', disabled: true}, - { - 'text': titles[0], callback: () => { - copyToClipboard(titles[0], e.originalEvent) - } - } - ]; - if (titles.length > 1) { - items.push({ - 'text': titles[1] + ' <span class="leaflet-sovietgrid-lang">RUS</span>', callback: () => { - copyToClipboard(titles[1], e.originalEvent) - } - } - ); - items.push({ - 'text': titles[2] + ' <span class="leaflet-sovietgrid-lang">LAT</span>', callback: () => { - copyToClipboard(titles[2], e.originalEvent) - } - } - ); - } - const menu = new Contextmenu(items); - menu.show(e); - }, - - _removeQuad: function(id) { - this._quads[id].forEach(this.removeLayer.bind(this)); - delete this._quads[id]; - }, - - - _addQuads: function(quads, scale, color, layer) { - quads.forEach(function(quad) { - var id = scale + quad.names[0]; - this._addQuad(quad.bounds, id, quad.names, color, layer, scale); - }.bind(this) - ); - }, - - _addGrid: function() { - var quads; - var layer; - var map_bbox = this._map.getBounds(); - var zoom = this._map.getZoom(); - - if (zoom >= 10) { - quads = Nomenclature.getQuads050k(map_bbox); - layer = (zoom >= 12) ? 2 : 1; - this._addQuads(quads, '050k', '#50d', layer); - } - - if (zoom >= 8 && zoom < 12) { - quads = Nomenclature.getQuads100k(map_bbox); - layer = (zoom >= 10) ? 2 : 1; - this._addQuads(quads, '100k', '#d50', layer); - } - - - if (zoom >= 6 && zoom < 10) { - quads = Nomenclature.getQuads500k(map_bbox); - layer = (zoom >= 8) ? 2 : 1; - this._addQuads(quads, '500k', '#099', layer); - } - - if (zoom >= 4 && zoom < 8) { - layer = (zoom >= 6) ? 2 : 1; - quads = Nomenclature.getQuads1m(map_bbox); - this._addQuads(quads, '1m', 'blue', layer); - } - }, - - _reset: function() { - this._update(true); - }, - - _cleanupQuads: function(reset) { - if (reset === true) { - this.clearLayers(); - this._quads = {}; - } else { - var map_bbox = this._map.getBounds(); - for (var quad_id of Object.keys(this._quads)) { - var rect = this._quads[quad_id][0]; - if (!map_bbox.intersects(rect.getBounds())) { - this._removeQuad(quad_id); - } - } - } - }, - - _updateRenderer: function() { - if (this.renderer._map) { - this.renderer._update(); - } - }, - - _update: function(reset) { - this._cleanupQuads(reset); - this._addGrid(); - } - } -); diff --git a/src/lib/leaflet.layer.westraPasses/index.js b/src/lib/leaflet.layer.westraPasses/index.js @@ -0,0 +1,99 @@ +import L from 'leaflet'; +import './westraPasses.css'; +import 'lib/leaflet.layer.geojson-ajax'; +import westraPasesMarkers from './westraPassesMarkers'; + +L.Layer.WestraPasses = L.Layer.extend({ + options: { + fileRegions1: 'westra_regions_geo1.json', + fileRegions2: 'westra_regions_geo2.json', + scaleDependent: true + }, + + initialize: function(baseUrl, options) { + L.setOptions(this, options); + this.markers = new westraPasesMarkers(baseUrl); + this.regions1 = new L.Layer.GeoJSONAjax(baseUrl + this.options.fileRegions1, { + className: 'westra-region-polygon', + onEachFeature: this._setRegionLabel.bind(this, 'regions1') + } + ); + this.regions2 = new L.Layer.GeoJSONAjax(baseUrl + this.options.fileRegions2, { + className: 'westra-region-polygon', + onEachFeature: this._setRegionLabel.bind(this, 'regions2') + } + ); + }, + _setRegionLabel: function(layerName, feature, layer) { + var latlon = layer.getBounds().getCenter(); + var icon = L.divIcon({ + className: 'westra-region-label', + html: '<span>' + feature.properties.name + '</span>' + } + ); + var labelMarker = L.marker(latlon, {icon: icon}); + this[layerName].addLayer(labelMarker); + function zoomToRegion() { + this._map.fitBounds(layer.getBounds()); + } + + layer.on('click', zoomToRegion, this); + labelMarker.on('click', zoomToRegion, this); + }, + + setLayersVisibility: function(e) { + if (!this._map) { + return; + } + var newZoom; + var zoomFinished = e ? (e.type !== 'zoomanim') : true; + if (e && e.zoom !== undefined) { + newZoom = e.zoom; + } else { + newZoom = this._map.getZoom(); + } + if (newZoom < 2) { + this._map.removeLayer(this.markers); + this._map.removeLayer(this.regions1); + this._map.removeLayer(this.regions2); + } else if (newZoom < 7) { + this._map.removeLayer(this.markers); + this._map.addLayer(this.regions1); + this._map.removeLayer(this.regions2); + } + else if (newZoom < 10) { + this._map.removeLayer(this.regions1); + this._map.addLayer(this.regions2); + this._map.removeLayer(this.markers); + } else { + if (zoomFinished) { + this._map.addLayer(this.markers); + } + this._map.removeLayer(this.regions1); + this._map.removeLayer(this.regions2); + } + }, + + onAdd: function(map) { + this._map = map; + this.markers.loadData(); + this.regions1.loadData(); + this.regions2.loadData(); + this.setLayersVisibility(); + map.on('zoomend', this.setLayersVisibility, this); + map.on('zoomanim', this.setLayersVisibility, this); + }, + + onRemove: function() { + this._map.removeLayer(this.markers); + this._map.removeLayer(this.regions1); + this._map.removeLayer(this.regions2); + this._map.off('zoomend', this.setLayersVisibility, this); + this._map.off('zoomanim', this.setLayersVisibility, this); + this._map = null; + } + } +); + + + diff --git a/src/lib/leaflet.layer.westraPasses/westraPasses.js b/src/lib/leaflet.layer.westraPasses/westraPasses.js @@ -1,99 +0,0 @@ -import L from 'leaflet'; -import './westraPasses.css'; -import 'lib/leaflet.layer.geojson-ajax/geojson-ajax'; -import westraPasesMarkers from './westraPassesMarkers'; - -L.Layer.WestraPasses = L.Layer.extend({ - options: { - fileRegions1: 'westra_regions_geo1.json', - fileRegions2: 'westra_regions_geo2.json', - scaleDependent: true - }, - - initialize: function(baseUrl, options) { - L.setOptions(this, options); - this.markers = new westraPasesMarkers(baseUrl); - this.regions1 = new L.Layer.GeoJSONAjax(baseUrl + this.options.fileRegions1, { - className: 'westra-region-polygon', - onEachFeature: this._setRegionLabel.bind(this, 'regions1') - } - ); - this.regions2 = new L.Layer.GeoJSONAjax(baseUrl + this.options.fileRegions2, { - className: 'westra-region-polygon', - onEachFeature: this._setRegionLabel.bind(this, 'regions2') - } - ); - }, - _setRegionLabel: function(layerName, feature, layer) { - var latlon = layer.getBounds().getCenter(); - var icon = L.divIcon({ - className: 'westra-region-label', - html: '<span>' + feature.properties.name + '</span>' - } - ); - var labelMarker = L.marker(latlon, {icon: icon}); - this[layerName].addLayer(labelMarker); - function zoomToRegion() { - this._map.fitBounds(layer.getBounds()); - } - - layer.on('click', zoomToRegion, this); - labelMarker.on('click', zoomToRegion, this); - }, - - setLayersVisibility: function(e) { - if (!this._map) { - return; - } - var newZoom; - var zoomFinished = e ? (e.type !== 'zoomanim') : true; - if (e && e.zoom !== undefined) { - newZoom = e.zoom; - } else { - newZoom = this._map.getZoom(); - } - if (newZoom < 2) { - this._map.removeLayer(this.markers); - this._map.removeLayer(this.regions1); - this._map.removeLayer(this.regions2); - } else if (newZoom < 7) { - this._map.removeLayer(this.markers); - this._map.addLayer(this.regions1); - this._map.removeLayer(this.regions2); - } - else if (newZoom < 10) { - this._map.removeLayer(this.regions1); - this._map.addLayer(this.regions2); - this._map.removeLayer(this.markers); - } else { - if (zoomFinished) { - this._map.addLayer(this.markers); - } - this._map.removeLayer(this.regions1); - this._map.removeLayer(this.regions2); - } - }, - - onAdd: function(map) { - this._map = map; - this.markers.loadData(); - this.regions1.loadData(); - this.regions2.loadData(); - this.setLayersVisibility(); - map.on('zoomend', this.setLayersVisibility, this); - map.on('zoomanim', this.setLayersVisibility, this); - }, - - onRemove: function() { - this._map.removeLayer(this.markers); - this._map.removeLayer(this.regions1); - this._map.removeLayer(this.regions2); - this._map.off('zoomend', this.setLayersVisibility, this); - this._map.off('zoomanim', this.setLayersVisibility, this); - this._map = null; - } - } -); - - - diff --git a/src/lib/leaflet.layer.westraPasses/westraPassesMarkers.js b/src/lib/leaflet.layer.westraPasses/westraPassesMarkers.js @@ -1,11 +1,11 @@ import L from 'leaflet'; -import 'lib/leaflet.layer.canvasMarkers/canvasMarkers' -import openPopup from 'lib/popupWindow/popupWindow'; +import 'lib/leaflet.layer.canvasMarkers' +import openPopup from 'lib/popupWindow'; import escapeHtml from 'escape-html'; import {saveAs} from 'browser-filesaver'; -import iconFromBackgroundImage from 'lib/iconFromBackgroundImage/iconFromBackgroundImage'; -import {fetch} from 'lib/xhr-promise/xhr-promise'; -import {notifyXhrError} from 'lib/notifications/notifications'; +import iconFromBackgroundImage from 'lib/iconFromBackgroundImage'; +import {fetch} from 'lib/xhr-promise'; +import {notifyXhrError} from 'lib/notifications'; const westraPasesMarkers = L.Layer.CanvasMarkers.extend({ diff --git a/src/lib/leaflet.layer.yandex/yandex.js b/src/lib/leaflet.layer.yandex/index.js diff --git a/src/lib/leaflet.lineutil.simplifyLatLngs/simplify.js b/src/lib/leaflet.lineutil.simplifyLatLngs/index.js diff --git a/src/lib/leaflet.polyline-edit/edit_line.js b/src/lib/leaflet.polyline-edit/index.js diff --git a/src/lib/leaflet.polyline-measure/measured_line.js b/src/lib/leaflet.polyline-measure/index.js diff --git a/src/lib/notifications/notifications.js b/src/lib/notifications/index.js diff --git a/src/lib/popupWindow/popupWindow.js b/src/lib/popupWindow/index.js diff --git a/src/lib/xhr-promise/xhr-promise.js b/src/lib/xhr-promise/index.js