summaryrefslogtreecommitdiff
path: root/assets/static/vendor/leaflet-elevation/libs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/fullpage.css13
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.css8
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.js314
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.min.css1
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.min.js1
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/leaflet-edgescale.js636
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/leaflet-edgescale.min.js1
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/leaflet-gpxgroup.js347
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/leaflet-gpxgroup.min.js1
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/leaflet-hotline.js457
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/leaflet-hotline.min.js1
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.css42
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.js183
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.min.css1
-rw-r--r--assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.min.js1
15 files changed, 2007 insertions, 0 deletions
diff --git a/assets/static/vendor/leaflet-elevation/libs/fullpage.css b/assets/static/vendor/leaflet-elevation/libs/fullpage.css
new file mode 100644
index 0000000..c2b8539
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/fullpage.css
@@ -0,0 +1,13 @@
+html,
+body,
+.leaflet-map {
+ height: 100%;
+ width: 100%;
+ padding: 0px;
+ margin: 0px;
+}
+
+body {
+ display: flex;
+ flex-direction: column;
+}
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.css b/assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.css
new file mode 100644
index 0000000..fcf9a40
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.css
@@ -0,0 +1,8 @@
+.dist-marker {
+ font-size: 9px;
+ border: 1px solid #777;
+ border-radius: 10px;
+ text-align: center;
+ color: #000;
+ background: #fff;
+}
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.js b/assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.js
new file mode 100644
index 0000000..43beb77
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.js
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2022, GPL-3.0+ Project, Raruto
+ *
+ * This file is free software: you may copy, redistribute and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright (c) 2014- Doroszlai Attila, 2016- Phil Whitehurst
+ *
+ * Permission to use, copy, modify, and/or distribute this software
+ * for any purpose with or without fee is hereby granted, provided
+ * that the above copyright notice and this permission notice appear
+ * in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+// TODO: the "L.DistanceMarker" (canvas marker) class could be alternatively provided by "leaflet-rotate"?
+
+L.DistanceMarker = L.CircleMarker.extend({
+ _updatePath: function () {
+ let ctx = this._renderer._ctx;
+ let p = this._point;
+
+ // Calculate image direction (rotation)
+ this.options.rotation = this.options.rotation || 0;
+
+ // Draw circle marker (canvas point)
+ if (this.options.radius && this._renderer._updateCircle) {
+ this._renderer._updateCircle(this);
+ }
+
+ // Draw image over circle (distance marker)
+ if (this.options.icon && this.options.icon.url) {
+ if (!this.options.icon.element) {
+ const icon = document.createElement('img');
+ this.options.icon = L.extend({ rotate: 0, size: [40, 40], offset: { x: 0, y: 0 } }, this.options.icon);
+ this.options.icon.rotate += this.options.rotation;
+ this.options.icon.element = icon;
+ icon.src = this.options.icon.url;
+ icon.onload = () => this.redraw();
+ icon.onerror = () => this.options.icon = null;
+ } else {
+ const icon = this.options.icon;
+ let cx = p.x + icon.offset.x;
+ let cy = p.y + icon.offset.y;
+ ctx.save();
+ if (icon.rotate) {
+ ctx.translate(p.x, p.y);
+ ctx.rotate(icon.rotate);
+ cx = 0;
+ cy = 0;
+ }
+ ctx.drawImage(icon.element, cx - icon.size[0] / 2, cy - icon.size[1] / 2, icon.size[0], icon.size[1]);
+ ctx.restore();
+ }
+ }
+
+ // Add a label inside the circle (distance marker)
+ if (this.options.label) {
+ let cx = p.x, cy = p.y;
+ ctx.save();
+
+ ctx.font = this.options.font || 'normal 7pt "Helvetica Neue", Arial, Helvetica, sans-serif';
+ ctx.textAlign = "center";
+ ctx.textBaseline = "middle";
+ ctx.fillStyle = this.options.fillStyle || 'black';
+
+ // TODO rescale circle to fit text
+ // let fontSize = Number(/[0-9\.]+/.exec(ctx.font)[0]);
+ // let fontWidth = ctx.measureText(this.options.html).width;
+
+ if (this.options.rotation) {
+ ctx.translate(p.x, p.y);
+ ctx.rotate(this.options.rotation);
+ cx = 0;
+ cy = 0;
+ }
+
+ // Temporary fix to prevent stroke blurs at higher zoom levels
+ if (this._map.getZoom() > 17) {
+ ctx.fillStyle = this.options.strokeStyle || 'black';
+ }
+
+ ctx.fillText(this.options.label, cx, cy);
+
+ if (this.options.strokeStyle && this._map.getZoom() <= 17) {
+ ctx.strokeStyle = this.options.strokeStyle;
+ ctx.strokeText(this.options.label, cx, cy);
+ }
+
+ ctx.restore();
+ }
+ }
+});
+
+L.DistanceMarkers = L.LayerGroup.extend({
+ options: {
+ cssClass: 'dist-marker',
+ iconSize: [12, 12],
+ arrowSize: [10, 10],
+ arrowUrl: "data:image/svg+xml,%3Csvg transform='rotate(90)' xmlns='http://www.w3.org/2000/svg' width='560px' height='560px' viewBox='0 0 560 560'%3E%3Cpath stroke-width='35' fill='%23000' stroke='%23FFF' d='M280,40L522,525L280,420L38,525z'/%3E%3C/svg%3E",
+ offset: 1000,
+ showAll: 12,
+ textFunction: (distance, i, offset) => i,
+ distance: true,
+ direction: true,
+ },
+ initialize: function (line, map, options) {
+
+ this._layers = {};
+ this._zoomLayers = {};
+
+ options = L.setOptions(this, options);
+
+ let preferCanvas = map.options.preferCanvas;
+ let showAll = Math.min(map.getMaxZoom(), options.showAll);
+
+ // You should use "leaflet-rotate" to show rotated arrow markers (preferCanvas: false)
+ if (!preferCanvas && !map.options.rotate) {
+ console.warn('Missing dependency: "leaflet-rotate"');
+ }
+
+ // Get line coords as an array
+ let coords = typeof line.getLatLngs == 'function' ? line.getLatLngs() : line;
+
+ // Handle "MultiLineString" features
+ coords = L.LineUtil.isFlat(coords) ? [coords] : coords;
+
+ coords.forEach(latlngs => {
+ // Get accumulated line lengths as well as overall length
+ let accumulated = L.GeometryUtil.accumulatedLengths(latlngs);
+ let length = accumulated.length > 0 ? accumulated[accumulated.length - 1] : 0;
+
+ // count = Number of distance markers to be added
+ // j = Position in accumulated line length array
+ for (let i = 1, count = Math.floor(length / options.offset), j = 0; i <= count; ++i) {
+
+ let distance = options.offset * i;
+
+ // Find the first accumulated distance that is greater than the distance of this marker
+ while (j < accumulated.length - 1 && accumulated[j] < distance) ++j;
+
+ // Grab two nearest points either side marker position
+ let p1 = latlngs[j - 1];
+ let p2 = latlngs[j];
+ let m_line = L.polyline([p1, p2]);
+
+ // and create a simple line to interpolate on
+ let ratio = (distance - accumulated[j - 1]) / (accumulated[j] - accumulated[j - 1]);
+ let position = L.GeometryUtil.interpolateOnLine(map, m_line, ratio);
+ let delta = map.project(p2).subtract(map.project(p1));
+ let angle = Math.atan2(delta.y, delta.x);
+
+ // Generate distance marker label
+ let text = options.textFunction.call(this, distance, i, options.offset);
+
+ // Grouping layer of visible layers at zoom level (arrow + distance)
+ let zoom = this._minimumZoomLevelForItem(i, showAll);
+ let markers = this._zoomLayers[zoom] = this._zoomLayers[zoom] || L.layerGroup()
+
+ // create arrow markers
+ if (options.direction && ((options.distance && i % 2 == 1) || !options.distance)) {
+ if (preferCanvas) {
+ markers.addLayer(
+ new L.DistanceMarker(p1, {
+ radius: 0,
+ icon: {
+ url: options.arrowUrl, //image link
+ size: options.arrowSize, //image size ( default [40, 40] )
+ rotate: 0, //image base rotate ( default 0 )
+ offset: { x: 0, y: 0 }, //image offset ( default { x: 0, y: 0 } )
+ },
+ rotation: angle,
+ interactive: false,
+ // label: '⮞', //'➜',
+ // font: 'normal 20pt "Helvetica Neue", Arial, Helvetica, sans-serif',
+ // fillStyle: 'white',//'#3366CC',
+ // strokeStyle: 'black',
+ })
+ );
+ } else {
+ markers.addLayer(
+ L.marker(position.latLng, {
+ icon: L.icon({
+ iconUrl: options.arrowUrl,
+ iconSize: options.arrowSize,
+ }),
+ // NB the following option is added by "leaflet-rotate"
+ rotation: angle,
+ interactive: false,
+ })
+ );
+ }
+ }
+
+ // create distance markers
+ if (options.distance && i % 2 == 0) {
+ if (preferCanvas) {
+ markers.addLayer(
+ new L.DistanceMarker(position.latLng, {
+ label: text, // TODO: handle text rotation (leaflet-rotate)
+ radius: 7,
+ fillColor: '#fff',
+ fillOpacity: 1,
+ fillStyle: 'black',
+ color: '#777',
+ weight: 1,
+ interactive: false,
+ })
+ );
+ } else {
+ markers.addLayer(
+ L.marker(position.latLng, {
+ title: text,
+ icon: L.divIcon({
+ className: options.cssClass,
+ html: text,
+ iconSize: options.iconSize
+ }),
+ interactive: false,
+ })
+ );
+ }
+ }
+ }
+ });
+
+ const updateMarkerVisibility = () => {
+ let oldZoom = this._lastZoomLevel || 0;
+ let newZoom = map.getZoom();
+ if (newZoom > oldZoom) {
+ for (let i = oldZoom + 1; i <= newZoom; ++i) {
+ if (this._zoomLayers[i] !== undefined) {
+ this.addLayer(this._zoomLayers[i]);
+ }
+ }
+ } else if (newZoom < oldZoom) {
+ for (let i = oldZoom; i > newZoom; --i) {
+ if (this._zoomLayers[i] !== undefined) {
+ this.removeLayer(this._zoomLayers[i]);
+ }
+ }
+ }
+ this._lastZoomLevel = newZoom;
+ };
+ map.on('zoomend', updateMarkerVisibility);
+ updateMarkerVisibility();
+ },
+
+ _minimumZoomLevelForItem: function (i, zoom) {
+ while (i > 0 && i % 2 === 0) {
+ --zoom;
+ i = Math.floor(i / 2);
+ }
+ return zoom;
+ },
+
+});
+
+L.Polyline.include({
+
+ _originalOnAdd: L.Polyline.prototype.onAdd,
+ _originalOnRemove: L.Polyline.prototype.onRemove,
+
+ addDistanceMarkers: function () {
+ if (this._map && this._distanceMarkers) {
+ this._map.addLayer(this._distanceMarkers);
+ }
+ },
+
+ removeDistanceMarkers: function () {
+ if (this._map && this._distanceMarkers) {
+ this._map.removeLayer(this._distanceMarkers);
+ }
+ },
+
+ onAdd: function (map) {
+ this._originalOnAdd(map);
+
+ let opts = this.options.distanceMarkers || {};
+ if (this.options.distanceMarkers) {
+ this._distanceMarkers = this._distanceMarkers || new L.DistanceMarkers(this, map, opts);
+ }
+ if (opts.lazy === undefined || opts.lazy === false) {
+ this.addDistanceMarkers();
+ }
+ },
+
+ onRemove: function (map) {
+ this.removeDistanceMarkers();
+ this._originalOnRemove(map);
+ }
+
+});
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.min.css b/assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.min.css
new file mode 100644
index 0000000..473e8f3
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.min.css
@@ -0,0 +1 @@
+.dist-marker{font-size:9px;border:1px solid #777;border-radius:10px;text-align:center;color:#000;background:#fff} \ No newline at end of file
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.min.js b/assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.min.js
new file mode 100644
index 0000000..b8bc58b
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.min.js
@@ -0,0 +1 @@
+L.DistanceMarker=L.CircleMarker.extend({_updatePath:function(){let ctx=this._renderer._ctx,p=this._point;if(this.options.rotation=this.options.rotation||0,this.options.radius&&this._renderer._updateCircle&&this._renderer._updateCircle(this),this.options.icon&&this.options.icon.url)if(this.options.icon.element){const icon=this.options.icon;let cx=p.x+icon.offset.x,cy=p.y+icon.offset.y;ctx.save(),icon.rotate&&(ctx.translate(p.x,p.y),ctx.rotate(icon.rotate),cx=0,cy=0),ctx.drawImage(icon.element,cx-icon.size[0]/2,cy-icon.size[1]/2,icon.size[0],icon.size[1]),ctx.restore()}else{const icon=document.createElement("img");this.options.icon=L.extend({rotate:0,size:[40,40],offset:{x:0,y:0}},this.options.icon),this.options.icon.rotate+=this.options.rotation,this.options.icon.element=icon,icon.src=this.options.icon.url,icon.onload=()=>this.redraw(),icon.onerror=()=>this.options.icon=null}if(this.options.label){let cx=p.x,cy=p.y;ctx.save(),ctx.font=this.options.font||'normal 7pt "Helvetica Neue", Arial, Helvetica, sans-serif',ctx.textAlign="center",ctx.textBaseline="middle",ctx.fillStyle=this.options.fillStyle||"black",this.options.rotation&&(ctx.translate(p.x,p.y),ctx.rotate(this.options.rotation),cx=0,cy=0),this._map.getZoom()>17&&(ctx.fillStyle=this.options.strokeStyle||"black"),ctx.fillText(this.options.label,cx,cy),this.options.strokeStyle&&this._map.getZoom()<=17&&(ctx.strokeStyle=this.options.strokeStyle,ctx.strokeText(this.options.label,cx,cy)),ctx.restore()}}}),L.DistanceMarkers=L.LayerGroup.extend({options:{cssClass:"dist-marker",iconSize:[12,12],arrowSize:[10,10],arrowUrl:"data:image/svg+xml,%3Csvg transform='rotate(90)' xmlns='http://www.w3.org/2000/svg' width='560px' height='560px' viewBox='0 0 560 560'%3E%3Cpath stroke-width='35' fill='%23000' stroke='%23FFF' d='M280,40L522,525L280,420L38,525z'/%3E%3C/svg%3E",offset:1e3,showAll:12,textFunction:(distance,i,offset)=>i,distance:!0,direction:!0},initialize:function(line,map,options){this._layers={},this._zoomLayers={},options=L.setOptions(this,options);let preferCanvas=map.options.preferCanvas,showAll=Math.min(map.getMaxZoom(),options.showAll);preferCanvas||map.options.rotate||console.warn('Missing dependency: "leaflet-rotate"');let coords="function"==typeof line.getLatLngs?line.getLatLngs():line;coords=L.LineUtil.isFlat(coords)?[coords]:coords,coords.forEach(latlngs=>{let accumulated=L.GeometryUtil.accumulatedLengths(latlngs),length=accumulated.length>0?accumulated[accumulated.length-1]:0;for(let i=1,count=Math.floor(length/options.offset),j=0;i<=count;++i){let distance=options.offset*i;for(;j<accumulated.length-1&&accumulated[j]<distance;)++j;let p1=latlngs[j-1],p2=latlngs[j],m_line=L.polyline([p1,p2]),ratio=(distance-accumulated[j-1])/(accumulated[j]-accumulated[j-1]),position=L.GeometryUtil.interpolateOnLine(map,m_line,ratio),delta=map.project(p2).subtract(map.project(p1)),angle=Math.atan2(delta.y,delta.x),text=options.textFunction.call(this,distance,i,options.offset),zoom=this._minimumZoomLevelForItem(i,showAll),markers=this._zoomLayers[zoom]=this._zoomLayers[zoom]||L.layerGroup();options.direction&&(options.distance&&i%2==1||!options.distance)&&(preferCanvas?markers.addLayer(new L.DistanceMarker(p1,{radius:0,icon:{url:options.arrowUrl,size:options.arrowSize,rotate:0,offset:{x:0,y:0}},rotation:angle,interactive:!1})):markers.addLayer(L.marker(position.latLng,{icon:L.icon({iconUrl:options.arrowUrl,iconSize:options.arrowSize}),rotation:angle,interactive:!1}))),options.distance&&i%2==0&&(preferCanvas?markers.addLayer(new L.DistanceMarker(position.latLng,{label:text,radius:7,fillColor:"#fff",fillOpacity:1,fillStyle:"black",color:"#777",weight:1,interactive:!1})):markers.addLayer(L.marker(position.latLng,{title:text,icon:L.divIcon({className:options.cssClass,html:text,iconSize:options.iconSize}),interactive:!1})))}});const updateMarkerVisibility=()=>{let oldZoom=this._lastZoomLevel||0,newZoom=map.getZoom();if(newZoom>oldZoom)for(let i=oldZoom+1;i<=newZoom;++i)void 0!==this._zoomLayers[i]&&this.addLayer(this._zoomLayers[i]);else if(newZoom<oldZoom)for(let i=oldZoom;i>newZoom;--i)void 0!==this._zoomLayers[i]&&this.removeLayer(this._zoomLayers[i]);this._lastZoomLevel=newZoom};map.on("zoomend",updateMarkerVisibility),updateMarkerVisibility()},_minimumZoomLevelForItem:function(i,zoom){for(;i>0&&i%2==0;)--zoom,i=Math.floor(i/2);return zoom}}),L.Polyline.include({_originalOnAdd:L.Polyline.prototype.onAdd,_originalOnRemove:L.Polyline.prototype.onRemove,addDistanceMarkers:function(){this._map&&this._distanceMarkers&&this._map.addLayer(this._distanceMarkers)},removeDistanceMarkers:function(){this._map&&this._distanceMarkers&&this._map.removeLayer(this._distanceMarkers)},onAdd:function(map){this._originalOnAdd(map);let opts=this.options.distanceMarkers||{};this.options.distanceMarkers&&(this._distanceMarkers=this._distanceMarkers||new L.DistanceMarkers(this,map,opts)),void 0!==opts.lazy&&!1!==opts.lazy||this.addDistanceMarkers()},onRemove:function(map){this.removeDistanceMarkers(),this._originalOnRemove(map)}}); \ No newline at end of file
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-edgescale.js b/assets/static/vendor/leaflet-elevation/libs/leaflet-edgescale.js
new file mode 100644
index 0000000..74c672d
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-edgescale.js
@@ -0,0 +1,636 @@
+/*
+ * Copyright (c) 2023, GPL-3.0+ Project, Raruto
+ *
+ * This file is free software: you may copy, redistribute and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright (c) GPL-3.0+ Project - 2018- Dražen Tutić - https://github.com/dtutic/Leaflet.EdgeScaleBar
+ * Copyright (c) MIT License (MIT) - 2015- Xisco Guaita - https://github.com/xguaita/Leaflet.MapCenterCoord
+ */
+
+/**
+ * Original source: https://github.com/xguaita/Leaflet.MapCenterCoord
+ */
+L.Control.EdgeScale = L.Control.extend({
+
+ // Defaults
+ options: {
+ position: 'bottomleft',
+ icon: true,
+ coords: true,
+ bar: true,
+ onMove: true,
+ template: '{y} | {x}', // https://en.wikipedia.org/wiki/ISO_6709
+ projected: false,
+ formatProjected: '#.##0,000',
+ latlngFormat: 'DD', // DD, DM, DMS
+ latlngDesignators: true,
+ latLngFormatter: undefined,
+ iconStyle: {
+ background: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' viewBox='0 0 100 100'%3E%3Cg stroke='%23fff'%3E%3Ccircle cx='50' cy='50.2' r='3.9' stroke-width='2' /%3E%3Cpath stroke-width='3' d='M5 54h32a4 4 0 1 0 0-8H5a4 4 0 1 0 0 8z M54 5a4 4 0 1 0-8 0v32a4 4 0 1 0 8 0V5z M99 50c0-2-2-4-4-4H63a4 4 0 1 0 0 8h32c2 0 4-1 4-4zM46 95a4 4 0 1 0 8 0V64a4 4 0 1 0-8 0v31z'/%3E%3C/g%3E%3C/svg%3E%0A")`,
+ width: '24px',
+ height: '24px',
+ left: 'calc(50% - 12px)',
+ top: 'calc(50% - 12px)',
+ content: '',
+ display: 'block',
+ position: 'absolute',
+ zIndex: 999,
+ pointerEvents: 'none',
+ },
+ containerStyle: {
+ backgroundColor: 'rgba(255, 255, 255, 0.7)',
+ boxShadow: '0 0 5px #bbb',
+ borderRadius: '3px',
+ padding: '3px 2px',
+ color: '#333',
+ font: '11px/1.5 Consolas, monaco, monospace',
+ writingMode: 'vertical-lr',
+ },
+ },
+
+ initialize: function(options) {
+ L.setOptions(this, options);
+ },
+
+ onAdd: function (map) {
+
+ if (this.options.bar) {
+ this._scaleBar = (new L.Control.EdgeScale.Layer(true === this.options.bar ? {} : this.options.bar)).addTo(map);
+ }
+
+ // create a DOM element and put it into overlayPane
+ if (this.options.icon) {
+ this._icon = L.DomUtil.create('div', 'leaflet-crosshair');
+ Object.assign(this._icon.style, this.options.iconStyle);
+ map.getContainer().insertBefore(this._icon, map.getContainer().firstChild);
+ }
+
+ // Control container
+ this._container = L.DomUtil.create('div', 'leaflet-control-mapcentercoord');
+
+ Object.assign(this._container.style, this.options.containerStyle);
+
+ if (!this.options.coords) {
+ this._container.style.display = 'none';
+ }
+
+ L.DomEvent.disableClickPropagation(this._container);
+
+ this._container.innerHTML = this._getMapCenterCoord();
+
+ // Add events listeners for updating coordinates & icon's position
+ map.on('move', this._onMapMove, this);
+ map.on('moveend', this._onMapMove, this);
+
+ return this._container;
+ },
+
+ onRemove: function (map) {
+ if (this.options.bar) {
+ this._scaleBar.remove();
+ }
+
+ // remove icon's DOM elements and listeners
+ if (this.options.icon) {
+ map.getContainer().removeChild(this._icon);
+ }
+ map.off('move', this._onMapMove, this);
+ map.off('moveend', this._onMapMove, this);
+ },
+
+ // update coordinates
+ _onMapMove: function (e) {
+ if (this.options.onMove || 'moveend' === e.type) {
+ this._container.innerHTML = this._getMapCenterCoord();
+ }
+ },
+
+ _getMapCenterCoord: function () {
+ const center = this._map.getCenter();
+ return this.options.projected
+ ? this._getProjectedCoord(this._map.options.crs.project(center))
+ : this._getLatLngCoord(center);
+ },
+
+ _getProjectedCoord: function (center) {
+ return L.Util.template(
+ this.options.template,
+ {
+ x: this._format(this.options.formatProjected, center.x),
+ y: this._format(this.options.formatProjected, center.y)
+ }
+ );
+ },
+
+ _getLatLngCoord: function (latLng) {
+
+ const { latLngFormatter, latlngFormat, latlngDesignators: designators } = this.options;
+
+ if (undefined !== latLngFormatter ) {
+ return latLngFormatter(latLng.lat, latLng.lng);
+ }
+
+ let lat, lng, deg, min;
+
+ // make a copy of center so we aren't affecting leaflet's internal state
+ let center = {
+ lat: latLng.lat,
+ lng: latLng.lng,
+ lng_neg: latLng.lng < 0,
+ lat_neg: latLng.lat < 0,
+ };
+
+ // 180 degrees & negative
+ if (center.lng < 0) {
+ center.lng = Math.abs(center.lng);
+ }
+ if (center.lng > 180) {
+ center.lng = 360 - center.lng;
+ center.lng_neg = !center.lng_neg;
+ }
+ if (center.lat < 0) {
+ center.lat = Math.abs(center.lat);
+ }
+
+ // format
+ if ('DM' === latlngFormat) {
+ deg = parseInt(center.lng);
+ lng = deg + 'º ' + this._format('00.000', (center.lng - deg) * 60) + "'";
+ deg = parseInt(center.lat);
+ lat = deg + 'º ' + this._format('00.000', (center.lat - deg) * 60) + "'";
+ } else if ('DMS' === latlngFormat) {
+ deg = parseInt(center.lng);
+ min = (center.lng - deg) * 60;
+ lng = deg + 'º ' + this._format('00', parseInt(min)) + "' " + this._format('00.0', (min - parseInt(min)) * 60) + "''";
+ deg = parseInt(center.lat);
+ min = (center.lat - deg) * 60;
+ lat = deg + 'º ' + this._format('00', parseInt(min)) + "' " + this._format('00.0', (min - parseInt(min)) * 60) + "''";
+ } else { // 'DD'
+ lng = this._format('#0.00000', center.lng) + 'º';
+ lat = this._format('##0.00000', center.lat) + 'º';
+ }
+
+ return L.Util.template(this.options.template, {
+ x: (!designators && center.lng_neg ? '-' : '') + lng + (designators ? (center.lng_neg ? ' W' : ' E') : ''),
+ y: (!designators && center.lat_neg ? '-' : '') + lat + (designators ? (center.lat_neg ? ' S' : ' N') : '')
+ });
+ },
+
+ /**
+ * IntegraXor Web SCADA - JavaScript Number Formatter
+ *
+ * @see https://code.google.com/p/javascript-number-formatter
+ * @authors KPL, KHL
+ */
+ _format: function (m, v) {
+ if (!m || isNaN(+v)) {
+ return v; // return as it is.
+ }
+
+ v = m.charAt(0) == '-' ? -v : +v; // convert any string to number according to formation sign.
+ let isNegative = v < 0 ? v = -v : 0; // process only abs(), and turn on flag.
+
+ let result = m.match(/[^\d\-\+#]/g); // search for separator for grp & decimal, anything not digit, not +/- sign, not #.
+ let Decimal = (result && result[result.length - 1]) || '.'; // treat the right most symbol as decimal
+ let Group = (result && result[1] && result[0]) || ','; // treat the left most symbol as group separator
+
+ m = m.split(Decimal); // split the decimal for the format string if any.
+ v = v.toFixed(m[1] && m[1].length); // Fix the decimal first, toFixed will auto fill trailing zero.
+ v = +(v) + ''; // convert number to string to trim off *all* trailing decimal zero(es)
+
+ let pos_trail_zero = m[1] && m[1].lastIndexOf('0'); // fill back any trailing zero according to format (look for last zero in format)
+ let part = v.split('.');
+
+ if (!part[1] || part[1] && part[1].length <= pos_trail_zero) { // integer will get !part[1]
+ v = (+v).toFixed(pos_trail_zero + 1);
+ }
+ let szSep = m[0].split(Group); // look for separator
+ m[0] = szSep.join(''); // join back without separator for counting the pos of any leading 0.
+
+ let pos_lead_zero = m[0] && m[0].indexOf('0');
+ if (pos_lead_zero > -1) {
+ while (part[0].length < (m[0].length - pos_lead_zero)) {
+ part[0] = '0' + part[0];
+ }
+ } else if (+part[0] == 0) {
+ part[0] = '';
+ }
+ v = v.split('.');
+ v[0] = part[0];
+
+ var pos_separator = (szSep[1] && szSep[szSep.length - 1].length); // process the first group separator from decimal (.) only, the rest ignore. Get the length of the last slice of split result.
+ if (pos_separator) {
+ let integer = v[0];
+ let str = '';
+ let offset = integer.length % pos_separator;
+ for (let i = 0, l = integer.length; i < l; i++) {
+ str += integer.charAt(i); // ie6 only support charAt for sz.
+ if (
+ !((i - offset + 1) % pos_separator) &&
+ i < l - pos_separator // -pos_separator so that won't trail separator on full length
+ ) {
+ str += Group;
+ }
+ }
+ v[0] = str;
+ }
+
+ v[1] = (m[1] && v[1]) ? Decimal + v[1] : "";
+ return (isNegative ? '-' : '') + v[0] + v[1]; // put back any negation and combine integer and fraction.
+ }
+
+});
+
+/**
+ * Original Source: https://github.com/dtutic/Leaflet.EdgeScaleBar
+ *
+ * Draws the metric scale bars in Web Mercator map along top and right edges.
+ * Authors: Dražen Tutić (dtutic@geof.hr), Ana Kuveždić Divjak (akuvezdic@geof.hr)
+ * University of Zagreb, Faculty of Geodesy, GEOF-OSGL Lab
+ * Inspired by LatLonGraticule Leaflet plugin by: lanwei@cloudybay.com.tw
+ */
+L.Control.EdgeScale.Layer = L.Layer.extend({
+
+ includes: L.Evented ? L.Evented.prototype : L.Mixin.Events,
+
+ options: {
+ opacity: 1,
+ weight: 0.8,
+ gradient: {
+ size: 10,
+ opacity: 0.5,
+ },
+ color: '#000',
+ font: '11px Arial',
+ zoomInterval: [
+ {start: 0, end: 2, interval: 5000000},
+ {start: 3, end: 3, interval: 2000000},
+ {start: 4, end: 4, interval: 1000000},
+ {start: 5, end: 5, interval: 500000},
+ {start: 6, end: 7, interval: 200000},
+ {start: 8, end: 8, interval: 100000},
+ {start: 9, end: 9, interval: 50000},
+ {start: 10, end: 10, interval: 20000},
+ {start: 11, end: 11, interval: 10000},
+ {start: 12, end: 12, interval: 5000},
+ {start: 13, end: 13, interval: 2000},
+ {start: 14, end: 14, interval: 1000},
+ {start: 15, end: 15, interval: 500},
+ {start: 16, end: 16, interval: 200},
+ {start: 17, end: 17, interval: 100},
+ {start: 18, end: 18, interval: 50},
+ {start: 19, end: 19, interval: 20},
+ {start: 20, end: 20, interval: 10}
+ ],
+ pane: 'edgescalePane'
+ },
+
+ initialize: function (options) {
+
+ L.setOptions(this, options);
+
+ // Constants of the WGS84 ellipsoid needed to calculate meridian length or latitute
+ const a = this._a = 6378137.0;
+ const b = this._b = 6356752.3142;
+ const n = this._n = (a - b)/(a + b);
+ const a2 = a * a;
+ const b2 = b * b;
+ const n2 = n * n;
+ const n3 = n2 * n;
+ const n4 = n3 * n;
+ const n5 = n4 * n;
+ this._A = a * (1.0 - n) * (1.0 - n2) * (1.0 + 9.0/4.0 * n2 + 225.0/64.0 * n4);
+ this._e2 = (a2 - b2) / a2;
+ this._ic1 = 1.5 * n - 29.0/12.0 * n3 + 553.0/80.0 * n5;
+ this._ic2 = 21.0/8.0 * n2 - 1537.0/128.0 * n4;
+ this._ic3 = 151.0/24.0 * n3 - 32373.0/640.0 * n5;
+ this._ic4 = 1097.0/64.0 * n4;
+ this._ic5 = 8011.0/150.0 * n5;
+ this._c1 = -1.5 * n + 31.0/24.0 * n3 - 669.0/640.0 * n5;
+ this._c2 = 15.0/18.0 * n2 - 435.0/128.0 * n4;
+ this._c3 = -35.0/12.0 * n3 + 651.0/80.0 * n5;
+ this._c4 = 315.0/64.0 * n4;
+ this._c5 = -693.0/80.0 * n5;
+
+ // Latitude limit of the Web Mercator projection
+ this._LIMIT_PHI = 1.484419982;
+ },
+
+ onAdd: function (map) {
+ this._map = map;
+
+ let pane = map.getPane(this.options.pane);
+ if (!pane) {
+ pane = this._pane = map.createPane('edgescalePane', map.getPane('norotatePane') || map.getPane('mapPane'));
+ pane.style.zIndex = 625; // This pane is above markers but below popups.
+ pane.style.pointerEvents = 'none';
+ }
+
+ this._pane = pane;
+
+ // if (this._renderer) this._renderer.remove()
+ // this._renderer = L.canvas({ pane: "edgescalePane" }).addTo(this._map); // default leaflet svg renderer
+
+ if (!this._canvas) {
+ this._initCanvas();
+ }
+
+ this._pane.appendChild(this._canvas);
+
+ map.on('viewreset', this._reset, this);
+ map.on('move', this._reset, this);
+ map.on('moveend', this._reset, this);
+ map.on('rotate', this._reset, this);
+
+ this._reset();
+ },
+
+ onRemove: function (map) {
+ this._pane.removeChild(this._canvas);
+
+ map.off('viewreset', this._reset, this);
+ map.off('move', this._reset, this);
+ map.off('moveend', this._reset, this);
+ },
+
+ addTo: function (map) {
+ map.addLayer(this);
+ return this;
+ },
+
+ setOpacity: function (opacity) {
+ this.options.opacity = opacity;
+ L.DomUtil.setOpacity(this._canvas, this.options.opacity);
+ return this;
+ },
+
+ bringToFront: function () {
+ if (this._canvas) {
+ this._pane.appendChild(this._canvas);
+ }
+ return this;
+ },
+
+ bringToBack: function () {
+ if (this._canvas) {
+ this._pane.insertBefore(this._canvas, pane.firstChild);
+ }
+ return this;
+ },
+
+ _initCanvas: function () {
+ this._canvas = L.DomUtil.create('canvas', '');
+ this._ctx = this._canvas.getContext('2d');
+
+ this.setOpacity();
+
+ L.extend(this._canvas, {
+ onselectstart: L.Util.falseFn,
+ onmousemove: L.Util.falseFn,
+ onload: L.bind(this._onCanvasLoad, this)
+ });
+ },
+
+ _reset: function () {
+ var canvas = this._canvas,
+ size = this._map.getSize();
+
+ this._setCanvasPosition();
+
+ canvas.width = size.x;
+ canvas.height = size.y;
+ canvas.style.width = size.x + 'px';
+ canvas.style.height = size.y + 'px';
+
+ /**
+ * @TODO add support for "leaflet-rotate"
+ */
+ if (this._map._bearing) {
+ return;
+ }
+
+ const { gradient } = this.options;
+
+ // horizontal gradient
+ if (!this._hor_gradient) {
+ this._hor_gradient = this._ctx.createLinearGradient(0, 0, 0, gradient.size);
+ this._hor_gradient.addColorStop(0,"rgba(255, 255, 255, " + gradient.opacity + ")");
+ this._hor_gradient.addColorStop(1,"rgba(255, 255, 255, 0)");
+ }
+ this._ctx.fillStyle = this._hor_gradient;
+ this._ctx.fillRect(0, 0, size.x, gradient.size);
+
+ // vertical gradient
+ if (!this._vert_gradient) {
+ this._vert_gradient = this._ctx.createLinearGradient(0, 0, gradient.size, 0);
+ this._vert_gradient.addColorStop(0,"rgba(255, 255, 255, " + gradient.opacity + ")");
+ this._vert_gradient.addColorStop(1,"rgba(255, 255, 255, 0)");
+ }
+ this._ctx.fillStyle = this._vert_gradient;
+ this._ctx.fillRect(0, 0, gradient.size, size.y);
+
+ this._ctx.beginPath();
+ this._ctx.moveTo(0,0);
+ this._ctx.lineTo(size.x,0);
+ this._ctx.lineTo(size.x,size.y);
+ this._ctx.stroke();
+
+ this._calcInterval();
+ this._draw();
+ },
+
+ _onCanvasLoad: function () {
+ this.fire('load');
+ },
+
+ _calcInterval: function() {
+ const { zoomInterval } = this.options;
+ const zoom = this._map.getZoom();
+
+ if (undefined !== zoomInterval) {
+ // Manually set scale using a custom this.options.zoomInterval object
+ for (const idx in zoomInterval) {
+ const dict = zoomInterval[idx];
+ if (dict.start <= zoom && dict.end && dict.end >= zoom) {
+ this._interval = dict.interval;
+ break;
+ }
+ }
+ } else {
+ // Autamatically get current scale using L.Control.Scale
+ // Source: https://gis.stackexchange.com/a/198444
+ this._interval = L.Control.Scale.prototype._getRoundNum(
+ this._map
+ .containerPointToLatLng([0, this._map.getSize().y / 2 ])
+ .distanceTo(
+ this._map.containerPointToLatLng([L.Control.Scale.prototype.options.maxWidth, this._map.getSize().y / 2 ]
+ )
+ )
+ );
+ }
+
+ this._currZoom = zoom;
+
+ },
+
+ _draw: function() {
+ this._ctx.strokeStyle = this.options.color;
+ this._create_lat_ticks();
+ this._create_lon_ticks();
+
+ this._ctx.fillStyle = this.options.color;
+ this._ctx.font = this.options.font;
+
+ const size = this._map.getSize();
+ const text = this._interval >= 1000 ? (this._interval / 1000 + ' km') : this._interval + ' m';
+
+ this._ctx.textAlign = 'left';
+ this._ctx.textBaseline = 'middle';
+ this._ctx.fillText(text, +12, size.y / 2);
+
+ this._ctx.textAlign = 'center';
+ this._ctx.textBaseline = 'top';
+ this._ctx.fillText(text, size.x / 2, 12);
+ },
+
+ _create_lat_ticks: function() {
+ const { weight } = this.options;
+ const size = this._map.getSize();
+ const to_rad = Math.PI/180.0;
+ const center = this._merLength(this._map.containerPointToLatLng(L.point(0, size.y / 2)).lat * to_rad);
+ const top = this._merLength(this._map.containerPointToLatLng(L.point(0,0)).lat * to_rad);
+ const bottom = this._merLength(this._map.containerPointToLatLng(L.point(0, size.y)).lat * to_rad);
+
+ // draw major ticks
+ for (let i = center + this._interval / 2; i < top; i = i + this._interval) {
+ const phi = this._invmerLength(i);
+ if ((phi < this._LIMIT_PHI) && (phi > -this._LIMIT_PHI)) {
+ this._draw_lat_tick(phi, 10, weight * 1.5);
+ }
+ }
+ for (let i = center - this._interval / 2; i > bottom; i = i - this._interval) {
+ const phi = this._invmerLength(i);
+ if ((phi > -this._LIMIT_PHI) && (phi < this._LIMIT_PHI)) {
+ this._draw_lat_tick(phi, 10, weight * 1.5);
+ }
+ }
+
+ // draw minor ticks
+ for (let i = center; i < top; i = i + this._interval / 10.0) {
+ const phi = this._invmerLength(i);
+ if ((phi < this._LIMIT_PHI) && (phi > -this._LIMIT_PHI)) {
+ this._draw_lat_tick(phi, 4, weight);
+ }
+ }
+ for (let i = center - this._interval / 10; i > bottom; i = i - this._interval / 10.0) {
+ const phi = this._invmerLength(i);
+ if ((phi > -this._LIMIT_PHI) && (phi < this._LIMIT_PHI)) {
+ this._draw_lat_tick(phi, 4, weight);
+ }
+ }
+ },
+
+ _create_lon_ticks: function() {
+ const { weight } = this.options;
+ const size = this._map.getSize();
+ const to_rad = Math.PI/180.0;
+ const to_deg = 180.0/Math.PI;
+ const center = this._map.containerPointToLatLng(L.point(size.x / 2, 0));
+ const left = this._map.containerPointToLatLng(L.point(0, 0));
+ const right = this._map.containerPointToLatLng(L.point(size.x, 0));
+ const sinPhi2 = Math.pow(Math.sin(center.lat * to_rad), 2);
+ const N = this._a / Math.sqrt(1.0 - this._e2 * sinPhi2);
+ const dl = this._interval / (N * Math.cos(center.lat * to_rad)) * to_deg;
+
+ // draw major ticks
+ for (let i = center.lng + dl / 2; i < right.lng; i = i + dl) this._draw_lon_tick(i, 10, weight * 1.5);
+ for (let i = center.lng - dl / 2; i > left.lng; i = i - dl) this._draw_lon_tick(i, 10, weight * 1.5);
+
+ // draw minor ticks
+ for (let i = center.lng; i < right.lng; i = i + dl / 10) this._draw_lon_tick(i, 4, weight);
+ for (let i = center.lng - dl / 10; i > left.lng; i = i - dl / 10) this._draw_lon_tick(i, 4, weight);
+ },
+
+ _setCanvasPosition: function() {
+ let lt = this._map.containerPointToLayerPoint([0, 0]);
+ /**
+ * @TODO add support for "leaflet-rotate"
+ */
+ if (this._map._bearing) {
+ lt = this._map.rotatedPointToMapPanePoint(
+ this._map.containerPointToLayerPoint(L.point(this._map._container.getBoundingClientRect()))
+ );
+ }
+ L.DomUtil.setPosition(this._canvas, lt);
+ },
+
+ _latLngToCanvasPoint: function (latlng) {
+ return L.point(
+ this._map
+ .project(L.latLng(latlng))
+ ._subtract(this._map.getPixelOrigin())
+ ).add(this._map._getMapPanePos());
+ },
+
+ _draw_lat_tick: function (phi, lenght, weight) {
+ const to_deg = 180.0/Math.PI;
+ const size = this._map.getSize();
+ const y = this._latLngToCanvasPoint(L.latLng(phi * to_deg, 0.0)).y;
+ this._ctx.lineWidth = weight;
+ this._ctx.beginPath();
+ this._ctx.moveTo(0, y);
+ this._ctx.lineTo(+ lenght, y);
+ this._ctx.stroke();
+ },
+
+ _draw_lon_tick: function(lam, lenght, weight) {
+ const x = this._latLngToCanvasPoint(L.latLng(0.0, lam)).x;
+ this._ctx.lineWidth = weight;
+ this._ctx.beginPath();
+ this._ctx.moveTo(x, 0);
+ this._ctx.lineTo(x, lenght);
+ this._ctx.stroke();
+ },
+
+ _merLength: function(phi) {
+ const cos2 = Math.cos(2.0 * phi);
+ const sin2 = Math.sin(2.0 * phi);
+ return this._A * (phi + sin2 * (this._c1 + (this._c2 + (this._c3 + (this._c4 + this._c5 * cos2) * cos2) *cos2) * cos2));
+ },
+
+ _invmerLength: function(s) {
+ const psi = s/this._A;
+ const cos2 = Math.cos(2.0 * psi);
+ const sin2 = Math.sin(2.0 * psi);
+ return psi + sin2 * (this._ic1 + (this._ic2 + (this._ic3 + (this._ic4 + this._ic5 * cos2) * cos2) * cos2) * cos2);
+ },
+});
+
+L.control.edgeScale = function (options) {
+ return new L.Control.EdgeScale(options);
+};
+
+L.Map.mergeOptions({
+ edgeScaleControl: false
+});
+
+L.Map.addInitHook(function () {
+ if (this.options.edgeScaleControl) {
+ this.edgeScaleControl = new L.Control.EdgeScale();
+ this.addControl(this.edgeScaleControl);
+ }
+}); \ No newline at end of file
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-edgescale.min.js b/assets/static/vendor/leaflet-elevation/libs/leaflet-edgescale.min.js
new file mode 100644
index 0000000..81237d5
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-edgescale.min.js
@@ -0,0 +1 @@
+L.Control.EdgeScale=L.Control.extend({options:{position:"bottomleft",icon:!0,coords:!0,bar:!0,onMove:!0,template:"{y} | {x}",projected:!1,formatProjected:"#.##0,000",latlngFormat:"DD",latlngDesignators:!0,latLngFormatter:void 0,iconStyle:{background:"url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' viewBox='0 0 100 100'%3E%3Cg stroke='%23fff'%3E%3Ccircle cx='50' cy='50.2' r='3.9' stroke-width='2' /%3E%3Cpath stroke-width='3' d='M5 54h32a4 4 0 1 0 0-8H5a4 4 0 1 0 0 8z M54 5a4 4 0 1 0-8 0v32a4 4 0 1 0 8 0V5z M99 50c0-2-2-4-4-4H63a4 4 0 1 0 0 8h32c2 0 4-1 4-4zM46 95a4 4 0 1 0 8 0V64a4 4 0 1 0-8 0v31z'/%3E%3C/g%3E%3C/svg%3E%0A\")",width:"24px",height:"24px",left:"calc(50% - 12px)",top:"calc(50% - 12px)",content:"",display:"block",position:"absolute",zIndex:999,pointerEvents:"none"},containerStyle:{backgroundColor:"rgba(255, 255, 255, 0.7)",boxShadow:"0 0 5px #bbb",borderRadius:"3px",padding:"3px 2px",color:"#333",font:"11px/1.5 Consolas, monaco, monospace",writingMode:"vertical-lr"}},initialize:function(options){L.setOptions(this,options)},onAdd:function(map){return this.options.bar&&(this._scaleBar=new L.Control.EdgeScale.Layer(!0===this.options.bar?{}:this.options.bar).addTo(map)),this.options.icon&&(this._icon=L.DomUtil.create("div","leaflet-crosshair"),Object.assign(this._icon.style,this.options.iconStyle),map.getContainer().insertBefore(this._icon,map.getContainer().firstChild)),this._container=L.DomUtil.create("div","leaflet-control-mapcentercoord"),Object.assign(this._container.style,this.options.containerStyle),this.options.coords||(this._container.style.display="none"),L.DomEvent.disableClickPropagation(this._container),this._container.innerHTML=this._getMapCenterCoord(),map.on("move",this._onMapMove,this),map.on("moveend",this._onMapMove,this),this._container},onRemove:function(map){this.options.bar&&this._scaleBar.remove(),this.options.icon&&map.getContainer().removeChild(this._icon),map.off("move",this._onMapMove,this),map.off("moveend",this._onMapMove,this)},_onMapMove:function(e){(this.options.onMove||"moveend"===e.type)&&(this._container.innerHTML=this._getMapCenterCoord())},_getMapCenterCoord:function(){const center=this._map.getCenter();return this.options.projected?this._getProjectedCoord(this._map.options.crs.project(center)):this._getLatLngCoord(center)},_getProjectedCoord:function(center){return L.Util.template(this.options.template,{x:this._format(this.options.formatProjected,center.x),y:this._format(this.options.formatProjected,center.y)})},_getLatLngCoord:function(latLng){const{latLngFormatter:latLngFormatter,latlngFormat:latlngFormat,latlngDesignators:designators}=this.options;if(void 0!==latLngFormatter)return latLngFormatter(latLng.lat,latLng.lng);let lat,lng,deg,min,center={lat:latLng.lat,lng:latLng.lng,lng_neg:latLng.lng<0,lat_neg:latLng.lat<0};return center.lng<0&&(center.lng=Math.abs(center.lng)),center.lng>180&&(center.lng=360-center.lng,center.lng_neg=!center.lng_neg),center.lat<0&&(center.lat=Math.abs(center.lat)),"DM"===latlngFormat?(deg=parseInt(center.lng),lng=deg+"º "+this._format("00.000",60*(center.lng-deg))+"'",deg=parseInt(center.lat),lat=deg+"º "+this._format("00.000",60*(center.lat-deg))+"'"):"DMS"===latlngFormat?(deg=parseInt(center.lng),min=60*(center.lng-deg),lng=deg+"º "+this._format("00",parseInt(min))+"' "+this._format("00.0",60*(min-parseInt(min)))+"''",deg=parseInt(center.lat),min=60*(center.lat-deg),lat=deg+"º "+this._format("00",parseInt(min))+"' "+this._format("00.0",60*(min-parseInt(min)))+"''"):(lng=this._format("#0.00000",center.lng)+"º",lat=this._format("##0.00000",center.lat)+"º"),L.Util.template(this.options.template,{x:(!designators&&center.lng_neg?"-":"")+lng+(designators?center.lng_neg?" W":" E":""),y:(!designators&&center.lat_neg?"-":"")+lat+(designators?center.lat_neg?" S":" N":"")})},_format:function(m,v){if(!m||isNaN(+v))return v;let isNegative=(v="-"==m.charAt(0)?-v:+v)<0?v=-v:0,result=m.match(/[^\d\-\+#]/g),Decimal=result&&result[result.length-1]||".",Group=result&&result[1]&&result[0]||",";m=m.split(Decimal),v=+(v=v.toFixed(m[1]&&m[1].length))+"";let pos_trail_zero=m[1]&&m[1].lastIndexOf("0"),part=v.split(".");(!part[1]||part[1]&&part[1].length<=pos_trail_zero)&&(v=(+v).toFixed(pos_trail_zero+1));let szSep=m[0].split(Group);m[0]=szSep.join("");let pos_lead_zero=m[0]&&m[0].indexOf("0");if(pos_lead_zero>-1)for(;part[0].length<m[0].length-pos_lead_zero;)part[0]="0"+part[0];else 0==+part[0]&&(part[0]="");(v=v.split("."))[0]=part[0];var pos_separator=szSep[1]&&szSep[szSep.length-1].length;if(pos_separator){let integer=v[0],str="",offset=integer.length%pos_separator;for(let i=0,l=integer.length;i<l;i++)str+=integer.charAt(i),!((i-offset+1)%pos_separator)&&i<l-pos_separator&&(str+=Group);v[0]=str}return v[1]=m[1]&&v[1]?Decimal+v[1]:"",(isNegative?"-":"")+v[0]+v[1]}}),L.Control.EdgeScale.Layer=L.Layer.extend({includes:L.Evented?L.Evented.prototype:L.Mixin.Events,options:{opacity:1,weight:.8,gradient:{size:10,opacity:.5},color:"#000",font:"11px Arial",zoomInterval:[{start:0,end:2,interval:5e6},{start:3,end:3,interval:2e6},{start:4,end:4,interval:1e6},{start:5,end:5,interval:5e5},{start:6,end:7,interval:2e5},{start:8,end:8,interval:1e5},{start:9,end:9,interval:5e4},{start:10,end:10,interval:2e4},{start:11,end:11,interval:1e4},{start:12,end:12,interval:5e3},{start:13,end:13,interval:2e3},{start:14,end:14,interval:1e3},{start:15,end:15,interval:500},{start:16,end:16,interval:200},{start:17,end:17,interval:100},{start:18,end:18,interval:50},{start:19,end:19,interval:20},{start:20,end:20,interval:10}],pane:"edgescalePane"},initialize:function(options){L.setOptions(this,options);const a=this._a=6378137,b=this._b=6356752.3142,n=this._n=(a-b)/(a+b),a2=a*a,b2=b*b,n2=n*n,n3=n2*n,n4=n3*n,n5=n4*n;this._A=a*(1-n)*(1-n2)*(1+9/4*n2+225/64*n4),this._e2=(a2-b2)/a2,this._ic1=1.5*n-29/12*n3+6.9125*n5,this._ic2=21/8*n2-1537/128*n4,this._ic3=151/24*n3-32373/640*n5,this._ic4=1097/64*n4,this._ic5=8011/150*n5,this._c1=-1.5*n+31/24*n3-669/640*n5,this._c2=15/18*n2-435/128*n4,this._c3=-35/12*n3+8.1375*n5,this._c4=315/64*n4,this._c5=-8.6625*n5,this._LIMIT_PHI=1.484419982},onAdd:function(map){this._map=map;let pane=map.getPane(this.options.pane);pane||(pane=this._pane=map.createPane("edgescalePane",map.getPane("norotatePane")||map.getPane("mapPane")),pane.style.zIndex=625,pane.style.pointerEvents="none"),this._pane=pane,this._canvas||this._initCanvas(),this._pane.appendChild(this._canvas),map.on("viewreset",this._reset,this),map.on("move",this._reset,this),map.on("moveend",this._reset,this),map.on("rotate",this._reset,this),this._reset()},onRemove:function(map){this._pane.removeChild(this._canvas),map.off("viewreset",this._reset,this),map.off("move",this._reset,this),map.off("moveend",this._reset,this)},addTo:function(map){return map.addLayer(this),this},setOpacity:function(opacity){return this.options.opacity=opacity,L.DomUtil.setOpacity(this._canvas,this.options.opacity),this},bringToFront:function(){return this._canvas&&this._pane.appendChild(this._canvas),this},bringToBack:function(){return this._canvas&&this._pane.insertBefore(this._canvas,pane.firstChild),this},_initCanvas:function(){this._canvas=L.DomUtil.create("canvas",""),this._ctx=this._canvas.getContext("2d"),this.setOpacity(),L.extend(this._canvas,{onselectstart:L.Util.falseFn,onmousemove:L.Util.falseFn,onload:L.bind(this._onCanvasLoad,this)})},_reset:function(){var canvas=this._canvas,size=this._map.getSize();if(this._setCanvasPosition(),canvas.width=size.x,canvas.height=size.y,canvas.style.width=size.x+"px",canvas.style.height=size.y+"px",this._map._bearing)return;const{gradient:gradient}=this.options;this._hor_gradient||(this._hor_gradient=this._ctx.createLinearGradient(0,0,0,gradient.size),this._hor_gradient.addColorStop(0,"rgba(255, 255, 255, "+gradient.opacity+")"),this._hor_gradient.addColorStop(1,"rgba(255, 255, 255, 0)")),this._ctx.fillStyle=this._hor_gradient,this._ctx.fillRect(0,0,size.x,gradient.size),this._vert_gradient||(this._vert_gradient=this._ctx.createLinearGradient(0,0,gradient.size,0),this._vert_gradient.addColorStop(0,"rgba(255, 255, 255, "+gradient.opacity+")"),this._vert_gradient.addColorStop(1,"rgba(255, 255, 255, 0)")),this._ctx.fillStyle=this._vert_gradient,this._ctx.fillRect(0,0,gradient.size,size.y),this._ctx.beginPath(),this._ctx.moveTo(0,0),this._ctx.lineTo(size.x,0),this._ctx.lineTo(size.x,size.y),this._ctx.stroke(),this._calcInterval(),this._draw()},_onCanvasLoad:function(){this.fire("load")},_calcInterval:function(){const{zoomInterval:zoomInterval}=this.options,zoom=this._map.getZoom();if(void 0!==zoomInterval)for(const idx in zoomInterval){const dict=zoomInterval[idx];if(dict.start<=zoom&&dict.end&&dict.end>=zoom){this._interval=dict.interval;break}}else this._interval=L.Control.Scale.prototype._getRoundNum(this._map.containerPointToLatLng([0,this._map.getSize().y/2]).distanceTo(this._map.containerPointToLatLng([L.Control.Scale.prototype.options.maxWidth,this._map.getSize().y/2])));this._currZoom=zoom},_draw:function(){this._ctx.strokeStyle=this.options.color,this._create_lat_ticks(),this._create_lon_ticks(),this._ctx.fillStyle=this.options.color,this._ctx.font=this.options.font;const size=this._map.getSize(),text=this._interval>=1e3?this._interval/1e3+" km":this._interval+" m";this._ctx.textAlign="left",this._ctx.textBaseline="middle",this._ctx.fillText(text,12,size.y/2),this._ctx.textAlign="center",this._ctx.textBaseline="top",this._ctx.fillText(text,size.x/2,12)},_create_lat_ticks:function(){const{weight:weight}=this.options,size=this._map.getSize(),to_rad=Math.PI/180,center=this._merLength(this._map.containerPointToLatLng(L.point(0,size.y/2)).lat*to_rad),top=this._merLength(this._map.containerPointToLatLng(L.point(0,0)).lat*to_rad),bottom=this._merLength(this._map.containerPointToLatLng(L.point(0,size.y)).lat*to_rad);for(let i=center+this._interval/2;i<top;i+=this._interval){const phi=this._invmerLength(i);phi<this._LIMIT_PHI&&phi>-this._LIMIT_PHI&&this._draw_lat_tick(phi,10,1.5*weight)}for(let i=center-this._interval/2;i>bottom;i-=this._interval){const phi=this._invmerLength(i);phi>-this._LIMIT_PHI&&phi<this._LIMIT_PHI&&this._draw_lat_tick(phi,10,1.5*weight)}for(let i=center;i<top;i+=this._interval/10){const phi=this._invmerLength(i);phi<this._LIMIT_PHI&&phi>-this._LIMIT_PHI&&this._draw_lat_tick(phi,4,weight)}for(let i=center-this._interval/10;i>bottom;i-=this._interval/10){const phi=this._invmerLength(i);phi>-this._LIMIT_PHI&&phi<this._LIMIT_PHI&&this._draw_lat_tick(phi,4,weight)}},_create_lon_ticks:function(){const{weight:weight}=this.options,size=this._map.getSize(),to_rad=Math.PI/180,to_deg=180/Math.PI,center=this._map.containerPointToLatLng(L.point(size.x/2,0)),left=this._map.containerPointToLatLng(L.point(0,0)),right=this._map.containerPointToLatLng(L.point(size.x,0)),sinPhi2=Math.pow(Math.sin(center.lat*to_rad),2),N=this._a/Math.sqrt(1-this._e2*sinPhi2),dl=this._interval/(N*Math.cos(center.lat*to_rad))*to_deg;for(let i=center.lng+dl/2;i<right.lng;i+=dl)this._draw_lon_tick(i,10,1.5*weight);for(let i=center.lng-dl/2;i>left.lng;i-=dl)this._draw_lon_tick(i,10,1.5*weight);for(let i=center.lng;i<right.lng;i+=dl/10)this._draw_lon_tick(i,4,weight);for(let i=center.lng-dl/10;i>left.lng;i-=dl/10)this._draw_lon_tick(i,4,weight)},_setCanvasPosition:function(){let lt=this._map.containerPointToLayerPoint([0,0]);this._map._bearing&&(lt=this._map.rotatedPointToMapPanePoint(this._map.containerPointToLayerPoint(L.point(this._map._container.getBoundingClientRect())))),L.DomUtil.setPosition(this._canvas,lt)},_latLngToCanvasPoint:function(latlng){return L.point(this._map.project(L.latLng(latlng))._subtract(this._map.getPixelOrigin())).add(this._map._getMapPanePos())},_draw_lat_tick:function(phi,lenght,weight){const to_deg=180/Math.PI,size=this._map.getSize(),y=this._latLngToCanvasPoint(L.latLng(phi*to_deg,0)).y;this._ctx.lineWidth=weight,this._ctx.beginPath(),this._ctx.moveTo(0,y),this._ctx.lineTo(+lenght,y),this._ctx.stroke()},_draw_lon_tick:function(lam,lenght,weight){const x=this._latLngToCanvasPoint(L.latLng(0,lam)).x;this._ctx.lineWidth=weight,this._ctx.beginPath(),this._ctx.moveTo(x,0),this._ctx.lineTo(x,lenght),this._ctx.stroke()},_merLength:function(phi){const cos2=Math.cos(2*phi),sin2=Math.sin(2*phi);return this._A*(phi+sin2*(this._c1+(this._c2+(this._c3+(this._c4+this._c5*cos2)*cos2)*cos2)*cos2))},_invmerLength:function(s){const psi=s/this._A,cos2=Math.cos(2*psi),sin2=Math.sin(2*psi);return psi+sin2*(this._ic1+(this._ic2+(this._ic3+(this._ic4+this._ic5*cos2)*cos2)*cos2)*cos2)}}),L.control.edgeScale=function(options){return new L.Control.EdgeScale(options)},L.Map.mergeOptions({edgeScaleControl:!1}),L.Map.addInitHook((function(){this.options.edgeScaleControl&&(this.edgeScaleControl=new L.Control.EdgeScale,this.addControl(this.edgeScaleControl))})); \ No newline at end of file
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-gpxgroup.js b/assets/static/vendor/leaflet-elevation/libs/leaflet-gpxgroup.js
new file mode 100644
index 0000000..c7a9dd6
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-gpxgroup.js
@@ -0,0 +1,347 @@
+/*
+ * https://github.com/adoroszlai/joebed/tree/gh-pages
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014- Doroszlai Attila, 2019- Raruto
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+L.Mixin.Selectable = {
+ includes: L.Mixin.Events,
+
+ setSelected: function(s) {
+ var selected = !!s;
+ if (this._selected !== selected) {
+ this._selected = selected;
+ this.fire('selected');
+ }
+ },
+
+ isSelected: function() {
+ return !!this._selected;
+ },
+};
+
+L.Mixin.Selection = {
+ includes: L.Mixin.Events,
+
+ getSelection: function() {
+ return this._selected;
+ },
+
+ setSelection: function(item) {
+ if (this._selected === item) {
+ if (item !== null) {
+ item.setSelected(!item.isSelected());
+ if (!item.isSelected()) {
+ this._selected = null;
+ }
+ }
+ } else {
+ if (this._selected) {
+ this._selected.setSelected(false);
+ }
+ this._selected = item;
+ if (this._selected) {
+ this._selected.setSelected(true);
+ }
+ }
+ this.fire('selection_changed');
+ },
+};
+
+L.Control.LayersLegend = L.Control.Layers.extend({
+ _onInputClick: function() {
+ this._handlingClick = true;
+
+ this._layerControlInputs.reduceRight((_,input) => {
+ if (input.checked) {
+ this._map.fireEvent("legend_selected", {
+ layer: this._getLayer(input.layerId).layer,
+ input: input,
+ }, true);
+ return input;
+ }
+ }, 0);
+
+ this._handlingClick = false;
+
+ this._refocusOnMap();
+ }
+});
+
+L.control.layersLegend = (baseLayers, overlays, options) => new L.Control.LayersLegend(baseLayers, overlays, options);
+
+L.GeoJSON.include(L.Mixin.Selectable);
+
+L.GpxGroup = L.Class.extend({
+ options: {
+ highlight: {
+ color: '#ff0',
+ opacity: 1,
+ },
+ points: [],
+ points_options: {
+ icon: {
+ iconUrl: '../images/elevation-poi.png',
+ iconSize: [12, 12],
+ }
+ },
+ flyToBounds: true,
+ legend: false,
+ legend_options: {
+ position: "topright",
+ collapsed: false,
+ },
+ elevation: true,
+ elevation_options: {
+ theme: 'yellow-theme',
+ detached: true,
+ elevationDiv: '#elevation-div',
+ },
+ distanceMarkers: true,
+ distanceMarkers_options: {
+ lazy: true
+ },
+ },
+
+ initialize: function(tracks, options) {
+
+ L.Util.setOptions(this, options);
+
+ this._count = 0;
+ this._loadedCount = 0;
+ this._tracks = tracks;
+ this._layers = L.featureGroup();
+ this._markers = L.featureGroup();
+ this._elevation = L.control.elevation(this.options.elevation_options);
+ this._legend = L.control.layersLegend(null, null, this.options.legend_options);
+
+ this.options.points.forEach((poi) =>
+ L
+ .marker(poi.latlng, { icon: L.icon(this.options.points_options.icon) })
+ .bindTooltip(poi.name, { direction: 'auto' }).addTo(this._markers)
+ );
+
+ },
+
+ getBounds: function() {
+ return this._layers.getBounds();
+ },
+
+ addTo: function(map) {
+ this._layers.addTo(map);
+ this._markers.addTo(map);
+
+ this._map = map;
+
+ this.on('selection_changed', this._onSelectionChanged, this);
+ this._map.on('legend_selected', this._onLegendSelected, this);
+ this._tracks.forEach(this.addTrack, this);
+
+ },
+
+ addTrack: function(track) {
+ if (track instanceof Object) {
+ this._loadGeoJSON(track);
+ } else {
+ fetch(track)
+ .then(response => response.ok && response.text())
+ .then(text => this._elevation._parseFromString(text))
+ .then(geojson => this._loadGeoJSON(geojson, track.split('/').pop().split('#')[0].split('?')[0]));
+ }
+ },
+
+ _loadGeoJSON: function(geojson, fallbackName) {
+ if (geojson) {
+ geojson.name = geojson.name || (geojson[0] && geojson[0].properties.name) || fallbackName;
+ this._loadRoute(geojson);
+ }
+ },
+
+ _loadRoute: function(data) {
+ if (!data) return;
+
+ var line_style = {
+ color: this._uniqueColors(this._tracks.length)[this._count++],
+ opacity: 0.75,
+ weight: 5,
+ distanceMarkers: this.options.distanceMarkers_options,
+ };
+
+ var route = L.geoJson(data, {
+ name: data.name || '',
+ style: (feature) => line_style,
+ distanceMarkers: line_style.distanceMarkers,
+ originalStyle: line_style,
+ filter: feature => feature.geometry.type != "Point",
+ });
+
+
+ this._elevation.import(this._elevation.__LGEOMUTIL).then(() => {
+ route.addTo(this._layers);
+
+ route.eachLayer((layer) => this._onEachRouteLayer(route, layer));
+ this._onEachRouteLoaded(route);
+ });
+
+ },
+
+ _onEachRouteLayer: function(route, layer) {
+ var polyline = layer;
+
+ route.on('selected', L.bind(this._onRouteSelected, this, route, polyline));
+
+ polyline.on('mouseover', L.bind(this._onRouteMouseOver, this, route, polyline));
+ polyline.on('mouseout', L.bind(this._onRouteMouseOut, this, route, polyline));
+ polyline.on('click', L.bind(this._onRouteClick, this, route, polyline));
+
+ polyline.bindTooltip(route.options.name, { direction: 'auto', sticky: true, });
+ },
+
+ _onEachRouteLoaded: function(route) {
+ if (this.options.legend) {
+ this._legend.addBaseLayer(route, '<svg id="legend_' + route._leaflet_id + '" width="25" height="10" version="1.1" xmlns="http://www.w3.org/2000/svg">' + '<line x1="0" x2="50" y1="5" y2="5" stroke="' + route.options.originalStyle.color + '" fill="transparent" stroke-width="5" /></svg>' + ' ' + route.options.name);
+ }
+
+ this.fire('route_loaded', { route: route });
+
+ if (++this._loadedCount === this._tracks.length) {
+ this.fire('loaded');
+ if (this.options.flyToBounds) {
+ this._map.flyToBounds(this.getBounds(), { duration: 0.25, easeLinearity: 0.25, noMoveStart: true });
+ }
+ if (this.options.legend) {
+ this._legend.addTo(this._map);
+ }
+ }
+ },
+
+ highlight: function(route, polyline) {
+ polyline.setStyle(this.options.highlight);
+ if (this.options.distanceMarkers) {
+ polyline.addDistanceMarkers();
+ }
+ },
+
+ unhighlight: function(route, polyline) {
+ polyline.setStyle(route.options.originalStyle);
+ if (this.options.distanceMarkers) {
+ polyline.removeDistanceMarkers();
+ }
+ },
+
+ _onRouteMouseOver: function(route, polyline) {
+ if (!route.isSelected()) {
+ this.highlight(route, polyline);
+ if (this.options.legend) {
+ this.setSelection(route);
+ L.DomUtil.get('legend_' + route._leaflet_id).parentNode.previousSibling.click();
+ }
+ }
+ this.fire('route_mouseover', { route: route, polyline: polyline });
+ },
+
+ _onRouteMouseOut: function(route, polyline) {
+ if (!route.isSelected()) {
+ this.unhighlight(route, polyline);
+ }
+ this.fire('route_mouseout', { route: route, polyline: polyline });
+ },
+
+ _onRouteClick: function(route, polyline) {
+ this.setSelection(route);
+ },
+
+ _onRouteSelected: function(route, polyline) {
+ if (!route.isSelected()) {
+ this.unhighlight(route, polyline);
+ }
+ },
+
+ _onSelectionChanged: function(e) {
+ var elevation = this._elevation;
+ var eleDiv = elevation.getContainer();
+ var route = this.getSelection();
+
+ elevation.clear();
+
+ if (route && route.isSelected()) {
+ if (!eleDiv) {
+ elevation.addTo(this._map);
+ }
+ route.getLayers().forEach(function(layer) {
+ if (layer instanceof L.Polyline) {
+ elevation.addData(layer, false);
+ layer.bringToFront();
+ }
+ });
+ } else {
+ if (eleDiv) {
+ elevation.remove();
+ }
+ }
+ },
+
+ _onLegendSelected: function(e) {
+ var parent = e.input.closest('.leaflet-control-layers-list');
+ var route = e.layer;
+
+ if (!route.isSelected()) {
+ this.setSelection(route);
+ for (var i in route._layers) {
+ this.highlight(route, route._layers[i]);
+ }
+ this._map.flyToBounds(e.layer.getBounds());
+ }
+
+ parent.scroll({ top: (e.input.offsetTop - parent.offsetTop) || 0, behavior: 'smooth' });
+
+ this._layers.eachLayer(layer => {
+ var legend = L.DomUtil.get('legend_' + layer._leaflet_id);
+ legend.querySelector("line").style.stroke = layer.isSelected() ? this.options.highlight.color : "";
+ legend.parentNode.style.fontWeight = layer.isSelected() ? "700" : "";
+ });
+ },
+
+ _uniqueColors: function(count) {
+ return count === 1 ? ['#0000ff'] : new Array(count).fill(null).map((_,i) => this._hsvToHex(i * (1 / count), 1, 0.7));
+ },
+
+ _hsvToHex: function(h, s, v) {
+ var i = Math.floor(h * 6);
+ var f = h * 6 - i;
+ var p = v * (1 - s);
+ var q = v * (1 - f * s);
+ var t = v * (1 - (1 - f) * s);
+ var rgb = { 0: [v, t, p], 1: [q, v, p], 2: [p, v, t], 3: [p, q, v], 4: [t, p, v], 5: [v, p, q] }[i % 6];
+ return rgb.map(d => d * 255).reduce((hex, byte) => hex + ((byte >> 4) & 0x0F).toString(16) + (byte & 0x0F).toString(16), "#");
+ },
+
+ removeFrom: function(map) {
+ this._layers.removeFrom(map);
+ },
+
+});
+
+L.GpxGroup.include(L.Mixin.Events);
+L.GpxGroup.include(L.Mixin.Selection);
+
+L.gpxGroup = (tracks, options) => new L.GpxGroup(tracks, options); \ No newline at end of file
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-gpxgroup.min.js b/assets/static/vendor/leaflet-elevation/libs/leaflet-gpxgroup.min.js
new file mode 100644
index 0000000..5b30fb0
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-gpxgroup.min.js
@@ -0,0 +1 @@
+L.Mixin.Selectable={includes:L.Mixin.Events,setSelected:function(s){var selected=!!s;this._selected!==selected&&(this._selected=selected,this.fire("selected"))},isSelected:function(){return!!this._selected}},L.Mixin.Selection={includes:L.Mixin.Events,getSelection:function(){return this._selected},setSelection:function(item){this._selected===item?null!==item&&(item.setSelected(!item.isSelected()),item.isSelected()||(this._selected=null)):(this._selected&&this._selected.setSelected(!1),this._selected=item,this._selected&&this._selected.setSelected(!0)),this.fire("selection_changed")}},L.Control.LayersLegend=L.Control.Layers.extend({_onInputClick:function(){this._handlingClick=!0,this._layerControlInputs.reduceRight((_,input)=>{if(input.checked)return this._map.fireEvent("legend_selected",{layer:this._getLayer(input.layerId).layer,input:input},!0),input},0),this._handlingClick=!1,this._refocusOnMap()}}),L.control.layersLegend=(baseLayers,overlays,options)=>new L.Control.LayersLegend(baseLayers,overlays,options),L.GeoJSON.include(L.Mixin.Selectable),L.GpxGroup=L.Class.extend({options:{highlight:{color:"#ff0",opacity:1},points:[],points_options:{icon:{iconUrl:"../images/elevation-poi.png",iconSize:[12,12]}},flyToBounds:!0,legend:!1,legend_options:{position:"topright",collapsed:!1},elevation:!0,elevation_options:{theme:"yellow-theme",detached:!0,elevationDiv:"#elevation-div"},distanceMarkers:!0,distanceMarkers_options:{lazy:!0}},initialize:function(tracks,options){L.Util.setOptions(this,options),this._count=0,this._loadedCount=0,this._tracks=tracks,this._layers=L.featureGroup(),this._markers=L.featureGroup(),this._elevation=L.control.elevation(this.options.elevation_options),this._legend=L.control.layersLegend(null,null,this.options.legend_options),this.options.points.forEach(poi=>L.marker(poi.latlng,{icon:L.icon(this.options.points_options.icon)}).bindTooltip(poi.name,{direction:"auto"}).addTo(this._markers))},getBounds:function(){return this._layers.getBounds()},addTo:function(map){this._layers.addTo(map),this._markers.addTo(map),this._map=map,this.on("selection_changed",this._onSelectionChanged,this),this._map.on("legend_selected",this._onLegendSelected,this),this._tracks.forEach(this.addTrack,this)},addTrack:function(track){track instanceof Object?this._loadGeoJSON(track):fetch(track).then(response=>response.ok&&response.text()).then(text=>this._elevation._parseFromString(text)).then(geojson=>this._loadGeoJSON(geojson,track.split("/").pop().split("#")[0].split("?")[0]))},_loadGeoJSON:function(geojson,fallbackName){geojson&&(geojson.name=geojson.name||geojson[0]&&geojson[0].properties.name||fallbackName,this._loadRoute(geojson))},_loadRoute:function(data){if(data){var line_style={color:this._uniqueColors(this._tracks.length)[this._count++],opacity:.75,weight:5,distanceMarkers:this.options.distanceMarkers_options},route=L.geoJson(data,{name:data.name||"",style:feature=>line_style,distanceMarkers:line_style.distanceMarkers,originalStyle:line_style,filter:feature=>"Point"!=feature.geometry.type});this._elevation.import(this._elevation.__LGEOMUTIL).then(()=>{route.addTo(this._layers),route.eachLayer(layer=>this._onEachRouteLayer(route,layer)),this._onEachRouteLoaded(route)})}},_onEachRouteLayer:function(route,layer){var polyline=layer;route.on("selected",L.bind(this._onRouteSelected,this,route,polyline)),polyline.on("mouseover",L.bind(this._onRouteMouseOver,this,route,polyline)),polyline.on("mouseout",L.bind(this._onRouteMouseOut,this,route,polyline)),polyline.on("click",L.bind(this._onRouteClick,this,route,polyline)),polyline.bindTooltip(route.options.name,{direction:"auto",sticky:!0})},_onEachRouteLoaded:function(route){this.options.legend&&this._legend.addBaseLayer(route,'<svg id="legend_'+route._leaflet_id+'" width="25" height="10" version="1.1" xmlns="http://www.w3.org/2000/svg"><line x1="0" x2="50" y1="5" y2="5" stroke="'+route.options.originalStyle.color+'" fill="transparent" stroke-width="5" /></svg> '+route.options.name),this.fire("route_loaded",{route:route}),++this._loadedCount===this._tracks.length&&(this.fire("loaded"),this.options.flyToBounds&&this._map.flyToBounds(this.getBounds(),{duration:.25,easeLinearity:.25,noMoveStart:!0}),this.options.legend&&this._legend.addTo(this._map))},highlight:function(route,polyline){polyline.setStyle(this.options.highlight),this.options.distanceMarkers&&polyline.addDistanceMarkers()},unhighlight:function(route,polyline){polyline.setStyle(route.options.originalStyle),this.options.distanceMarkers&&polyline.removeDistanceMarkers()},_onRouteMouseOver:function(route,polyline){route.isSelected()||(this.highlight(route,polyline),this.options.legend&&(this.setSelection(route),L.DomUtil.get("legend_"+route._leaflet_id).parentNode.previousSibling.click())),this.fire("route_mouseover",{route:route,polyline:polyline})},_onRouteMouseOut:function(route,polyline){route.isSelected()||this.unhighlight(route,polyline),this.fire("route_mouseout",{route:route,polyline:polyline})},_onRouteClick:function(route,polyline){this.setSelection(route)},_onRouteSelected:function(route,polyline){route.isSelected()||this.unhighlight(route,polyline)},_onSelectionChanged:function(e){var elevation=this._elevation,eleDiv=elevation.getContainer(),route=this.getSelection();elevation.clear(),route&&route.isSelected()?(eleDiv||elevation.addTo(this._map),route.getLayers().forEach((function(layer){layer instanceof L.Polyline&&(elevation.addData(layer,!1),layer.bringToFront())}))):eleDiv&&elevation.remove()},_onLegendSelected:function(e){var parent=e.input.closest(".leaflet-control-layers-list"),route=e.layer;if(!route.isSelected()){for(var i in this.setSelection(route),route._layers)this.highlight(route,route._layers[i]);this._map.flyToBounds(e.layer.getBounds())}parent.scroll({top:e.input.offsetTop-parent.offsetTop||0,behavior:"smooth"}),this._layers.eachLayer(layer=>{var legend=L.DomUtil.get("legend_"+layer._leaflet_id);legend.querySelector("line").style.stroke=layer.isSelected()?this.options.highlight.color:"",legend.parentNode.style.fontWeight=layer.isSelected()?"700":""})},_uniqueColors:function(count){return 1===count?["#0000ff"]:new Array(count).fill(null).map((_,i)=>this._hsvToHex(i*(1/count),1,.7))},_hsvToHex:function(h,s,v){var i=Math.floor(6*h),f=6*h-i,p=v*(1-s),q=v*(1-f*s),t=v*(1-(1-f)*s),rgb;return{0:[v,t,p],1:[q,v,p],2:[p,v,t],3:[p,q,v],4:[t,p,v],5:[v,p,q]}[i%6].map(d=>255*d).reduce((hex,byte)=>hex+(byte>>4&15).toString(16)+(15&byte).toString(16),"#")},removeFrom:function(map){this._layers.removeFrom(map)}}),L.GpxGroup.include(L.Mixin.Events),L.GpxGroup.include(L.Mixin.Selection),L.gpxGroup=(tracks,options)=>new L.GpxGroup(tracks,options); \ No newline at end of file
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-hotline.js b/assets/static/vendor/leaflet-elevation/libs/leaflet-hotline.js
new file mode 100644
index 0000000..1684560
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-hotline.js
@@ -0,0 +1,457 @@
+/*
+(c) 2017, iosphere GmbH
+Leaflet.hotline, a Leaflet plugin for drawing gradients along polylines.
+https://github.com/iosphere/Leaflet.hotline/
+*/
+
+(function (root, plugin) {
+ /**
+ * UMD wrapper.
+ * When used directly in the Browser it expects Leaflet to be globally
+ * available as `L`. The plugin then adds itself to Leaflet.
+ * When used as a CommonJS module (e.g. with browserify) only the plugin
+ * factory gets exported, so one hast to call the factory manually and pass
+ * Leaflet as the only parameter.
+ * @see {@link https://github.com/umdjs/umd}
+ */
+ if (typeof define === 'function' && define.amd) {
+ define(['leaflet'], plugin);
+ } else if (typeof exports === 'object') {
+ module.exports = plugin;
+ } else {
+ plugin(root.L);
+ }
+}(window, function (L) {
+ // Plugin is already added to Leaflet
+ if (L.Hotline) {
+ return L;
+ }
+
+ /**
+ * Core renderer.
+ * @constructor
+ * @param {HTMLElement | string} canvas - &lt;canvas> element or its id
+ * to initialize the instance on.
+ */
+ var Hotline = function (canvas) {
+ if (!(this instanceof Hotline)) { return new Hotline(canvas); }
+
+ var defaultPalette = {
+ 0.0: 'green',
+ 0.5: 'yellow',
+ 1.0: 'red'
+ };
+
+ this._canvas = canvas = ('string' === typeof canvas)
+ ? document.getElementById(canvas)
+ : canvas;
+
+ this._ctx = canvas.getContext('2d');
+ this._width = canvas.width;
+ this._height = canvas.height;
+
+ this._weight = 5;
+ this._outlineWidth = 1;
+ this._outlineColor = 'black';
+
+ this._min = 0;
+ this._max = 1;
+
+ this._data = [];
+
+ this.palette(defaultPalette);
+ };
+
+ Hotline.prototype = {
+ /**
+ * Sets the width of the canvas. Used when clearing the canvas.
+ * @param {number} width - Width of the canvas.
+ */
+ width: function (width) {
+ this._width = width;
+ return this;
+ },
+
+ /**
+ * Sets the height of the canvas. Used when clearing the canvas.
+ * @param {number} height - Height of the canvas.
+ */
+ height: function (height) {
+ this._height = height;
+ return this;
+ },
+
+ /**
+ * Sets the weight of the path.
+ * @param {number} weight - Weight of the path in px.
+ */
+ weight: function (weight) {
+ this._weight = weight;
+ return this;
+ },
+
+ /**
+ * Sets the width of the outline around the path.
+ * @param {number} outlineWidth - Width of the outline in px.
+ */
+ outlineWidth: function (outlineWidth) {
+ this._outlineWidth = outlineWidth;
+ return this;
+ },
+
+ /**
+ * Sets the color of the outline around the path.
+ * @param {string} outlineColor - A CSS color value.
+ */
+ outlineColor: function (outlineColor) {
+ this._outlineColor = outlineColor;
+ return this;
+ },
+
+ /**
+ * Sets the palette gradient.
+ * @param {Object.<number, string>} palette - Gradient definition.
+ * e.g. { 0.0: 'white', 1.0: 'black' }
+ */
+ palette: function (palette) {
+ var canvas = document.createElement('canvas'),
+ ctx = canvas.getContext('2d'),
+ gradient = ctx.createLinearGradient(0, 0, 0, 256);
+
+ canvas.width = 1;
+ canvas.height = 256;
+
+ for (var i in palette) {
+ gradient.addColorStop(i, palette[i]);
+ }
+
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, 0, 1, 256);
+
+ this._palette = ctx.getImageData(0, 0, 1, 256).data;
+
+ return this;
+ },
+
+ /**
+ * Sets the value used at the start of the palette gradient.
+ * @param {number} min
+ */
+ min: function (min) {
+ this._min = min;
+ return this;
+ },
+
+ /**
+ * Sets the value used at the end of the palette gradient.
+ * @param {number} max
+ */
+ max: function (max) {
+ this._max = max;
+ return this;
+ },
+
+ /**
+ * A path to rander as a hotline.
+ * @typedef Array.<{x:number, y:number, z:number}> Path - Array of x, y and z coordinates.
+ */
+
+ /**
+ * Sets the data that gets drawn on the canvas.
+ * @param {(Path|Path[])} data - A single path or an array of paths.
+ */
+ data: function (data) {
+ this._data = data;
+ return this;
+ },
+
+ /**
+ * Adds a path to the list of paths.
+ * @param {Path} path
+ */
+ add: function (path) {
+ this._data.push(path);
+ return this;
+ },
+
+ /**
+ * Draws the currently set paths.
+ */
+ draw: function () {
+ var ctx = this._ctx;
+
+ ctx.globalCompositeOperation = 'source-over';
+ ctx.lineCap = 'round';
+
+ this._drawOutline(ctx);
+ this._drawHotline(ctx);
+
+ return this;
+ },
+
+ /**
+ * Gets the RGB values of a given z value of the current palette.
+ * @param {number} value - Value to get the color for, should be between min and max.
+ * @returns {Array.<number>} The RGB values as an array [r, g, b]
+ */
+ getRGBForValue: function (value) {
+ var valueRelative = Math.min(Math.max((value - this._min) / (this._max - this._min), 0), 0.999);
+ var paletteIndex = Math.floor(valueRelative * 256) * 4;
+
+ return [
+ this._palette[paletteIndex],
+ this._palette[paletteIndex + 1],
+ this._palette[paletteIndex + 2]
+ ];
+ },
+
+ /**
+ * Draws the outline of the graphs.
+ * @private
+ */
+ _drawOutline: function (ctx) {
+ var i, j, dataLength, path, pathLength, pointStart, pointEnd;
+
+ if (this._outlineWidth) {
+ for (i = 0, dataLength = this._data.length; i < dataLength; i++) {
+ path = this._data[i];
+ ctx.lineWidth = this._weight + 2 * this._outlineWidth;
+
+ for (j = 1, pathLength = path.length; j < pathLength; j++) {
+ pointStart = path[j - 1];
+ pointEnd = path[j];
+
+ ctx.strokeStyle = this._outlineColor;
+ ctx.beginPath();
+ ctx.moveTo(pointStart.x, pointStart.y);
+ ctx.lineTo(pointEnd.x, pointEnd.y);
+ ctx.stroke();
+ }
+ }
+ }
+ },
+
+ /**
+ * Draws the color encoded hotline of the graphs.
+ * @private
+ */
+ _drawHotline: function (ctx) {
+ var i, j, dataLength, path, pathLength, pointStart, pointEnd,
+ gradient, gradientStartRGB, gradientEndRGB;
+
+ ctx.lineWidth = this._weight;
+
+ for (i = 0, dataLength = this._data.length; i < dataLength; i++) {
+ path = this._data[i];
+
+ for (j = 1, pathLength = path.length; j < pathLength; j++) {
+ pointStart = path[j - 1];
+ pointEnd = path[j];
+
+ // Create a gradient for each segment, pick start end end colors from palette gradient
+ gradient = ctx.createLinearGradient(pointStart.x, pointStart.y, pointEnd.x, pointEnd.y);
+ gradientStartRGB = this.getRGBForValue(pointStart.z);
+ gradientEndRGB = this.getRGBForValue(pointEnd.z);
+ gradient.addColorStop(0, 'rgb(' + gradientStartRGB.join(',') + ')');
+ gradient.addColorStop(1, 'rgb(' + gradientEndRGB.join(',') + ')');
+
+ ctx.strokeStyle = gradient;
+ ctx.beginPath();
+ ctx.moveTo(pointStart.x, pointStart.y);
+ ctx.lineTo(pointEnd.x, pointEnd.y);
+ ctx.stroke();
+ }
+ }
+ }
+ };
+
+
+ var Renderer = L.Canvas.extend({
+ _initContainer: function () {
+ L.Canvas.prototype._initContainer.call(this);
+ this._hotline = new Hotline(this._container);
+ },
+
+ _update: function () {
+ L.Canvas.prototype._update.call(this);
+ this._hotline.width(this._container.width);
+ this._hotline.height(this._container.height);
+ },
+
+ _updatePoly: function (layer) {
+ if (!this._drawing) { return; }
+
+ var parts = layer._parts;
+
+ if (!parts.length) { return; }
+
+ this._updateOptions(layer);
+
+ this._hotline
+ .data(parts)
+ .draw();
+ },
+
+ _updateOptions: function (layer) {
+ if (layer.options.min != null) {
+ this._hotline.min(layer.options.min);
+ }
+ if (layer.options.max != null) {
+ this._hotline.max(layer.options.max);
+ }
+ if (layer.options.weight != null) {
+ this._hotline.weight(layer.options.weight);
+ }
+ if (layer.options.outlineWidth != null) {
+ this._hotline.outlineWidth(layer.options.outlineWidth);
+ }
+ if (layer.options.outlineColor != null) {
+ this._hotline.outlineColor(layer.options.outlineColor);
+ }
+ if (layer.options.palette) {
+ this._hotline.palette(layer.options.palette);
+ }
+ }
+ });
+
+ var renderer = function (options) {
+ return L.Browser.canvas ? new Renderer(options) : null;
+ };
+
+
+ var Util = {
+ /**
+ * This is just a copy of the original Leaflet version that support a third z coordinate.
+ * @see {@link http://leafletjs.com/reference.html#lineutil-clipsegment|Leaflet}
+ */
+ clipSegment: function (a, b, bounds, useLastCode, round) {
+ var codeA = useLastCode ? this._lastCode : L.LineUtil._getBitCode(a, bounds),
+ codeB = L.LineUtil._getBitCode(b, bounds),
+ codeOut, p, newCode;
+
+ // save 2nd code to avoid calculating it on the next segment
+ this._lastCode = codeB;
+
+ while (true) {
+ // if a,b is inside the clip window (trivial accept)
+ if (!(codeA | codeB)) {
+ return [a, b];
+ // if a,b is outside the clip window (trivial reject)
+ } else if (codeA & codeB) {
+ return false;
+ // other cases
+ } else {
+ codeOut = codeA || codeB;
+ p = L.LineUtil._getEdgeIntersection(a, b, codeOut, bounds, round);
+ newCode = L.LineUtil._getBitCode(p, bounds);
+
+ if (codeOut === codeA) {
+ p.z = a.z;
+ a = p;
+ codeA = newCode;
+ } else {
+ p.z = b.z;
+ b = p;
+ codeB = newCode;
+ }
+ }
+ }
+ }
+ };
+
+
+ L.Hotline = L.Polyline.extend({
+ statics: {
+ Renderer: Renderer,
+ renderer: renderer
+ },
+
+ options: {
+ renderer: renderer(),
+ min: 0,
+ max: 1,
+ palette: {
+ 0.0: 'green',
+ 0.5: 'yellow',
+ 1.0: 'red'
+ },
+ weight: 5,
+ outlineColor: 'black',
+ outlineWidth: 1
+ },
+
+ getRGBForValue: function (value) {
+ return this._renderer._hotline.getRGBForValue(value);
+ },
+
+ /**
+ * Just like the Leaflet version, but with support for a z coordinate.
+ */
+ _projectLatlngs: function (latlngs, result, projectedBounds) {
+ var flat = latlngs[0] instanceof L.LatLng,
+ len = latlngs.length,
+ i, ring;
+
+ if (flat) {
+ ring = [];
+ for (i = 0; i < len; i++) {
+ ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
+ // Add the altitude of the latLng as the z coordinate to the point
+ ring[i].z = latlngs[i].alt;
+ projectedBounds.extend(ring[i]);
+ }
+ result.push(ring);
+ } else {
+ for (i = 0; i < len; i++) {
+ this._projectLatlngs(latlngs[i], result, projectedBounds);
+ }
+ }
+ },
+
+ /**
+ * Just like the Leaflet version, but uses `Util.clipSegment()`.
+ */
+ _clipPoints: function () {
+ if (this.options.noClip) {
+ this._parts = this._rings;
+ return;
+ }
+
+ this._parts = [];
+
+ var parts = this._parts,
+ bounds = this._renderer._bounds,
+ i, j, k, len, len2, segment, points;
+
+ for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
+ points = this._rings[i];
+
+ for (j = 0, len2 = points.length; j < len2 - 1; j++) {
+ segment = Util.clipSegment(points[j], points[j + 1], bounds, j, true);
+
+ if (!segment) { continue; }
+
+ parts[k] = parts[k] || [];
+ parts[k].push(segment[0]);
+
+ // if segment goes out of screen, or it's the last one, it's the end of the line part
+ if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
+ parts[k].push(segment[1]);
+ k++;
+ }
+ }
+ }
+ },
+
+ _clickTolerance: function () {
+ return this.options.weight / 2 + this.options.outlineWidth + (L.Browser.touch ? 10 : 0);
+ }
+ });
+
+ L.hotline = function (latlngs, options) {
+ return new L.Hotline(latlngs, options);
+ };
+
+
+ return L;
+})); \ No newline at end of file
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-hotline.min.js b/assets/static/vendor/leaflet-elevation/libs/leaflet-hotline.min.js
new file mode 100644
index 0000000..a1466b8
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-hotline.min.js
@@ -0,0 +1 @@
+!function(root,plugin){"function"==typeof define&&define.amd?define(["leaflet"],plugin):"object"==typeof exports?module.exports=plugin:plugin(root.L)}(window,(function(L){if(L.Hotline)return L;var Hotline=function(canvas){if(!(this instanceof Hotline))return new Hotline(canvas);var defaultPalette={0:"green",.5:"yellow",1:"red"};this._canvas=canvas="string"==typeof canvas?document.getElementById(canvas):canvas,this._ctx=canvas.getContext("2d"),this._width=canvas.width,this._height=canvas.height,this._weight=5,this._outlineWidth=1,this._outlineColor="black",this._min=0,this._max=1,this._data=[],this.palette(defaultPalette)};Hotline.prototype={width:function(width){return this._width=width,this},height:function(height){return this._height=height,this},weight:function(weight){return this._weight=weight,this},outlineWidth:function(outlineWidth){return this._outlineWidth=outlineWidth,this},outlineColor:function(outlineColor){return this._outlineColor=outlineColor,this},palette:function(palette){var canvas=document.createElement("canvas"),ctx=canvas.getContext("2d"),gradient=ctx.createLinearGradient(0,0,0,256);for(var i in canvas.width=1,canvas.height=256,palette)gradient.addColorStop(i,palette[i]);return ctx.fillStyle=gradient,ctx.fillRect(0,0,1,256),this._palette=ctx.getImageData(0,0,1,256).data,this},min:function(min){return this._min=min,this},max:function(max){return this._max=max,this},data:function(data){return this._data=data,this},add:function(path){return this._data.push(path),this},draw:function(){var ctx=this._ctx;return ctx.globalCompositeOperation="source-over",ctx.lineCap="round",this._drawOutline(ctx),this._drawHotline(ctx),this},getRGBForValue:function(value){var valueRelative=Math.min(Math.max((value-this._min)/(this._max-this._min),0),.999),paletteIndex=4*Math.floor(256*valueRelative);return[this._palette[paletteIndex],this._palette[paletteIndex+1],this._palette[paletteIndex+2]]},_drawOutline:function(ctx){var i,j,dataLength,path,pathLength,pointStart,pointEnd;if(this._outlineWidth)for(i=0,dataLength=this._data.length;i<dataLength;i++)for(path=this._data[i],ctx.lineWidth=this._weight+2*this._outlineWidth,j=1,pathLength=path.length;j<pathLength;j++)pointStart=path[j-1],pointEnd=path[j],ctx.strokeStyle=this._outlineColor,ctx.beginPath(),ctx.moveTo(pointStart.x,pointStart.y),ctx.lineTo(pointEnd.x,pointEnd.y),ctx.stroke()},_drawHotline:function(ctx){var i,j,dataLength,path,pathLength,pointStart,pointEnd,gradient,gradientStartRGB,gradientEndRGB;for(ctx.lineWidth=this._weight,i=0,dataLength=this._data.length;i<dataLength;i++)for(j=1,pathLength=(path=this._data[i]).length;j<pathLength;j++)pointStart=path[j-1],pointEnd=path[j],gradient=ctx.createLinearGradient(pointStart.x,pointStart.y,pointEnd.x,pointEnd.y),gradientStartRGB=this.getRGBForValue(pointStart.z),gradientEndRGB=this.getRGBForValue(pointEnd.z),gradient.addColorStop(0,"rgb("+gradientStartRGB.join(",")+")"),gradient.addColorStop(1,"rgb("+gradientEndRGB.join(",")+")"),ctx.strokeStyle=gradient,ctx.beginPath(),ctx.moveTo(pointStart.x,pointStart.y),ctx.lineTo(pointEnd.x,pointEnd.y),ctx.stroke()}};var Renderer=L.Canvas.extend({_initContainer:function(){L.Canvas.prototype._initContainer.call(this),this._hotline=new Hotline(this._container)},_update:function(){L.Canvas.prototype._update.call(this),this._hotline.width(this._container.width),this._hotline.height(this._container.height)},_updatePoly:function(layer){if(this._drawing){var parts=layer._parts;parts.length&&(this._updateOptions(layer),this._hotline.data(parts).draw())}},_updateOptions:function(layer){null!=layer.options.min&&this._hotline.min(layer.options.min),null!=layer.options.max&&this._hotline.max(layer.options.max),null!=layer.options.weight&&this._hotline.weight(layer.options.weight),null!=layer.options.outlineWidth&&this._hotline.outlineWidth(layer.options.outlineWidth),null!=layer.options.outlineColor&&this._hotline.outlineColor(layer.options.outlineColor),layer.options.palette&&this._hotline.palette(layer.options.palette)}}),renderer=function(options){return L.Browser.canvas?new Renderer(options):null},Util={clipSegment:function(a,b,bounds,useLastCode,round){var codeA=useLastCode?this._lastCode:L.LineUtil._getBitCode(a,bounds),codeB=L.LineUtil._getBitCode(b,bounds),codeOut,p,newCode;for(this._lastCode=codeB;;){if(!(codeA|codeB))return[a,b];if(codeA&codeB)return!1;codeOut=codeA||codeB,p=L.LineUtil._getEdgeIntersection(a,b,codeOut,bounds,round),newCode=L.LineUtil._getBitCode(p,bounds),codeOut===codeA?(p.z=a.z,a=p,codeA=newCode):(p.z=b.z,b=p,codeB=newCode)}}};return L.Hotline=L.Polyline.extend({statics:{Renderer:Renderer,renderer:renderer},options:{renderer:renderer(),min:0,max:1,palette:{0:"green",.5:"yellow",1:"red"},weight:5,outlineColor:"black",outlineWidth:1},getRGBForValue:function(value){return this._renderer._hotline.getRGBForValue(value)},_projectLatlngs:function(latlngs,result,projectedBounds){var flat=latlngs[0]instanceof L.LatLng,len=latlngs.length,i,ring;if(flat){for(ring=[],i=0;i<len;i++)ring[i]=this._map.latLngToLayerPoint(latlngs[i]),ring[i].z=latlngs[i].alt,projectedBounds.extend(ring[i]);result.push(ring)}else for(i=0;i<len;i++)this._projectLatlngs(latlngs[i],result,projectedBounds)},_clipPoints:function(){if(this.options.noClip)this._parts=this._rings;else{this._parts=[];var parts=this._parts,bounds=this._renderer._bounds,i,j,k,len,len2,segment,points;for(i=0,k=0,len=this._rings.length;i<len;i++)for(j=0,len2=(points=this._rings[i]).length;j<len2-1;j++)(segment=Util.clipSegment(points[j],points[j+1],bounds,j,!0))&&(parts[k]=parts[k]||[],parts[k].push(segment[0]),segment[1]===points[j+1]&&j!==len2-2||(parts[k].push(segment[1]),k++))}},_clickTolerance:function(){return this.options.weight/2+this.options.outlineWidth+(L.Browser.touch?10:0)}}),L.hotline=function(latlngs,options){return new L.Hotline(latlngs,options)},L})); \ No newline at end of file
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.css b/assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.css
new file mode 100644
index 0000000..e59d69b
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.css
@@ -0,0 +1,42 @@
+.leaflet-ruler {
+ height: 35px;
+ width: 35px;
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACx0lEQVRIS52VWahOURTHXRSJzBkiJcpYxgeS4oUi90GR4VK6DzcUD3gQIQ8UCXkwlCJRkjJEpqT7oGTMeHONmacyRRl/v9ve2n2d7/vOtevXPmefff5r7bXXXruiSf42gKkToTu0hY/wEk7CnWIyFWX0B/N9OkyF1tANdsEz6AIL4Sl8h8NwEK6nmsUMNGPSalgePFxLfwl2hrG39O1hM8yFobAKpsAGWAE/NJRloBfj++ErnIYesDj1qsjzRsYN2VhwdTPgUaGBkUF0e/D0TxDrRD8ehsFw6AcP4FrgCL17EpsrWAKDUgNtwuQP9FehJsyeSb8FbsOV8O0efVeYHOZV0h8N8w3dWXBOVWpgDwO/oBqc1BxciV7Pg3OJhz66ivPB+PoM8TnqRQPTeNkKfeALtAQ39QYsgE85xNsFz+voFe8AldFALS8DYU3waAd9b5gAv3OKn2HefagK4q64VgPm9k0we0wtN9pN6w+mY9qywqLnWeKXGa/WwHwYFSwrZtw/w9Ic4p5oxesLPG8Qd/UacMf13IE38ArGwN3EQJbnintOHsJsMOaG5Z+4/2vAY282eALd4FvQqoj4Aca3gQfPw1hSPBr4xoNH3bz1EO0D428r9LwpY3vB+nSolOfRQVeg1yOCgXH01pu+GeL+Y1hOwWOYBZlhieJxBU94eA2Lwo9WRw+X8fUEx0PUaPFowKW6Cg+UBc6M6AzrMsR1xtJR1vO4CkNk1TMLJoXBlcHQpvAePW+0eFyBRe45jAbL7fskhqm4MbdGmYruUU94l8zNfIyl4hhfLQteHqaiLYq7J4YlipvnXkgWRotgyRYNWB4st6bpizLiHsiO4H1gMrhnRVtarpcxy0vd+9eLPMvzhuMf1E7QmyC78xrQmDk+BC6Am98CLoKXi/dvWll1xjvjeAkD9YVXpkJ6ae7/BOPus4b+p9X9BdB/zv8zawwXAAAAAElFTkSuQmCC"); /* <div>Icons made by <a href="http://www.freepik.com" title="Freepik">Freepik</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div> */
+ background-repeat: no-repeat;
+ background-position: center;
+}
+.leaflet-ruler-clicked,
+.leaflet-ruler:hover {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAE/ElEQVRIS6WVeWwUVRzHfzOzs7Pdi+7Rdq/ubrH0sNaKBFMCSEvBQLivFkhAE8GDYAyHATWgETUoMZ7BGCMBNKYiQbTKpYQKBqKlWmBbadmW7bbbFjv0mu2eM298M5SlpS3U+LL/zL6Zz/f7vr/3e4+AsQ6j80EgxTkKhGwkQRqQiLp5UtEOSDwOXf660TDEPfkGR75RTT8VF1EpJRJ6LsbrVuaZg2YtrWND8WD5FVajV9IcTyCOoYhv2QgcgE5fzWDmaAKU0eHa3RMStszNMqIdM+zUY3YdPFvRCG+VOCFFQ0N3mIfNJ3ywb3Em/BHgYNeZFnTc20OOU1Efd7eZtwBUxyWh4QLJGS6jFv04MU2bNWeCQRnoi8L7czPuG+RLJ31g0Smhsqk3dqGVa7wZZOZBb/31oQKm8ZMZRbxyU6FV9XaJmyQGZsNxBD/Ud8FXNWy/rzci9ER4Spqa5tYrit3jmNI8M2DnCRNvnm1Bu35tjcR4lH1HIDtbp+2NNuSmqCyTrBr4dMEDuH4A754LCLvOtQo44zouKp7hQagGIK+CIFgoipqPEHru6OocWJhtlAWk6GZ8cSXs64sd49qbSxMCaenu7+ZkGuftW5JJSy/FBARlh+vDF1tDfWEerYWullNDcjK5c9Q0Or/jcad++3SbbP8ueBn+S7glkOIsNTDUQf+mRxmtkoIIj6Bgb024hYtVhGPK9dDl7RsJvhPDtw3AcWzYuSd8vTcqOS8DrcUIKsUiWcDgyKghRLFgZ1E6vFhohfXfN8YP1bFVfW3N0/E0Ggu8CMNxfY9z7b5SCU4o6dMkhc4RYM61aulwU8fWSSqaIqEqEIRZBzzBSCg+HoIdncPglHgBG9ENdj4inEAXhZuBdQSYXBuWZOnfOLI61yTB1ld4Y1/+xe6Ndvo33Q+ON5Rc0GHOB+DS6gmF2XF6dkZy0eeLM8lU3EDGd6qiQRAnQnvz3wkBXFANdr5jkHMJXrS/NtTIRk5yN3wrErEMgsuNpkp13ViRZ0zdOtUOWiUJ2R/WxPibfmYk+Mp8E/XCT01y4y0/1HBfuCygMDljVzYW0DnmJKhuC8Lsg7WB7kCzQxa4y7nUF08euQblHlZMoqij93J+2yBBpzgjlzcUMJLAmeu9sOjrq+1cR7PtNvzVYrv+5WkOUvpAiqUYx+JlI6cwfPlosQyuHcGYnYECq9r2AV62O5mB9PcuItxjj2ho6uz/hcsRESbH4TX5qQv3LsigNbjJ7Huq+tkIT75enM4Mdx76mbvhXzYW54mIwOBeNd2l+eTs03nyYYIPqrhaSVGbC213xfLf4fIKAB9yqp4IW/VMvtKqVYJJrUhEeCdzGS5lbki22Wu5btYg0IwV2hrYIb0ywoN8VCTbXadDUTRz/9IJsCrfLL82CP7LQCyGcVab56HColQkCKL3ctWxTs/5+WMSAHwPGBih0rNxotqGL42R4DqLpebhKTNta7fvgWBfN7yybIoQjYdzoLPVey+RxHFtdrhfy05J2laxKjep5EAd3oqhhHNtqrm6YOoT6RKcIOXSwEdb1ojXLv35fDRQ99mYBKR6GOyu3zBhcowXTvR3+JdCWhqjVhk9FEW7Z5Wtw1vuFlwatb9XQmuD51J/MFQ+qgAheIdemSl5FoDgOuhs3o0/4vWOKcZkh/4bEJFKxAMA/6QhgsjzAvrH3+SXn0YZAinU/wuTosgK53+p+wAAAABJRU5ErkJggg=="); /* <div>Icons made by <a href="http://www.freepik.com" title="Freepik">Freepik</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div> */
+}
+.leaflet-ruler-clicked {
+ height: 35px;
+ width: 35px;
+ background-repeat: no-repeat;
+ background-position: center;
+ border-color: chartreuse !important;
+}
+.leaflet-bar.leaflet-ruler {
+ background-color: #ffffff;
+}
+.leaflet-control.leaflet-ruler {
+ cursor: pointer;
+}
+.result-tooltip {
+ background-color: white;
+ border-width: medium;
+ border-color: #de0000;
+ font-size: smaller;
+}
+.moving-tooltip {
+ background-color: rgba(255, 255, 255, .7);
+ background-clip: padding-box;
+ opacity: 0.5;
+ border: dotted;
+ border-color: red;
+ font-size: smaller;
+}
+.plus-length {
+ padding-left: 45px;
+}
+ \ No newline at end of file
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.js b/assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.js
new file mode 100644
index 0000000..847f9b8
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.js
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2022, GPL-3.0+ Project, Raruto
+ *
+ * This file is free software: you may copy, redistribute and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright (c) 2017 Goker Tanrisever
+ *
+ * Permission to use, copy, modify, and/or distribute this software
+ * for any purpose with or without fee is hereby granted, provided
+ * that the above copyright notice and this permission notice appear
+ * in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+L.Control.Ruler = L.Control.extend({
+ options: {
+ position: 'topright',
+ circleMarker: {
+ color: 'red',
+ radius: 2,
+ },
+ lineStyle: {
+ color: 'red',
+ dashArray: '1,6'
+ },
+ lengthUnit: {
+ display: 'km',
+ decimal: 2,
+ factor: 0.001, // meters -> kilometers
+ label: 'Distance:'
+ },
+ angleUnit: {
+ display: '&deg;',
+ decimal: 2,
+ factor: 360,
+ label: 'Bearing:'
+ }
+ },
+ initialize: function (options) {
+ L.setOptions(this, options);
+ this._layers = L.layerGroup();
+ this._enabled = false;
+ },
+ onAdd: function (map) {
+ this._defaultCursor = map._container.style.cursor;
+ this._map = map;
+ let container = L.DomUtil.create('div', 'leaflet-bar');
+ container.classList.add('leaflet-ruler');
+ L.DomEvent.disableClickPropagation(container);
+ L.DomEvent.on(container, 'click', this._toggleMeasure, this);
+ return this._container = container;
+ },
+ onRemove: function () {
+ L.DomEvent.off(this._container, 'click', this._toggleMeasure, this);
+ },
+ _attachMouseEvents: function () {
+ let map = this._map;
+ map.doubleClickZoom.disable();
+ L.DomEvent.on(map._container, 'keydown', this._escape, this);
+ L.DomEvent.on(map._container, 'dblclick', this._closePath, this);
+ map._container.style.cursor = 'crosshair';
+ map.on('click', this._addPoint, this);
+ map.on('mousemove', this._moving, this);
+ },
+ _removeMouseEvents: function () {
+ let map = this._map;
+ map.doubleClickZoom.enable();
+ L.DomEvent.off(map._container, 'keydown', this._escape, this);
+ L.DomEvent.off(map._container, 'dblclick', this._closePath, this);
+ map._container.style.cursor = this._defaultCursor;
+ map.off('click', this._addPoint, this);
+ map.off('mousemove', this._moving, this);
+ },
+ _disable: function () {
+ this._enabled = false;
+ this._container.classList.remove("leaflet-ruler-clicked");
+ this._layers.remove().clearLayers();
+ this._latlngs = [];
+ this._totalLength = 0;
+ this._removeMouseEvents();
+ },
+ _enable: function () {
+ this._enabled = true;
+ this._container.classList.add("leaflet-ruler-clicked");
+ this._circles = L.featureGroup().addTo(this._layers);
+ this._polyline = L.polyline([], this.options.lineStyle).addTo(this._layers);
+ this._layers.addTo(this._map);
+ this._latlngs = [];
+ this._totalLength = 0;
+ this._attachMouseEvents();
+ },
+ _toggleMeasure: function () {
+ this._enabled ? this._disable() : this._enable();
+ },
+ _drawTooltip: function (latlng, layer, incremental) {
+ let lastClick = this._latlngs[this._latlngs.length - 1] ?? latlng;
+ let bearing = this._calculateBearing(lastClick, latlng);
+ let distance = lastClick.distanceTo(latlng) * this.options.lengthUnit.factor;
+ let accumulated = this._totalLength + distance;
+ let totalLength = accumulated.toFixed(this.options.lengthUnit.decimal);
+ let plusLength = incremental ? '<br><div class="plus-length">(+' + distance.toFixed(this.options.lengthUnit.decimal) + ')</div>' : '';
+ this._totalLength = incremental ? this._totalLength : accumulated;
+ if (!layer.getTooltip()) layer.bindTooltip('', incremental ? { direction: "auto", sticky: true, offset: L.point(0, -40), className: 'moving-tooltip' } : { permanent: true, className: 'result-tooltip' }).openTooltip();
+ layer.setLatLng(latlng).setTooltipContent('<b>' + this.options.angleUnit.label + '</b>&nbsp;' + bearing.toFixed(this.options.angleUnit.decimal) + '&nbsp;' + this.options.angleUnit.display + '<br><b>' + this.options.lengthUnit.label + '</b>&nbsp;' + totalLength + '&nbsp;' + this.options.lengthUnit.display + plusLength);
+ },
+ _addPoint: function (e) {
+ let latlng = e.latlng || e;
+ let point = L.circleMarker(latlng, this.options.circleMarker).addTo(this._circles);
+ this._polyline.addLatLng(latlng);
+ if(this._latlngs.length && !latlng.equals(this._latlngs[this._latlngs.length - 1])){
+ this._drawTooltip(latlng, point, false);
+ }
+ this._latlngs.push(latlng);
+ },
+ _moving: function (e) {
+ if (this._latlngs.length) {
+ let lastCLick = this._latlngs[this._latlngs.length - 1];
+ if (!this._tempLine) this._tempLine = L.polyline([], this.options.lineStyle).addTo(this._map);
+ if (!this._tempPoint) this._tempPoint = L.circleMarker(e.latlng, this.options.circleMarker).addTo(this._map);
+ this._tempLine.setLatLngs([lastCLick, e.latlng]);
+ this._drawTooltip(e.latlng, this._tempPoint, true);
+ L.DomEvent.off(this._container, 'click', this._toggleMeasure, this);
+ }
+ },
+ _escape: function (e) {
+ if (e.keyCode === 27) {
+ if (this._latlngs.length) {
+ this._closePath();
+ } else {
+ this._enabled = true;
+ this._toggleMeasure();
+ }
+ }
+ },
+ _calculateBearing: function (start, end) {
+ const toRad = L.DomUtil.DEG_TO_RAD;
+ const toDeg = (this.options.angleUnit.factor / 2) / Math.PI;
+ let y = Math.sin((end.lng - start.lng) * toRad) * Math.cos(end.lat * toRad);
+ let x = Math.cos(start.lat * toRad) * Math.sin(end.lat * toRad) - Math.sin(start.lat * toRad) * Math.cos(end.lat * toRad) * Math.cos((end.lng - start.lng) * toRad);
+ return (Math.atan2(y, x) * toDeg + this.options.angleUnit.factor) % this.options.angleUnit.factor;
+ },
+ _closePath: function () {
+ if (this._tempLine) {
+ this._tempLine.remove();
+ this._tempLine = null;
+ }
+ if (this._tempPoint) {
+ this._tempPoint.remove();
+ this._tempLine = null;
+ }
+ if (this._latlngs.length <= 1) {
+ this._circles.remove();
+ }
+ this._enabled = false;
+ L.DomEvent.on(this._container, 'click', this._toggleMeasure, this);
+ this._toggleMeasure();
+ },
+});
+L.control.ruler = function (options) {
+ return new L.Control.Ruler(options);
+}; \ No newline at end of file
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.min.css b/assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.min.css
new file mode 100644
index 0000000..9532825
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.min.css
@@ -0,0 +1 @@
+.leaflet-ruler{height:35px;width:35px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACx0lEQVRIS52VWahOURTHXRSJzBkiJcpYxgeS4oUi90GR4VK6DzcUD3gQIQ8UCXkwlCJRkjJEpqT7oGTMeHONmacyRRl/v9ve2n2d7/vOtevXPmefff5r7bXXXruiSf42gKkToTu0hY/wEk7CnWIyFWX0B/N9OkyF1tANdsEz6AIL4Sl8h8NwEK6nmsUMNGPSalgePFxLfwl2hrG39O1hM8yFobAKpsAGWAE/NJRloBfj++ErnIYesDj1qsjzRsYN2VhwdTPgUaGBkUF0e/D0TxDrRD8ehsFw6AcP4FrgCL17EpsrWAKDUgNtwuQP9FehJsyeSb8FbsOV8O0efVeYHOZV0h8N8w3dWXBOVWpgDwO/oBqc1BxciV7Pg3OJhz66ivPB+PoM8TnqRQPTeNkKfeALtAQ39QYsgE85xNsFz+voFe8AldFALS8DYU3waAd9b5gAv3OKn2HefagK4q64VgPm9k0we0wtN9pN6w+mY9qywqLnWeKXGa/WwHwYFSwrZtw/w9Ic4p5oxesLPG8Qd/UacMf13IE38ArGwN3EQJbnintOHsJsMOaG5Z+4/2vAY282eALd4FvQqoj4Aca3gQfPw1hSPBr4xoNH3bz1EO0D428r9LwpY3vB+nSolOfRQVeg1yOCgXH01pu+GeL+Y1hOwWOYBZlhieJxBU94eA2Lwo9WRw+X8fUEx0PUaPFowKW6Cg+UBc6M6AzrMsR1xtJR1vO4CkNk1TMLJoXBlcHQpvAePW+0eFyBRe45jAbL7fskhqm4MbdGmYruUU94l8zNfIyl4hhfLQteHqaiLYq7J4YlipvnXkgWRotgyRYNWB4st6bpizLiHsiO4H1gMrhnRVtarpcxy0vd+9eLPMvzhuMf1E7QmyC78xrQmDk+BC6Am98CLoKXi/dvWll1xjvjeAkD9YVXpkJ6ae7/BOPus4b+p9X9BdB/zv8zawwXAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center}.leaflet-ruler-clicked,.leaflet-ruler:hover{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAE/ElEQVRIS6WVeWwUVRzHfzOzs7Pdi+7Rdq/ubrH0sNaKBFMCSEvBQLivFkhAE8GDYAyHATWgETUoMZ7BGCMBNKYiQbTKpYQKBqKlWmBbadmW7bbbFjv0mu2eM298M5SlpS3U+LL/zL6Zz/f7vr/3e4+AsQ6j80EgxTkKhGwkQRqQiLp5UtEOSDwOXf660TDEPfkGR75RTT8VF1EpJRJ6LsbrVuaZg2YtrWND8WD5FVajV9IcTyCOoYhv2QgcgE5fzWDmaAKU0eHa3RMStszNMqIdM+zUY3YdPFvRCG+VOCFFQ0N3mIfNJ3ywb3Em/BHgYNeZFnTc20OOU1Efd7eZtwBUxyWh4QLJGS6jFv04MU2bNWeCQRnoi8L7czPuG+RLJ31g0Smhsqk3dqGVa7wZZOZBb/31oQKm8ZMZRbxyU6FV9XaJmyQGZsNxBD/Ud8FXNWy/rzci9ER4Spqa5tYrit3jmNI8M2DnCRNvnm1Bu35tjcR4lH1HIDtbp+2NNuSmqCyTrBr4dMEDuH4A754LCLvOtQo44zouKp7hQagGIK+CIFgoipqPEHru6OocWJhtlAWk6GZ8cSXs64sd49qbSxMCaenu7+ZkGuftW5JJSy/FBARlh+vDF1tDfWEerYWullNDcjK5c9Q0Or/jcad++3SbbP8ueBn+S7glkOIsNTDUQf+mRxmtkoIIj6Bgb024hYtVhGPK9dDl7RsJvhPDtw3AcWzYuSd8vTcqOS8DrcUIKsUiWcDgyKghRLFgZ1E6vFhohfXfN8YP1bFVfW3N0/E0Ggu8CMNxfY9z7b5SCU4o6dMkhc4RYM61aulwU8fWSSqaIqEqEIRZBzzBSCg+HoIdncPglHgBG9ENdj4inEAXhZuBdQSYXBuWZOnfOLI61yTB1ld4Y1/+xe6Ndvo33Q+ON5Rc0GHOB+DS6gmF2XF6dkZy0eeLM8lU3EDGd6qiQRAnQnvz3wkBXFANdr5jkHMJXrS/NtTIRk5yN3wrErEMgsuNpkp13ViRZ0zdOtUOWiUJ2R/WxPibfmYk+Mp8E/XCT01y4y0/1HBfuCygMDljVzYW0DnmJKhuC8Lsg7WB7kCzQxa4y7nUF08euQblHlZMoqij93J+2yBBpzgjlzcUMJLAmeu9sOjrq+1cR7PtNvzVYrv+5WkOUvpAiqUYx+JlI6cwfPlosQyuHcGYnYECq9r2AV62O5mB9PcuItxjj2ho6uz/hcsRESbH4TX5qQv3LsigNbjJ7Huq+tkIT75enM4Mdx76mbvhXzYW54mIwOBeNd2l+eTs03nyYYIPqrhaSVGbC213xfLf4fIKAB9yqp4IW/VMvtKqVYJJrUhEeCdzGS5lbki22Wu5btYg0IwV2hrYIb0ywoN8VCTbXadDUTRz/9IJsCrfLL82CP7LQCyGcVab56HColQkCKL3ctWxTs/5+WMSAHwPGBih0rNxotqGL42R4DqLpebhKTNta7fvgWBfN7yybIoQjYdzoLPVey+RxHFtdrhfy05J2laxKjep5EAd3oqhhHNtqrm6YOoT6RKcIOXSwEdb1ojXLv35fDRQ99mYBKR6GOyu3zBhcowXTvR3+JdCWhqjVhk9FEW7Z5Wtw1vuFlwatb9XQmuD51J/MFQ+qgAheIdemSl5FoDgOuhs3o0/4vWOKcZkh/4bEJFKxAMA/6QhgsjzAvrH3+SXn0YZAinU/wuTosgK53+p+wAAAABJRU5ErkJggg==)}.leaflet-ruler-clicked{height:35px;width:35px;background-repeat:no-repeat;background-position:center;border-color:#7fff00!important}.leaflet-bar.leaflet-ruler{background-color:#fff}.leaflet-control.leaflet-ruler{cursor:pointer}.result-tooltip{background-color:#fff;border-width:medium;border-color:#de0000;font-size:smaller}.moving-tooltip{background-color:rgba(255,255,255,.7);background-clip:padding-box;opacity:.5;border:dotted;border-color:red;font-size:smaller}.plus-length{padding-left:45px} \ No newline at end of file
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.min.js b/assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.min.js
new file mode 100644
index 0000000..e732020
--- /dev/null
+++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-ruler.min.js
@@ -0,0 +1 @@
+L.Control.Ruler=L.Control.extend({options:{position:"topright",circleMarker:{color:"red",radius:2},lineStyle:{color:"red",dashArray:"1,6"},lengthUnit:{display:"km",decimal:2,factor:.001,label:"Distance:"},angleUnit:{display:"&deg;",decimal:2,factor:360,label:"Bearing:"}},initialize:function(options){L.setOptions(this,options),this._layers=L.layerGroup(),this._enabled=!1},onAdd:function(map){this._defaultCursor=map._container.style.cursor,this._map=map;let container=L.DomUtil.create("div","leaflet-bar");return container.classList.add("leaflet-ruler"),L.DomEvent.disableClickPropagation(container),L.DomEvent.on(container,"click",this._toggleMeasure,this),this._container=container},onRemove:function(){L.DomEvent.off(this._container,"click",this._toggleMeasure,this)},_attachMouseEvents:function(){let map=this._map;map.doubleClickZoom.disable(),L.DomEvent.on(map._container,"keydown",this._escape,this),L.DomEvent.on(map._container,"dblclick",this._closePath,this),map._container.style.cursor="crosshair",map.on("click",this._addPoint,this),map.on("mousemove",this._moving,this)},_removeMouseEvents:function(){let map=this._map;map.doubleClickZoom.enable(),L.DomEvent.off(map._container,"keydown",this._escape,this),L.DomEvent.off(map._container,"dblclick",this._closePath,this),map._container.style.cursor=this._defaultCursor,map.off("click",this._addPoint,this),map.off("mousemove",this._moving,this)},_disable:function(){this._enabled=!1,this._container.classList.remove("leaflet-ruler-clicked"),this._map.removeLayer(this._layers),this._layers=L.layerGroup(),this._latlngs=[],this._totalLength=0,this._removeMouseEvents()},_enable:function(){this._enabled=!0,this._container.classList.add("leaflet-ruler-clicked"),this._circles=L.featureGroup().addTo(this._layers),this._polyline=L.polyline([],this.options.lineStyle).addTo(this._layers),this._layers.addTo(this._map),this._latlngs=[],this._totalLength=0,this._attachMouseEvents()},_toggleMeasure:function(){this._enabled?this._disable():this._enable()},_drawTooltip:function(latlng,layer,incremental){let clickCount=this._latlngs.length,lastClick=clickCount?this._latlngs[clickCount-1]:latlng,bearing=this._calculateBearing(lastClick,latlng),distance=lastClick.distanceTo(latlng)*this.options.lengthUnit.factor,totalLength,plusLength;incremental?(totalLength=(clickCount?distance+this._totalLength||0:distance).toFixed(this.options.lengthUnit.decimal),plusLength=clickCount?'<br><div class="plus-length">(+'+distance.toFixed(this.options.lengthUnit.decimal)+")</div>":""):(this._totalLength+=distance,totalLength=(clickCount?this._totalLength:distance).toFixed(this.options.lengthUnit.decimal),plusLength="");var text="<b>"+this.options.angleUnit.label+"</b>&nbsp;"+bearing.toFixed(this.options.angleUnit.decimal)+"&nbsp;"+this.options.angleUnit.display+"<br><b>"+this.options.lengthUnit.label+"</b>&nbsp;"+totalLength+"&nbsp;"+this.options.lengthUnit.display+plusLength;layer.setLatLng(latlng);let tooltip=layer.getTooltip();return tooltip?tooltip.setTooltipContent(text):layer.bindTooltip(text,incremental?{direction:"auto",sticky:!0,offset:L.point(0,-40),className:"moving-tooltip"}:{permanent:!0,className:"result-tooltip"}).openTooltip(),layer},_addPoint:function(e){let latlng=e.latlng||e,point=L.circleMarker(latlng,this.options.circleMarker).addTo(this._circles);this._polyline.addLatLng(latlng),this._latlngs.length&&!latlng.equals(this._latlngs[this._latlngs.length-1])&&this._drawTooltip(latlng,point,!1),this._latlngs.push(latlng)},_moving:function(e){if(this._latlngs.length){let lastCLick=this._latlngs[this._latlngs.length-1];this._tempLine||(this._tempLine=L.polyline([],this.options.lineStyle).addTo(this._map)),this._tempPoint||(this._tempPoint=L.circleMarker(e.latlng,this.options.circleMarker).addTo(this._map)),this._tempLine.setLatLngs([lastCLick,e.latlng]),this._drawTooltip(e.latlng,this._tempPoint,!0),L.DomEvent.off(this._container,"click",this._toggleMeasure,this)}},_escape:function(e){27===e.keyCode&&(this._latlngs.length?this._closePath():(this._enabled=!0,this._toggleMeasure()))},_calculateBearing:function(start,end){const toRad=L.DomUtil.DEG_TO_RAD,toDeg=this.options.angleUnit.factor/2/Math.PI;let y=Math.sin((end.lng-start.lng)*toRad)*Math.cos(end.lat*toRad),x=Math.cos(start.lat*toRad)*Math.sin(end.lat*toRad)-Math.sin(start.lat*toRad)*Math.cos(end.lat*toRad)*Math.cos((end.lng-start.lng)*toRad);return(Math.atan2(y,x)*toDeg+this.options.angleUnit.factor)%this.options.angleUnit.factor},_closePath:function(){this._tempLine&&(this._map.removeLayer(this._tempLine),this._tempLine=null),this._tempPoint&&(this._map.removeLayer(this._tempPoint),this._tempLine=null),this._latlngs.length<=1&&this._map.removeLayer(this._circles),this._enabled=!1,L.DomEvent.on(this._container,"click",this._toggleMeasure,this),this._toggleMeasure()}}),L.control.ruler=function(options){return new L.Control.Ruler(options)}; \ No newline at end of file