737 lines
28 KiB
JavaScript
737 lines
28 KiB
JavaScript
|
// 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.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 = Math.ceil(distance) + ' m';
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
distance *= 1.09361;
|
||
|
if (distance > 1760) {
|
||
|
distanceStr = (distance / 1760).toFixed(2) + ' miles';
|
||
|
}
|
||
|
else {
|
||
|
distanceStr = Math.ceil(distance) + ' 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;
|
||
|
|
||
|
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 (var i = 0; i < layer.length; i++) {
|
||
|
var subResult = L.GeometryUtil.closest(map, layer[i], latlng, vertices);
|
||
|
if (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 (var 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
|
||
|
var 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;
|
||
|
var a = pts[0],
|
||
|
b = pts[1],
|
||
|
distA = 0,
|
||
|
distB = a.distanceTo(b);
|
||
|
// follow the line segments [ab], adding lengths,
|
||
|
// until we find the segment where the points should lie on
|
||
|
var index = 1;
|
||
|
for (; index < n && distB < ratioDist; index++) {
|
||
|
a = b;
|
||
|
distA = distB;
|
||
|
b = pts[index];
|
||
|
distB += a.distanceTo(b);
|
||
|
}
|
||
|
// compute the ratio relative to the segment [ab]
|
||
|
var segmentRatio = ((distB - distA) !== 0) ? ((ratioDist - distA) / (distB - distA)) : 0;
|
||
|
var interpolatedPoint = L.GeometryUtil.interpolateOnPointSegment(a, b, segmentRatio);
|
||
|
return {
|
||
|
latLng: map.unproject(interpolatedPoint, maxzoom),
|
||
|
predecessor: index-2
|
||
|
};
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
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)) {
|
||
|
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 in degrees, clockwise from 0 degrees north.
|
||
|
@param {float}: 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]);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return L.GeometryUtil;
|
||
|
|
||
|
}));
|