UNPKG

s2-tools

Version:

A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.

177 lines 6.54 kB
import { EARTH_RADIUS } from '../../space/planets'; import { orient2d } from '../predicates'; import { pointOverlap } from '../bbox'; // TODO: polygon(s)AreaS2(...) /** * Find the area of a collection of polygons. Assumes geometry is in lon-lat space * @param polygons - the collection of polygons * @param planetRadius - the radius of the planet (Earth by default) * @returns - the total area of the polygon */ export function polygonsArea(polygons, planetRadius = EARTH_RADIUS) { const vectorPolygons = 'geometry' in polygons ? polygons.geometry.coordinates : 'coordinates' in polygons ? polygons.coordinates : polygons; let area = 0; for (const polygon of vectorPolygons) area += polygonArea(polygon, planetRadius); return area; } /** * Find the area of a polygon. Assumes geometry is in Lon-Lat space * @param polygon - the polygon * @param planetRadius - the radius of the planet (Earth by default) * @returns - The approximate signed geodesic area of the polygon in square meters. */ export function polygonArea(polygon, planetRadius = EARTH_RADIUS) { // check poly against the point const vectorPolygon = 'geometry' in polygon ? polygon.geometry.coordinates : 'coordinates' in polygon ? polygon.coordinates : polygon; // grab the area of the outer ring let area = _ringArea(vectorPolygon[0], planetRadius); // subtract the area of the inner rings (holes) for (let i = 1; i < vectorPolygon.length; i++) { area -= _ringArea(vectorPolygon[i], planetRadius); } return area; } /** * A robust method to see if a point is in a collection of polygons or not. * Be sure the point and polygon are in the same projection space. * @param point - the point to check * @param polygons - the collection of polygons * @param ignoreBoundary - if true, ignore when the point is on the boundary * @returns - true if the point is in the polygon */ export function pointInPolygons(point, polygons, ignoreBoundary = false) { const vectorPolygons = 'geometry' in polygons ? polygons.geometry.coordinates : 'coordinates' in polygons ? polygons.coordinates : polygons; for (const polygon of vectorPolygons) if (pointInPolygon(point, polygon, ignoreBoundary)) return true; return false; } /** * A robust method to see if a point is in a polygon or not. * Be sure the point and polygon are in the same projection space. * @param point - the point to check * @param polygon - the polygon * @param ignoreBoundary - if true, ignore when the point is on the boundary * @returns - true if the point is in the polygon */ export function pointInPolygon(point, polygon, ignoreBoundary = false) { // bbox test case - if it doesn't even fit within the bbox, we know it's not in the polygon const bbox = 'geometry' in polygon ? polygon.geometry.bbox : 'bbox' in polygon ? polygon.bbox : undefined; if (bbox !== undefined && !pointOverlap(bbox, point)) return false; // check poly against the point const vectorPolygon = 'geometry' in polygon ? polygon.geometry.coordinates : 'coordinates' in polygon ? polygon.coordinates : polygon; const pip = _pointInPolygon(point, vectorPolygon); if (pip === 0) { if (ignoreBoundary) return false; return true; } else return pip; } /** * A Robust point in polygon test * @param point - the point * @param polygon - the polygon * @returns - true if the point is in the polygon, 0 if on the boundary, false otherwise */ function _pointInPolygon(point, polygon) { let i; let ii; let k = 0; let f; let u1; let v1; let u2; let v2; let currentP; let nextP; const { x, y } = point; const numContours = polygon.length; for (i = 0; i < numContours; i++) { ii = 0; const contour = polygon[i]; const contourLen = contour.length - 1; currentP = contour[0]; if (currentP.x !== contour[contourLen].x && currentP.y !== contour[contourLen].y) { // since the first and last coordinates in a ring are not the same, assume it's not a polygon and return false return false; } u1 = currentP.x - x; v1 = currentP.y - y; for (ii; ii < contourLen; ii++) { nextP = contour[ii + 1]; u2 = nextP.x - x; v2 = nextP.y - y; if (v1 === 0 && v2 === 0) { if ((u2 <= 0 && u1 >= 0) || (u1 <= 0 && u2 >= 0)) return 0; } else if ((v2 >= 0 && v1 <= 0) || (v2 <= 0 && v1 >= 0)) { f = orient2d(u1, u2, v1, v2, 0, 0); if (f === 0) return 0; if ((f > 0 && v2 > 0 && v1 <= 0) || (f < 0 && v2 <= 0 && v1 > 0)) k++; } currentP = nextP; v1 = v2; u1 = u2; } } if (k % 2 === 0) return false; return true; } /** * Calculate the approximate area of the polygon were it projected onto the planet. * Note that this area will be positive if ring is oriented counter-clockwise, * otherwise it will be negative. * * Reference: * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for Polygons on a Sphere", * JPL Publication 07-03, Jet Propulsion * Laboratory, Pasadena, CA, June 2007 https://trs.jpl.nasa.gov/handle/2014/40409 * @param coords - ring Coordinates in lon-lat space * @param planetRadius - the radius of the planet (Earth by default) * @returns - The approximate signed geodesic area of the polygon in square meters. */ function _ringArea(coords, planetRadius) { const RAD = 0.017453292519943295; // Math.PI / 180; const coordsLength = coords.length - 1; const factor = (planetRadius * planetRadius) / 2; if (coordsLength <= 2) return 0; let total = 0; let i = 0; while (i < coordsLength) { const lower = coords[i]; const middle = coords[i + 1 === coordsLength ? 0 : i + 1]; const upper = coords[i + 2 >= coordsLength ? (i + 2) % coordsLength : i + 2]; const lowerX = lower.x * RAD; const middleY = middle.y * RAD; const upperX = upper.x * RAD; total += (upperX - lowerX) * Math.sin(middleY); i++; } return -(total * factor); } //# sourceMappingURL=polys.js.map