fixes leaflet-elevation in build
This commit is contained in:
2
web/static/vendor/leaflet-elevation/libs/d3.min.js
vendored
Normal file
2
web/static/vendor/leaflet-elevation/libs/d3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
314
web/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.js
vendored
Normal file
314
web/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.js
vendored
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
});
|
||||
1
web/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.min.js
vendored
Normal file
1
web/static/vendor/leaflet-elevation/libs/leaflet-distance-marker.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
636
web/static/vendor/leaflet-elevation/libs/leaflet-edgescale.js
vendored
Normal file
636
web/static/vendor/leaflet-elevation/libs/leaflet-edgescale.js
vendored
Normal file
@@ -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);
|
||||
}
|
||||
});
|
||||
1
web/static/vendor/leaflet-elevation/libs/leaflet-edgescale.min.js
vendored
Normal file
1
web/static/vendor/leaflet-elevation/libs/leaflet-edgescale.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
347
web/static/vendor/leaflet-elevation/libs/leaflet-gpxgroup.js
vendored
Normal file
347
web/static/vendor/leaflet-elevation/libs/leaflet-gpxgroup.js
vendored
Normal file
@@ -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);
|
||||
1
web/static/vendor/leaflet-elevation/libs/leaflet-gpxgroup.min.js
vendored
Normal file
1
web/static/vendor/leaflet-elevation/libs/leaflet-gpxgroup.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
457
web/static/vendor/leaflet-elevation/libs/leaflet-hotline.js
vendored
Normal file
457
web/static/vendor/leaflet-elevation/libs/leaflet-hotline.js
vendored
Normal file
@@ -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 - <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;
|
||||
}));
|
||||
1
web/static/vendor/leaflet-elevation/libs/leaflet-hotline.min.js
vendored
Normal file
1
web/static/vendor/leaflet-elevation/libs/leaflet-hotline.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
183
web/static/vendor/leaflet-elevation/libs/leaflet-ruler.js
vendored
Normal file
183
web/static/vendor/leaflet-elevation/libs/leaflet-ruler.js
vendored
Normal file
@@ -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: '°',
|
||||
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> ' + bearing.toFixed(this.options.angleUnit.decimal) + ' ' + this.options.angleUnit.display + '<br><b>' + this.options.lengthUnit.label + '</b> ' + totalLength + ' ' + 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);
|
||||
};
|
||||
1
web/static/vendor/leaflet-elevation/libs/leaflet-ruler.min.js
vendored
Normal file
1
web/static/vendor/leaflet-elevation/libs/leaflet-ruler.min.js
vendored
Normal file
@@ -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:"°",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> "+bearing.toFixed(this.options.angleUnit.decimal)+" "+this.options.angleUnit.display+"<br><b>"+this.options.lengthUnit.label+"</b> "+totalLength+" "+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)};
|
||||
150
web/static/vendor/leaflet-elevation/libs/leaflet.almostover.js
vendored
Normal file
150
web/static/vendor/leaflet-elevation/libs/leaflet.almostover.js
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
L.Map.mergeOptions({
|
||||
// @option almostOver: Boolean = true
|
||||
// Set it to false to disable this plugin
|
||||
almostOver: true,
|
||||
// @option almostDistance: Number = 25
|
||||
// Tolerance in pixels
|
||||
almostDistance: 25, // pixels
|
||||
// @option almostSamplingPeriod: Number = 50
|
||||
// To reduce the 'mousemove' event frequency. In milliseconds
|
||||
almostSamplingPeriod: 50, // ms
|
||||
// @option almostOnMouseMove Boolean = true
|
||||
// Set it to false to disable track 'mousemove' events and improve performance
|
||||
// if AlmostOver is only need for 'click' events.
|
||||
almostOnMouseMove: true,
|
||||
});
|
||||
|
||||
|
||||
L.Handler.AlmostOver = L.Handler.extend({
|
||||
|
||||
includes: L.Evented || L.Mixin.Events,
|
||||
|
||||
initialize: function (map) {
|
||||
this._map = map;
|
||||
this._layers = [];
|
||||
this._previous = null;
|
||||
this._marker = null;
|
||||
this._buffer = 0;
|
||||
|
||||
// Reduce 'mousemove' event frequency
|
||||
this.__mouseMoveSampling = (function () {
|
||||
var timer = new Date();
|
||||
return function (e) {
|
||||
var date = new Date(),
|
||||
filtered = (date - timer) < this._map.options.almostSamplingPeriod;
|
||||
if (filtered || this._layers.length === 0) {
|
||||
return; // Ignore movement
|
||||
}
|
||||
timer = date;
|
||||
this._map.fire('mousemovesample', {latlng: e.latlng});
|
||||
};
|
||||
})();
|
||||
},
|
||||
|
||||
addHooks: function () {
|
||||
if (this._map.options.almostOnMouseMove) {
|
||||
this._map.on('mousemove', this.__mouseMoveSampling, this);
|
||||
this._map.on('mousemovesample', this._onMouseMove, this);
|
||||
}
|
||||
this._map.on('click dblclick', this._onMouseClick, this);
|
||||
|
||||
var map = this._map;
|
||||
function computeBuffer() {
|
||||
this._buffer = this._map.layerPointToLatLng([0, 0]).lat -
|
||||
this._map.layerPointToLatLng([this._map.options.almostDistance,
|
||||
this._map.options.almostDistance]).lat;
|
||||
}
|
||||
this._map.on('viewreset zoomend', computeBuffer, this);
|
||||
this._map.whenReady(computeBuffer, this);
|
||||
},
|
||||
|
||||
removeHooks: function () {
|
||||
this._map.off('mousemovesample');
|
||||
this._map.off('mousemove', this.__mouseMoveSampling, this);
|
||||
this._map.off('click dblclick', this._onMouseClick, this);
|
||||
},
|
||||
|
||||
addLayer: function (layer) {
|
||||
if (typeof layer.eachLayer == 'function') {
|
||||
layer.eachLayer(function (l) {
|
||||
this.addLayer(l);
|
||||
}, this);
|
||||
}
|
||||
else {
|
||||
if (typeof this.indexLayer == 'function') {
|
||||
this.indexLayer(layer);
|
||||
}
|
||||
this._layers.push(layer);
|
||||
}
|
||||
},
|
||||
|
||||
removeLayer: function (layer) {
|
||||
if (typeof layer.eachLayer == 'function') {
|
||||
layer.eachLayer(function (l) {
|
||||
this.removeLayer(l);
|
||||
}, this);
|
||||
}
|
||||
else {
|
||||
if (typeof this.unindexLayer == 'function') {
|
||||
this.unindexLayer(layer);
|
||||
}
|
||||
var index = this._layers.indexOf(layer);
|
||||
if (0 <= index) {
|
||||
this._layers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
this._previous = null;
|
||||
},
|
||||
|
||||
getClosest: function (latlng) {
|
||||
var snapfunc = L.GeometryUtil.closestLayerSnap,
|
||||
distance = this._map.options.almostDistance;
|
||||
|
||||
var snaplist = [];
|
||||
if (typeof this.searchBuffer == 'function') {
|
||||
snaplist = this.searchBuffer(latlng, this._buffer);
|
||||
}
|
||||
else {
|
||||
snaplist = this._layers;
|
||||
}
|
||||
return snapfunc(this._map, snaplist, latlng, distance, false);
|
||||
},
|
||||
|
||||
_onMouseMove: function (e) {
|
||||
var closest = this.getClosest(e.latlng);
|
||||
if (closest) {
|
||||
if (!this._previous) {
|
||||
this._map.fire('almost:over', {layer: closest.layer,
|
||||
latlng: closest.latlng});
|
||||
}
|
||||
else if (L.stamp(this._previous.layer) != L.stamp(closest.layer)) {
|
||||
this._map.fire('almost:out', {layer: this._previous.layer});
|
||||
this._map.fire('almost:over', {layer: closest.layer,
|
||||
latlng: closest.latlng});
|
||||
}
|
||||
|
||||
this._map.fire('almost:move', {layer: closest.layer,
|
||||
latlng: closest.latlng});
|
||||
}
|
||||
else {
|
||||
if (this._previous) {
|
||||
this._map.fire('almost:out', {layer: this._previous.layer});
|
||||
}
|
||||
}
|
||||
this._previous = closest;
|
||||
},
|
||||
|
||||
_onMouseClick: function (e) {
|
||||
var closest = this.getClosest(e.latlng);
|
||||
if (closest) {
|
||||
this._map.fire('almost:' + e.type, {layer: closest.layer,
|
||||
latlng: closest.latlng});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (L.LayerIndexMixin !== undefined) {
|
||||
L.Handler.AlmostOver.include(L.LayerIndexMixin);
|
||||
}
|
||||
|
||||
L.Map.addInitHook('addHandler', 'almostOver', L.Handler.AlmostOver);
|
||||
767
web/static/vendor/leaflet-elevation/libs/leaflet.geometryutil.js
vendored
Normal file
767
web/static/vendor/leaflet-elevation/libs/leaflet.geometryutil.js
vendored
Normal file
@@ -0,0 +1,767 @@
|
||||
// Packaging/modules magic dance.
|
||||
(function (factory) {
|
||||
var L;
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD
|
||||
define(['leaflet'], factory);
|
||||
} else if (typeof module !== 'undefined') {
|
||||
// Node/CommonJS
|
||||
L = require('leaflet');
|
||||
module.exports = factory(L);
|
||||
} else {
|
||||
// Browser globals
|
||||
if (typeof window.L === 'undefined')
|
||||
throw 'Leaflet must be loaded first';
|
||||
factory(window.L);
|
||||
}
|
||||
}(function (L) {
|
||||
"use strict";
|
||||
|
||||
L.Polyline._flat = L.LineUtil.isFlat || L.Polyline._flat || function (latlngs) {
|
||||
// true if it's a flat array of latlngs; false if nested
|
||||
return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
|
||||
};
|
||||
|
||||
/**
|
||||
* @fileOverview Leaflet Geometry utilities for distances and linear referencing.
|
||||
* @name L.GeometryUtil
|
||||
*/
|
||||
|
||||
L.GeometryUtil = L.extend(L.GeometryUtil || {}, {
|
||||
|
||||
/**
|
||||
Shortcut function for planar distance between two {L.LatLng} at current zoom.
|
||||
|
||||
@tutorial distance-length
|
||||
|
||||
@param {L.Map} map Leaflet map to be used for this method
|
||||
@param {L.LatLng} latlngA geographical point A
|
||||
@param {L.LatLng} latlngB geographical point B
|
||||
@returns {Number} planar distance
|
||||
*/
|
||||
distance: function (map, latlngA, latlngB) {
|
||||
return map.latLngToLayerPoint(latlngA).distanceTo(map.latLngToLayerPoint(latlngB));
|
||||
},
|
||||
|
||||
/**
|
||||
Shortcut function for planar distance between a {L.LatLng} and a segment (A-B).
|
||||
@param {L.Map} map Leaflet map to be used for this method
|
||||
@param {L.LatLng} latlng - The position to search
|
||||
@param {L.LatLng} latlngA geographical point A of the segment
|
||||
@param {L.LatLng} latlngB geographical point B of the segment
|
||||
@returns {Number} planar distance
|
||||
*/
|
||||
distanceSegment: function (map, latlng, latlngA, latlngB) {
|
||||
var p = map.latLngToLayerPoint(latlng),
|
||||
p1 = map.latLngToLayerPoint(latlngA),
|
||||
p2 = map.latLngToLayerPoint(latlngB);
|
||||
return L.LineUtil.pointToSegmentDistance(p, p1, p2);
|
||||
},
|
||||
|
||||
/**
|
||||
Shortcut function for converting distance to readable distance.
|
||||
@param {Number} distance distance to be converted
|
||||
@param {String} unit 'metric' or 'imperial'
|
||||
@returns {String} in yard or miles
|
||||
*/
|
||||
readableDistance: function (distance, unit) {
|
||||
var isMetric = (unit !== 'imperial'),
|
||||
distanceStr;
|
||||
if (isMetric) {
|
||||
// show metres when distance is < 1km, then show km
|
||||
if (distance > 1000) {
|
||||
distanceStr = (distance / 1000).toFixed(2) + ' km';
|
||||
}
|
||||
else {
|
||||
distanceStr = distance.toFixed(1) + ' m';
|
||||
}
|
||||
}
|
||||
else {
|
||||
distance *= 1.09361;
|
||||
if (distance > 1760) {
|
||||
distanceStr = (distance / 1760).toFixed(2) + ' miles';
|
||||
}
|
||||
else {
|
||||
distanceStr = distance.toFixed(1) + ' yd';
|
||||
}
|
||||
}
|
||||
return distanceStr;
|
||||
},
|
||||
|
||||
/**
|
||||
Returns true if the latlng belongs to segment A-B
|
||||
@param {L.LatLng} latlng - The position to search
|
||||
@param {L.LatLng} latlngA geographical point A of the segment
|
||||
@param {L.LatLng} latlngB geographical point B of the segment
|
||||
@param {?Number} [tolerance=0.2] tolerance to accept if latlng belongs really
|
||||
@returns {boolean}
|
||||
*/
|
||||
belongsSegment: function(latlng, latlngA, latlngB, tolerance) {
|
||||
tolerance = tolerance === undefined ? 0.2 : tolerance;
|
||||
var hypotenuse = latlngA.distanceTo(latlngB),
|
||||
delta = latlngA.distanceTo(latlng) + latlng.distanceTo(latlngB) - hypotenuse;
|
||||
return delta/hypotenuse < tolerance;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns total length of line
|
||||
* @tutorial distance-length
|
||||
*
|
||||
* @param {L.Polyline|Array<L.Point>|Array<L.LatLng>} coords Set of coordinates
|
||||
* @returns {Number} Total length (pixels for Point, meters for LatLng)
|
||||
*/
|
||||
length: function (coords) {
|
||||
var accumulated = L.GeometryUtil.accumulatedLengths(coords);
|
||||
return accumulated.length > 0 ? accumulated[accumulated.length-1] : 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a list of accumulated length along a line.
|
||||
* @param {L.Polyline|Array<L.Point>|Array<L.LatLng>} coords Set of coordinates
|
||||
* @returns {Array<Number>} Array of accumulated lengths (pixels for Point, meters for LatLng)
|
||||
*/
|
||||
accumulatedLengths: function (coords) {
|
||||
if (typeof coords.getLatLngs == 'function') {
|
||||
coords = coords.getLatLngs();
|
||||
}
|
||||
if (coords.length === 0)
|
||||
return [];
|
||||
var total = 0,
|
||||
lengths = [0];
|
||||
for (var i = 0, n = coords.length - 1; i< n; i++) {
|
||||
total += coords[i].distanceTo(coords[i+1]);
|
||||
lengths.push(total);
|
||||
}
|
||||
return lengths;
|
||||
},
|
||||
|
||||
/**
|
||||
Returns the closest point of a {L.LatLng} on the segment (A-B)
|
||||
|
||||
@tutorial closest
|
||||
|
||||
@param {L.Map} map Leaflet map to be used for this method
|
||||
@param {L.LatLng} latlng - The position to search
|
||||
@param {L.LatLng} latlngA geographical point A of the segment
|
||||
@param {L.LatLng} latlngB geographical point B of the segment
|
||||
@returns {L.LatLng} Closest geographical point
|
||||
*/
|
||||
closestOnSegment: function (map, latlng, latlngA, latlngB) {
|
||||
var maxzoom = map.getMaxZoom();
|
||||
if (maxzoom === Infinity)
|
||||
maxzoom = map.getZoom();
|
||||
var p = map.project(latlng, maxzoom),
|
||||
p1 = map.project(latlngA, maxzoom),
|
||||
p2 = map.project(latlngB, maxzoom),
|
||||
closest = L.LineUtil.closestPointOnSegment(p, p1, p2);
|
||||
return map.unproject(closest, maxzoom);
|
||||
},
|
||||
|
||||
/**
|
||||
Returns the closest latlng on layer.
|
||||
|
||||
Accept nested arrays
|
||||
|
||||
@tutorial closest
|
||||
|
||||
@param {L.Map} map Leaflet map to be used for this method
|
||||
@param {Array<L.LatLng>|Array<Array<L.LatLng>>|L.PolyLine|L.Polygon} layer - Layer that contains the result
|
||||
@param {L.LatLng} latlng - The position to search
|
||||
@param {?boolean} [vertices=false] - Whether to restrict to path vertices.
|
||||
@returns {L.LatLng} Closest geographical point or null if layer param is incorrect
|
||||
*/
|
||||
closest: function (map, layer, latlng, vertices) {
|
||||
|
||||
var latlngs,
|
||||
mindist = Infinity,
|
||||
result = null,
|
||||
i, n, distance, subResult;
|
||||
|
||||
if (layer instanceof Array) {
|
||||
// if layer is Array<Array<T>>
|
||||
if (layer[0] instanceof Array && typeof layer[0][0] !== 'number') {
|
||||
// if we have nested arrays, we calc the closest for each array
|
||||
// recursive
|
||||
for (i = 0; i < layer.length; i++) {
|
||||
subResult = L.GeometryUtil.closest(map, layer[i], latlng, vertices);
|
||||
if (subResult && subResult.distance < mindist) {
|
||||
mindist = subResult.distance;
|
||||
result = subResult;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} else if (layer[0] instanceof L.LatLng
|
||||
|| typeof layer[0][0] === 'number'
|
||||
|| typeof layer[0].lat === 'number') { // we could have a latlng as [x,y] with x & y numbers or {lat, lng}
|
||||
layer = L.polyline(layer);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// if we don't have here a Polyline, that means layer is incorrect
|
||||
// see https://github.com/makinacorpus/Leaflet.GeometryUtil/issues/23
|
||||
if (! ( layer instanceof L.Polyline ) )
|
||||
return result;
|
||||
|
||||
// deep copy of latlngs
|
||||
latlngs = JSON.parse(JSON.stringify(layer.getLatLngs().slice(0)));
|
||||
|
||||
// add the last segment for L.Polygon
|
||||
if (layer instanceof L.Polygon) {
|
||||
// add the last segment for each child that is a nested array
|
||||
var addLastSegment = function(latlngs) {
|
||||
if (L.Polyline._flat(latlngs)) {
|
||||
latlngs.push(latlngs[0]);
|
||||
} else {
|
||||
for (var i = 0; i < latlngs.length; i++) {
|
||||
addLastSegment(latlngs[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
addLastSegment(latlngs);
|
||||
}
|
||||
|
||||
// we have a multi polygon / multi polyline / polygon with holes
|
||||
// use recursive to explore and return the good result
|
||||
if ( ! L.Polyline._flat(latlngs) ) {
|
||||
for (i = 0; i < latlngs.length; i++) {
|
||||
// if we are at the lower level, and if we have a L.Polygon, we add the last segment
|
||||
subResult = L.GeometryUtil.closest(map, latlngs[i], latlng, vertices);
|
||||
if (subResult.distance < mindist) {
|
||||
mindist = subResult.distance;
|
||||
result = subResult;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
} else {
|
||||
|
||||
// Lookup vertices
|
||||
if (vertices) {
|
||||
for(i = 0, n = latlngs.length; i < n; i++) {
|
||||
var ll = latlngs[i];
|
||||
distance = L.GeometryUtil.distance(map, latlng, ll);
|
||||
if (distance < mindist) {
|
||||
mindist = distance;
|
||||
result = ll;
|
||||
result.distance = distance;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Keep the closest point of all segments
|
||||
for (i = 0, n = latlngs.length; i < n-1; i++) {
|
||||
var latlngA = latlngs[i],
|
||||
latlngB = latlngs[i+1];
|
||||
distance = L.GeometryUtil.distanceSegment(map, latlng, latlngA, latlngB);
|
||||
if (distance <= mindist) {
|
||||
mindist = distance;
|
||||
result = L.GeometryUtil.closestOnSegment(map, latlng, latlngA, latlngB);
|
||||
result.distance = distance;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
Returns the closest layer to latlng among a list of layers.
|
||||
|
||||
@tutorial closest
|
||||
|
||||
@param {L.Map} map Leaflet map to be used for this method
|
||||
@param {Array<L.ILayer>} layers Set of layers
|
||||
@param {L.LatLng} latlng - The position to search
|
||||
@returns {object} ``{layer, latlng, distance}`` or ``null`` if list is empty;
|
||||
*/
|
||||
closestLayer: function (map, layers, latlng) {
|
||||
var mindist = Infinity,
|
||||
result = null,
|
||||
ll = null,
|
||||
distance = Infinity;
|
||||
|
||||
for (var i = 0, n = layers.length; i < n; i++) {
|
||||
var layer = layers[i];
|
||||
if (layer instanceof L.LayerGroup) {
|
||||
// recursive
|
||||
var subResult = L.GeometryUtil.closestLayer(map, layer.getLayers(), latlng);
|
||||
if (subResult.distance < mindist) {
|
||||
mindist = subResult.distance;
|
||||
result = subResult;
|
||||
}
|
||||
} else {
|
||||
// Single dimension, snap on points, else snap on closest
|
||||
if (typeof layer.getLatLng == 'function') {
|
||||
ll = layer.getLatLng();
|
||||
distance = L.GeometryUtil.distance(map, latlng, ll);
|
||||
}
|
||||
else {
|
||||
ll = L.GeometryUtil.closest(map, layer, latlng);
|
||||
if (ll) distance = ll.distance; // Can return null if layer has no points.
|
||||
}
|
||||
if (distance < mindist) {
|
||||
mindist = distance;
|
||||
result = {layer: layer, latlng: ll, distance: distance};
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
Returns the n closest layers to latlng among a list of input layers.
|
||||
|
||||
@param {L.Map} map - Leaflet map to be used for this method
|
||||
@param {Array<L.ILayer>} layers - Set of layers
|
||||
@param {L.LatLng} latlng - The position to search
|
||||
@param {?Number} [n=layers.length] - the expected number of output layers.
|
||||
@returns {Array<object>} an array of objects ``{layer, latlng, distance}`` or ``null`` if the input is invalid (empty list or negative n)
|
||||
*/
|
||||
nClosestLayers: function (map, layers, latlng, n) {
|
||||
n = typeof n === 'number' ? n : layers.length;
|
||||
|
||||
if (n < 1 || layers.length < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var results = [];
|
||||
var distance, ll;
|
||||
|
||||
for (var i = 0, m = layers.length; i < m; i++) {
|
||||
var layer = layers[i];
|
||||
if (layer instanceof L.LayerGroup) {
|
||||
// recursive
|
||||
var subResult = L.GeometryUtil.closestLayer(map, layer.getLayers(), latlng);
|
||||
results.push(subResult);
|
||||
} else {
|
||||
// Single dimension, snap on points, else snap on closest
|
||||
if (typeof layer.getLatLng == 'function') {
|
||||
ll = layer.getLatLng();
|
||||
distance = L.GeometryUtil.distance(map, latlng, ll);
|
||||
}
|
||||
else {
|
||||
ll = L.GeometryUtil.closest(map, layer, latlng);
|
||||
if (ll) distance = ll.distance; // Can return null if layer has no points.
|
||||
}
|
||||
results.push({layer: layer, latlng: ll, distance: distance});
|
||||
}
|
||||
}
|
||||
|
||||
results.sort(function(a, b) {
|
||||
return a.distance - b.distance;
|
||||
});
|
||||
|
||||
if (results.length > n) {
|
||||
return results.slice(0, n);
|
||||
} else {
|
||||
return results;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns all layers within a radius of the given position, in an ascending order of distance.
|
||||
@param {L.Map} map Leaflet map to be used for this method
|
||||
@param {Array<ILayer>} layers - A list of layers.
|
||||
@param {L.LatLng} latlng - The position to search
|
||||
@param {?Number} [radius=Infinity] - Search radius in pixels
|
||||
@return {object[]} an array of objects including layer within the radius, closest latlng, and distance
|
||||
*/
|
||||
layersWithin: function(map, layers, latlng, radius) {
|
||||
radius = typeof radius == 'number' ? radius : Infinity;
|
||||
|
||||
var results = [];
|
||||
var ll = null;
|
||||
var distance = 0;
|
||||
|
||||
for (var i = 0, n = layers.length; i < n; i++) {
|
||||
var layer = layers[i];
|
||||
|
||||
if (typeof layer.getLatLng == 'function') {
|
||||
ll = layer.getLatLng();
|
||||
distance = L.GeometryUtil.distance(map, latlng, ll);
|
||||
}
|
||||
else {
|
||||
ll = L.GeometryUtil.closest(map, layer, latlng);
|
||||
if (ll) distance = ll.distance; // Can return null if layer has no points.
|
||||
}
|
||||
|
||||
if (ll && distance < radius) {
|
||||
results.push({layer: layer, latlng: ll, distance: distance});
|
||||
}
|
||||
}
|
||||
|
||||
var sortedResults = results.sort(function(a, b) {
|
||||
return a.distance - b.distance;
|
||||
});
|
||||
|
||||
return sortedResults;
|
||||
},
|
||||
|
||||
/**
|
||||
Returns the closest position from specified {LatLng} among specified layers,
|
||||
with a maximum tolerance in pixels, providing snapping behaviour.
|
||||
|
||||
@tutorial closest
|
||||
|
||||
@param {L.Map} map Leaflet map to be used for this method
|
||||
@param {Array<ILayer>} layers - A list of layers to snap on.
|
||||
@param {L.LatLng} latlng - The position to snap
|
||||
@param {?Number} [tolerance=Infinity] - Maximum number of pixels.
|
||||
@param {?boolean} [withVertices=true] - Snap to layers vertices or segment points (not only vertex)
|
||||
@returns {object} with snapped {LatLng} and snapped {Layer} or null if tolerance exceeded.
|
||||
*/
|
||||
closestLayerSnap: function (map, layers, latlng, tolerance, withVertices) {
|
||||
tolerance = typeof tolerance == 'number' ? tolerance : Infinity;
|
||||
withVertices = typeof withVertices == 'boolean' ? withVertices : true;
|
||||
|
||||
var result = L.GeometryUtil.closestLayer(map, layers, latlng);
|
||||
if (!result || result.distance > tolerance)
|
||||
return null;
|
||||
|
||||
// If snapped layer is linear, try to snap on vertices (extremities and middle points)
|
||||
if (withVertices && typeof result.layer.getLatLngs == 'function') {
|
||||
var closest = L.GeometryUtil.closest(map, result.layer, result.latlng, true);
|
||||
if (closest.distance < tolerance) {
|
||||
result.latlng = closest;
|
||||
result.distance = L.GeometryUtil.distance(map, closest, latlng);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
Returns the Point located on a segment at the specified ratio of the segment length.
|
||||
@param {L.Point} pA coordinates of point A
|
||||
@param {L.Point} pB coordinates of point B
|
||||
@param {Number} the length ratio, expressed as a decimal between 0 and 1, inclusive.
|
||||
@returns {L.Point} the interpolated point.
|
||||
*/
|
||||
interpolateOnPointSegment: function (pA, pB, ratio) {
|
||||
return L.point(
|
||||
(pA.x * (1 - ratio)) + (ratio * pB.x),
|
||||
(pA.y * (1 - ratio)) + (ratio * pB.y)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
Returns the coordinate of the point located on a line at the specified ratio of the line length.
|
||||
@param {L.Map} map Leaflet map to be used for this method
|
||||
@param {Array<L.LatLng>|L.PolyLine} latlngs Set of geographical points
|
||||
@param {Number} ratio the length ratio, expressed as a decimal between 0 and 1, inclusive
|
||||
@returns {Object} an object with latLng ({LatLng}) and predecessor ({Number}), the index of the preceding vertex in the Polyline
|
||||
(-1 if the interpolated point is the first vertex)
|
||||
*/
|
||||
interpolateOnLine: function (map, latLngs, ratio) {
|
||||
latLngs = (latLngs instanceof L.Polyline) ? latLngs.getLatLngs() : latLngs;
|
||||
var n = latLngs.length;
|
||||
if (n < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ensure the ratio is between 0 and 1;
|
||||
ratio = Math.max(Math.min(ratio, 1), 0);
|
||||
|
||||
if (ratio === 0) {
|
||||
return {
|
||||
latLng: latLngs[0] instanceof L.LatLng ? latLngs[0] : L.latLng(latLngs[0]),
|
||||
predecessor: -1
|
||||
};
|
||||
}
|
||||
if (ratio == 1) {
|
||||
return {
|
||||
latLng: latLngs[latLngs.length -1] instanceof L.LatLng ? latLngs[latLngs.length -1] : L.latLng(latLngs[latLngs.length -1]),
|
||||
predecessor: latLngs.length - 2
|
||||
};
|
||||
}
|
||||
|
||||
// project the LatLngs as Points,
|
||||
// and compute total planar length of the line at max precision
|
||||
var maxzoom = map.getMaxZoom();
|
||||
if (maxzoom === Infinity)
|
||||
maxzoom = map.getZoom();
|
||||
var pts = [];
|
||||
var lineLength = 0;
|
||||
for(var i = 0; i < n; i++) {
|
||||
pts[i] = map.project(latLngs[i], maxzoom);
|
||||
if(i > 0)
|
||||
lineLength += pts[i-1].distanceTo(pts[i]);
|
||||
}
|
||||
|
||||
var ratioDist = lineLength * ratio;
|
||||
|
||||
// follow the line segments [ab], adding lengths,
|
||||
// until we find the segment where the points should lie on
|
||||
var cumulativeDistanceToA = 0, cumulativeDistanceToB = 0;
|
||||
for (var i = 0; cumulativeDistanceToB < ratioDist; i++) {
|
||||
var pointA = pts[i], pointB = pts[i+1];
|
||||
|
||||
cumulativeDistanceToA = cumulativeDistanceToB;
|
||||
cumulativeDistanceToB += pointA.distanceTo(pointB);
|
||||
}
|
||||
|
||||
if (pointA == undefined && pointB == undefined) { // Happens when line has no length
|
||||
var pointA = pts[0], pointB = pts[1], i = 1;
|
||||
}
|
||||
|
||||
// compute the ratio relative to the segment [ab]
|
||||
var segmentRatio = ((cumulativeDistanceToB - cumulativeDistanceToA) !== 0) ? ((ratioDist - cumulativeDistanceToA) / (cumulativeDistanceToB - cumulativeDistanceToA)) : 0;
|
||||
var interpolatedPoint = L.GeometryUtil.interpolateOnPointSegment(pointA, pointB, segmentRatio);
|
||||
return {
|
||||
latLng: map.unproject(interpolatedPoint, maxzoom),
|
||||
predecessor: i-1
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
Returns a float between 0 and 1 representing the location of the
|
||||
closest point on polyline to the given latlng, as a fraction of total line length.
|
||||
(opposite of L.GeometryUtil.interpolateOnLine())
|
||||
@param {L.Map} map Leaflet map to be used for this method
|
||||
@param {L.PolyLine} polyline Polyline on which the latlng will be search
|
||||
@param {L.LatLng} latlng The position to search
|
||||
@returns {Number} Float between 0 and 1
|
||||
*/
|
||||
locateOnLine: function (map, polyline, latlng) {
|
||||
var latlngs = polyline.getLatLngs();
|
||||
if (latlng.equals(latlngs[0]))
|
||||
return 0.0;
|
||||
if (latlng.equals(latlngs[latlngs.length-1]))
|
||||
return 1.0;
|
||||
|
||||
var point = L.GeometryUtil.closest(map, polyline, latlng, false),
|
||||
lengths = L.GeometryUtil.accumulatedLengths(latlngs),
|
||||
total_length = lengths[lengths.length-1],
|
||||
portion = 0,
|
||||
found = false;
|
||||
for (var i=0, n = latlngs.length-1; i < n; i++) {
|
||||
var l1 = latlngs[i],
|
||||
l2 = latlngs[i+1];
|
||||
portion = lengths[i];
|
||||
if (L.GeometryUtil.belongsSegment(point, l1, l2, 0.001)) {
|
||||
portion += l1.distanceTo(point);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
throw "Could not interpolate " + latlng.toString() + " within " + polyline.toString();
|
||||
}
|
||||
return portion / total_length;
|
||||
},
|
||||
|
||||
/**
|
||||
Returns a clone with reversed coordinates.
|
||||
@param {L.PolyLine} polyline polyline to reverse
|
||||
@returns {L.PolyLine} polyline reversed
|
||||
*/
|
||||
reverse: function (polyline) {
|
||||
return L.polyline(polyline.getLatLngs().slice(0).reverse());
|
||||
},
|
||||
|
||||
/**
|
||||
Returns a sub-part of the polyline, from start to end.
|
||||
If start is superior to end, returns extraction from inverted line.
|
||||
@param {L.Map} map Leaflet map to be used for this method
|
||||
@param {L.PolyLine} polyline Polyline on which will be extracted the sub-part
|
||||
@param {Number} start ratio, expressed as a decimal between 0 and 1, inclusive
|
||||
@param {Number} end ratio, expressed as a decimal between 0 and 1, inclusive
|
||||
@returns {Array<L.LatLng>} new polyline
|
||||
*/
|
||||
extract: function (map, polyline, start, end) {
|
||||
if (start > end) {
|
||||
return L.GeometryUtil.extract(map, L.GeometryUtil.reverse(polyline), 1.0-start, 1.0-end);
|
||||
}
|
||||
|
||||
// Bound start and end to [0-1]
|
||||
start = Math.max(Math.min(start, 1), 0);
|
||||
end = Math.max(Math.min(end, 1), 0);
|
||||
|
||||
var latlngs = polyline.getLatLngs(),
|
||||
startpoint = L.GeometryUtil.interpolateOnLine(map, polyline, start),
|
||||
endpoint = L.GeometryUtil.interpolateOnLine(map, polyline, end);
|
||||
// Return single point if start == end
|
||||
if (start == end) {
|
||||
var point = L.GeometryUtil.interpolateOnLine(map, polyline, end);
|
||||
return [point.latLng];
|
||||
}
|
||||
// Array.slice() works indexes at 0
|
||||
if (startpoint.predecessor == -1)
|
||||
startpoint.predecessor = 0;
|
||||
if (endpoint.predecessor == -1)
|
||||
endpoint.predecessor = 0;
|
||||
var result = latlngs.slice(startpoint.predecessor+1, endpoint.predecessor+1);
|
||||
result.unshift(startpoint.latLng);
|
||||
result.push(endpoint.latLng);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
Returns true if first polyline ends where other second starts.
|
||||
@param {L.PolyLine} polyline First polyline
|
||||
@param {L.PolyLine} other Second polyline
|
||||
@returns {bool}
|
||||
*/
|
||||
isBefore: function (polyline, other) {
|
||||
if (!other) return false;
|
||||
var lla = polyline.getLatLngs(),
|
||||
llb = other.getLatLngs();
|
||||
return (lla[lla.length-1]).equals(llb[0]);
|
||||
},
|
||||
|
||||
/**
|
||||
Returns true if first polyline starts where second ends.
|
||||
@param {L.PolyLine} polyline First polyline
|
||||
@param {L.PolyLine} other Second polyline
|
||||
@returns {bool}
|
||||
*/
|
||||
isAfter: function (polyline, other) {
|
||||
if (!other) return false;
|
||||
var lla = polyline.getLatLngs(),
|
||||
llb = other.getLatLngs();
|
||||
return (lla[0]).equals(llb[llb.length-1]);
|
||||
},
|
||||
|
||||
/**
|
||||
Returns true if first polyline starts where second ends or start.
|
||||
@param {L.PolyLine} polyline First polyline
|
||||
@param {L.PolyLine} other Second polyline
|
||||
@returns {bool}
|
||||
*/
|
||||
startsAtExtremity: function (polyline, other) {
|
||||
if (!other) return false;
|
||||
var lla = polyline.getLatLngs(),
|
||||
llb = other.getLatLngs(),
|
||||
start = lla[0];
|
||||
return start.equals(llb[0]) || start.equals(llb[llb.length-1]);
|
||||
},
|
||||
|
||||
/**
|
||||
Returns horizontal angle in degres between two points.
|
||||
@param {L.Point} a Coordinates of point A
|
||||
@param {L.Point} b Coordinates of point B
|
||||
@returns {Number} horizontal angle
|
||||
*/
|
||||
computeAngle: function(a, b) {
|
||||
return (Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI);
|
||||
},
|
||||
|
||||
/**
|
||||
Returns slope (Ax+B) between two points.
|
||||
@param {L.Point} a Coordinates of point A
|
||||
@param {L.Point} b Coordinates of point B
|
||||
@returns {Object} with ``a`` and ``b`` properties.
|
||||
*/
|
||||
computeSlope: function(a, b) {
|
||||
var s = (b.y - a.y) / (b.x - a.x),
|
||||
o = a.y - (s * a.x);
|
||||
return {'a': s, 'b': o};
|
||||
},
|
||||
|
||||
/**
|
||||
Returns LatLng of rotated point around specified LatLng center.
|
||||
@param {L.LatLng} latlngPoint: point to rotate
|
||||
@param {double} angleDeg: angle to rotate in degrees
|
||||
@param {L.LatLng} latlngCenter: center of rotation
|
||||
@returns {L.LatLng} rotated point
|
||||
*/
|
||||
rotatePoint: function(map, latlngPoint, angleDeg, latlngCenter) {
|
||||
var maxzoom = map.getMaxZoom();
|
||||
if (maxzoom === Infinity)
|
||||
maxzoom = map.getZoom();
|
||||
var angleRad = angleDeg*Math.PI/180,
|
||||
pPoint = map.project(latlngPoint, maxzoom),
|
||||
pCenter = map.project(latlngCenter, maxzoom),
|
||||
x2 = Math.cos(angleRad)*(pPoint.x-pCenter.x) - Math.sin(angleRad)*(pPoint.y-pCenter.y) + pCenter.x,
|
||||
y2 = Math.sin(angleRad)*(pPoint.x-pCenter.x) + Math.cos(angleRad)*(pPoint.y-pCenter.y) + pCenter.y;
|
||||
return map.unproject(new L.Point(x2,y2), maxzoom);
|
||||
},
|
||||
|
||||
/**
|
||||
Returns the bearing in degrees clockwise from north (0 degrees)
|
||||
from the first L.LatLng to the second, at the first LatLng
|
||||
@param {L.LatLng} latlng1: origin point of the bearing
|
||||
@param {L.LatLng} latlng2: destination point of the bearing
|
||||
@returns {float} degrees clockwise from north.
|
||||
*/
|
||||
bearing: function(latlng1, latlng2) {
|
||||
var rad = Math.PI / 180,
|
||||
lat1 = latlng1.lat * rad,
|
||||
lat2 = latlng2.lat * rad,
|
||||
lon1 = latlng1.lng * rad,
|
||||
lon2 = latlng2.lng * rad,
|
||||
y = Math.sin(lon2 - lon1) * Math.cos(lat2),
|
||||
x = Math.cos(lat1) * Math.sin(lat2) -
|
||||
Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);
|
||||
|
||||
var bearing = ((Math.atan2(y, x) * 180 / Math.PI) + 360) % 360;
|
||||
return bearing >= 180 ? bearing-360 : bearing;
|
||||
},
|
||||
|
||||
/**
|
||||
Returns the point that is a distance and heading away from
|
||||
the given origin point.
|
||||
@param {L.LatLng} latlng: origin point
|
||||
@param {float} heading: heading in degrees, clockwise from 0 degrees north.
|
||||
@param {float} distance: distance in meters
|
||||
@returns {L.latLng} the destination point.
|
||||
Many thanks to Chris Veness at http://www.movable-type.co.uk/scripts/latlong.html
|
||||
for a great reference and examples.
|
||||
*/
|
||||
destination: function(latlng, heading, distance) {
|
||||
heading = (heading + 360) % 360;
|
||||
var rad = Math.PI / 180,
|
||||
radInv = 180 / Math.PI,
|
||||
R = 6378137, // approximation of Earth's radius
|
||||
lon1 = latlng.lng * rad,
|
||||
lat1 = latlng.lat * rad,
|
||||
rheading = heading * rad,
|
||||
sinLat1 = Math.sin(lat1),
|
||||
cosLat1 = Math.cos(lat1),
|
||||
cosDistR = Math.cos(distance / R),
|
||||
sinDistR = Math.sin(distance / R),
|
||||
lat2 = Math.asin(sinLat1 * cosDistR + cosLat1 *
|
||||
sinDistR * Math.cos(rheading)),
|
||||
lon2 = lon1 + Math.atan2(Math.sin(rheading) * sinDistR *
|
||||
cosLat1, cosDistR - sinLat1 * Math.sin(lat2));
|
||||
lon2 = lon2 * radInv;
|
||||
lon2 = lon2 > 180 ? lon2 - 360 : lon2 < -180 ? lon2 + 360 : lon2;
|
||||
return L.latLng([lat2 * radInv, lon2]);
|
||||
},
|
||||
|
||||
/**
|
||||
Returns the the angle of the given segment and the Equator in degrees,
|
||||
clockwise from 0 degrees north.
|
||||
@param {L.Map} map: Leaflet map to be used for this method
|
||||
@param {L.LatLng} latlngA: geographical point A of the segment
|
||||
@param {L.LatLng} latlngB: geographical point B of the segment
|
||||
@returns {Float} the angle in degrees.
|
||||
*/
|
||||
angle: function(map, latlngA, latlngB) {
|
||||
var pointA = map.latLngToContainerPoint(latlngA),
|
||||
pointB = map.latLngToContainerPoint(latlngB),
|
||||
angleDeg = Math.atan2(pointB.y - pointA.y, pointB.x - pointA.x) * 180 / Math.PI + 90;
|
||||
angleDeg += angleDeg < 0 ? 360 : 0;
|
||||
return angleDeg;
|
||||
},
|
||||
|
||||
/**
|
||||
Returns a point snaps on the segment and heading away from the given origin point a distance.
|
||||
@param {L.Map} map: Leaflet map to be used for this method
|
||||
@param {L.LatLng} latlngA: geographical point A of the segment
|
||||
@param {L.LatLng} latlngB: geographical point B of the segment
|
||||
@param {float} distance: distance in meters
|
||||
@returns {L.latLng} the destination point.
|
||||
*/
|
||||
destinationOnSegment: function(map, latlngA, latlngB, distance) {
|
||||
var angleDeg = L.GeometryUtil.angle(map, latlngA, latlngB),
|
||||
latlng = L.GeometryUtil.destination(latlngA, angleDeg, distance);
|
||||
return L.GeometryUtil.closestOnSegment(map, latlng, latlngA, latlngB);
|
||||
},
|
||||
});
|
||||
|
||||
return L.GeometryUtil;
|
||||
|
||||
}));
|
||||
2
web/static/vendor/leaflet-elevation/libs/togeojson.umd.js
vendored
Normal file
2
web/static/vendor/leaflet-elevation/libs/togeojson.umd.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user