2018-11-25 23:21:48 +01:00
|
|
|
/**
|
|
|
|
* Check whether a given point is within (in the interior) of a ring.
|
|
|
|
* Adapted from https://github.com/Turfjs/turf/, licensed under MIT.
|
|
|
|
*
|
|
|
|
* @param latLng The [latitude, longitude] coordinates of the point.
|
|
|
|
* @param ring A list of [latitude, longitude] for each vertex in
|
|
|
|
* the ring. The ring is always considered to be
|
|
|
|
* closed (last point being the same as the first
|
|
|
|
* one), even if that is not explicitly the case.
|
|
|
|
* @param ignoreBoundary Whether to consider a point on the boundary as
|
|
|
|
* being within the ring or not.
|
|
|
|
*
|
|
|
|
* @note This is used with latitude and longitude in mind,
|
|
|
|
* hence the names, but is much more generic and can
|
|
|
|
* be used with any (X, Y) coordinates.
|
|
|
|
*/
|
|
|
|
export function isInRing(latLng, ring, ignoreBoundary) {
|
|
|
|
let isInside = false;
|
|
|
|
|
|
|
|
// If the ring is a full loop, ignore the duplicate point
|
2018-11-29 14:13:58 +01:00
|
|
|
let openRing = [].concat(ring);
|
2018-11-25 23:21:48 +01:00
|
|
|
if (
|
|
|
|
openRing[0][0] === openRing[openRing.length - 1][0]
|
|
|
|
&& openRing[0][1] === openRing[openRing.length - 1][1]
|
|
|
|
) {
|
|
|
|
openRing = openRing.slice(0, openRing.length - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0, j = openRing.length - 1; i < openRing.length; j = i, i += 1) {
|
|
|
|
// Get the current edge of the ring
|
|
|
|
const xi = openRing[i][0];
|
|
|
|
const yi = openRing[i][1];
|
|
|
|
const xj = openRing[j][0];
|
|
|
|
const yj = openRing[j][1];
|
|
|
|
|
|
|
|
// Check whether the point is on the boundary
|
|
|
|
const onBoundary = (
|
|
|
|
(latLng[1] * (xi - xj) + yi * (xj - latLng[0]) + yj * (latLng[0] - xi) === 0)
|
|
|
|
&& ((xi - latLng[0]) * (xj - latLng[0]) <= 0)
|
|
|
|
&& ((yi - latLng[1]) * (yj - latLng[1]) <= 0)
|
|
|
|
);
|
|
|
|
if (onBoundary) {
|
|
|
|
return !ignoreBoundary;
|
|
|
|
}
|
|
|
|
|
|
|
|
const intersect = (
|
|
|
|
((yi > latLng[1]) !== (yj > latLng[1]))
|
|
|
|
&& (latLng[0] < (xj - xi) * (latLng[1] - yi) / (yj - yi) + xi)
|
|
|
|
);
|
|
|
|
if (intersect) {
|
|
|
|
isInside = !isInside;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return isInside;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check whether a point is within a given bbox.
|
|
|
|
* Adapted from https://github.com/Turfjs/turf/, licensed under MIT.
|
|
|
|
*
|
|
|
|
* @param latlng A [latitude, longitude] array for the point.
|
|
|
|
* @param bbox A [minLatitude, minLongitude, maxLatitude, maxLongitude]
|
|
|
|
* array representing the bbox.
|
|
|
|
* @return True if the point is within the bbox, false otherwise.
|
|
|
|
*
|
|
|
|
* @note This is used with latitude and longitude in mind, hence the
|
|
|
|
* names, but is much more generic and can be used with any
|
|
|
|
* (X, Y) coordinates.
|
|
|
|
*/
|
|
|
|
export function isInBBox(latLng, bbox) {
|
|
|
|
return (
|
|
|
|
bbox[0] <= latLng[0]
|
|
|
|
&& bbox[1] <= latLng[1]
|
|
|
|
&& bbox[2] >= latLng[0]
|
|
|
|
&& bbox[3] >= latLng[1]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compute the bbox of a Polygon.
|
|
|
|
*
|
|
|
|
* @param polygon A list of [latitude, longitude] each vertex in the polygon
|
|
|
|
* (or polyline).
|
|
|
|
* @return A [minLatitude, minLongitude, maxLatitude, maxLongitude]
|
|
|
|
* array representing the bbox.
|
|
|
|
*
|
|
|
|
* @note This is used with latitude and longitude in mind, hence the
|
|
|
|
* names, but is much more generic and can be used with any
|
|
|
|
* (X, Y) coordinates.
|
|
|
|
* @note This works with a polygon or polyline.
|
|
|
|
*/
|
|
|
|
export function computeBBox(polygon) {
|
|
|
|
const latList = polygon.map(item => item[0]);
|
|
|
|
const lngList = polygon.map(item => item[1]);
|
|
|
|
return [
|
|
|
|
Math.min(...latList),
|
|
|
|
Math.min(...lngList),
|
|
|
|
Math.max(...latList),
|
|
|
|
Math.max(...lngList),
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check whether a point is within a Polygon.
|
|
|
|
* Adapted from https://github.com/Turfjs/turf/, licensed under MIT.
|
|
|
|
*
|
|
|
|
* @param latLng A [latitude, longitude] array for the point.
|
|
|
|
* @param polygon An array of [latitude, longitude] arrays for each
|
|
|
|
* vertex of the Polygon (polygon ring).
|
|
|
|
* @param ignoreBoundary Whether a point on the boundary should be considered
|
|
|
|
* within the Polygon or not. Default to false.
|
|
|
|
* @return true if the point is within the Polygon, false
|
|
|
|
* otherwise.
|
|
|
|
*
|
|
|
|
* @note This is used with latitude and longitude in mind,
|
|
|
|
* hence the names, but is much more generic and can
|
|
|
|
* be used with any (X, Y) coordinates.
|
|
|
|
*/
|
|
|
|
export function isWithinPolygon(latLng, polygon, ignoreBoundary) {
|
|
|
|
const shouldIgnoreBoundary = ignoreBoundary || false;
|
|
|
|
// Quick check: is point inside bbox?
|
|
|
|
const bbox = computeBBox(polygon);
|
|
|
|
if (isInBBox(latLng, bbox) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Thorough check
|
|
|
|
if (isInRing(latLng, polygon, shouldIgnoreBoundary)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cheap distance computation between two points based on
|
|
|
|
* https://blog.mapbox.com/fast-geodesic-approximations-with-cheap-ruler-106f229ad016.
|
|
|
|
* (ISC license)
|
|
|
|
*
|
|
|
|
* @param latLng1 A [latitude, longitude] array for the first point.
|
|
|
|
* @param latLng2 A [latitude, longitude] array for the second point.
|
|
|
|
* @return The distance in meters.
|
|
|
|
*/
|
|
|
|
export function pointToPointDistance(latLng1, latLng2) {
|
|
|
|
const cos = Math.cos((latLng1[0] + latLng2[0]) / 2 * Math.PI / 180);
|
|
|
|
const cos2 = 2 * cos * cos - 1;
|
|
|
|
const cos3 = 2 * cos * cos2 - cos;
|
|
|
|
const cos4 = 2 * cos * cos3 - cos2;
|
|
|
|
const cos5 = 2 * cos * cos4 - cos3;
|
|
|
|
|
|
|
|
// Multipliers for converting longitude and latitude degrees into distance
|
|
|
|
// (http://1.usa.gov/1Wb1bv7)
|
|
|
|
const kx = 1000 * (111.41513 * cos - 0.09455 * cos3 + 0.00012 * cos5);
|
|
|
|
const ky = 1000 * (111.13209 - 0.56605 * cos2 + 0.0012 * cos4);
|
|
|
|
|
|
|
|
const dx = (latLng1[1] - latLng2[1]) * kx;
|
|
|
|
const dy = (latLng1[0] - latLng2[0]) * ky;
|
|
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compute the dot product of two vectors.
|
|
|
|
* Adapted from https://github.com/Turfjs/turf/, licensed under MIT.
|
|
|
|
*
|
|
|
|
* @param u Array of coordinates of the first vector.
|
|
|
|
* @param v Array of coordinates of the second vector.
|
|
|
|
* @return The dot product of the two vectors.
|
|
|
|
*/
|
|
|
|
export function dot(u, v) {
|
|
|
|
return (u[0] * v[0] + u[1] * v[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compute the distance between a point and a polyLine.
|
|
|
|
* Adapted from https://github.com/Turfjs/turf/, licensed under MIT.
|
|
|
|
*
|
|
|
|
* @param latLng An array [latitude, longitude] for the point to
|
|
|
|
* compute distance from.
|
|
|
|
* @param polyLine A list of [latitude, longitude] arrays for each vertex
|
|
|
|
* of the polyLine.
|
|
|
|
* @return The distance between the point and the polyLine.
|
|
|
|
*/
|
|
|
|
export function pointToLineDistance(latLng, polyLine) {
|
|
|
|
let distance = Number.POSITIVE_INFINITY;
|
|
|
|
|
|
|
|
// Iterate over the segments forming the polyLine
|
|
|
|
for (let i = 0; i < (polyLine.length - 1); i += 1) {
|
|
|
|
// Distance between point and the current segment
|
|
|
|
let distanceToSegment = null;
|
|
|
|
|
|
|
|
// Origin and end of the segment
|
|
|
|
const a = polyLine[i];
|
|
|
|
const b = polyLine[i + 1];
|
|
|
|
|
|
|
|
// Segment vector
|
|
|
|
const v = [b[0] - a[0], b[1] - a[1]];
|
|
|
|
// Point to origin of the segment vector
|
|
|
|
const w = [latLng[0] - a[0], latLng[1] - a[1]];
|
|
|
|
|
|
|
|
const c1 = dot(w, v);
|
|
|
|
if (c1 <= 0) {
|
|
|
|
// Point is closer to origin
|
|
|
|
distanceToSegment = pointToPointDistance(latLng, a);
|
|
|
|
} else {
|
|
|
|
const c2 = dot(v, v);
|
|
|
|
if (c2 <= c1) {
|
|
|
|
// Point is closer to end
|
|
|
|
distanceToSegment = pointToPointDistance(latLng, b);
|
|
|
|
} else {
|
|
|
|
const b2 = c1 / c2;
|
|
|
|
const Pb = [a[0] + (b2 * v[0]), a[1] + (b2 * v[1])];
|
|
|
|
distanceToSegment = pointToPointDistance(latLng, Pb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (distanceToSegment < distance) {
|
|
|
|
distance = distanceToSegment;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return distance;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compute the distance between a point and a polygon.
|
|
|
|
*
|
|
|
|
* @param latLng A [latitude, longitude] array representing the point.
|
|
|
|
* @param polygon A list of [latitude, longitude] arrays of the vertices of
|
|
|
|
* the polygon.
|
|
|
|
* @return The distance between the point and the polygon.
|
|
|
|
*/
|
|
|
|
export function pointToPolygonDistance(latLng, polygon) {
|
|
|
|
const polygonRing = polygon;
|
|
|
|
// Ensure the polygon ring is a full loop
|
|
|
|
if (
|
|
|
|
polygonRing[0][0] !== polygonRing[polygonRing.length - 1][0]
|
|
|
|
&& polygonRing[0][1] !== polygonRing[polygonRing.length - 1][1]
|
|
|
|
) {
|
|
|
|
polygonRing.push(polygonRing[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// First, check whether the point is on or inside the polygon
|
|
|
|
if (isWithinPolygon(latLng, polygonRing, false)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise return the distance from the point to the polygon ring.
|
|
|
|
return pointToLineDistance(latLng, polygonRing);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compute the distance between a point and a GeoJSON geometry.
|
|
|
|
*
|
|
|
|
* @param latLng A [latitude, longitude] array representing the point.
|
|
|
|
* @param geometry A GeoJSON-like geometry (Object with "type" and
|
|
|
|
* "coordinates" keys). Coordinates are GeoJSON-like,
|
|
|
|
* longitude first and latitude then.
|
|
|
|
* @return The distance between the point and the geometry.
|
|
|
|
*/
|
|
|
|
export function pointToGeometryDistance(latLng, geometry) {
|
2018-11-29 14:13:58 +01:00
|
|
|
const lngLatCoordinates = [].concat(geometry.coordinates);
|
2018-11-25 23:21:48 +01:00
|
|
|
|
|
|
|
if (geometry.type === 'Point') {
|
|
|
|
return pointToPointDistance(latLng, lngLatCoordinates.reverse());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (geometry.type === 'LineString') {
|
|
|
|
return pointToLineDistance(latLng, lngLatCoordinates.map(item => item.reverse()));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (geometry.type === 'Polygon') {
|
|
|
|
return pointToPolygonDistance(latLng, lngLatCoordinates.map(item => item.reverse()));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unsupported geometry
|
|
|
|
return null;
|
|
|
|
}
|