UNPKG

heli-agri

Version:

HeliAgri is a high-performance, feature-packed library for creating interactive maps on the web. It can display map tiles, vector data and markers loaded from any source on any web page. OpenLayers has been developed to further the use of geographic infor

264 lines (255 loc) 9.19 kB
/** * @module ol/sphere */ import {toDegrees, toRadians} from './math.js'; /** * Object literal with options for the {@link getLength} or {@link getArea} * functions. * @typedef {Object} SphereMetricOptions * @property {import("./proj.js").ProjectionLike} [projection='EPSG:3857'] * Projection of the geometry. By default, the geometry is assumed to be in * Web Mercator. * @property {number} [radius=6371008.8] Sphere radius. By default, the * [mean Earth radius](https://en.wikipedia.org/wiki/Earth_radius#Mean_radius) * for the WGS84 ellipsoid is used. */ /** * The mean Earth radius (1/3 * (2a + b)) for the WGS84 ellipsoid. * https://en.wikipedia.org/wiki/Earth_radius#Mean_radius * @type {number} */ export const DEFAULT_RADIUS = 6371008.8; /** * Get the great circle distance (in meters) between two geographic coordinates. * @param {Array} c1 Starting coordinate. * @param {Array} c2 Ending coordinate. * @param {number} [radius] The sphere radius to use. Defaults to the Earth's * mean radius using the WGS84 ellipsoid. * @return {number} The great circle distance between the points (in meters). * @api */ export function getDistance(c1, c2, radius) { radius = radius || DEFAULT_RADIUS; const lat1 = toRadians(c1[1]); const lat2 = toRadians(c2[1]); const deltaLatBy2 = (lat2 - lat1) / 2; const deltaLonBy2 = toRadians(c2[0] - c1[0]) / 2; const a = Math.sin(deltaLatBy2) * Math.sin(deltaLatBy2) + Math.sin(deltaLonBy2) * Math.sin(deltaLonBy2) * Math.cos(lat1) * Math.cos(lat2); return 2 * radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } /** * Get the cumulative great circle length of linestring coordinates (geographic). * @param {Array} coordinates Linestring coordinates. * @param {number} radius The sphere radius to use. * @return {number} The length (in meters). */ function getLengthInternal(coordinates, radius) { let length = 0; for (let i = 0, ii = coordinates.length; i < ii - 1; ++i) { length += getDistance(coordinates[i], coordinates[i + 1], radius); } return length; } /** * Get the spherical length of a geometry. This length is the sum of the * great circle distances between coordinates. For polygons, the length is * the sum of all rings. For points, the length is zero. For multi-part * geometries, the length is the sum of the length of each part. * @param {import("./geom/Geometry.js").default} geometry A geometry. * @param {SphereMetricOptions} [options] Options for the * length calculation. By default, geometries are assumed to be in 'EPSG:3857'. * You can change this by providing a `projection` option. * @return {number} The spherical length (in meters). * @api */ export function getLength(geometry, options) { options = options || {}; const radius = options.radius || DEFAULT_RADIUS; const projection = options.projection || 'EPSG:3857'; const type = geometry.getType(); if (type !== 'GeometryCollection') { geometry = geometry.clone().transform(projection, 'EPSG:4326'); } let length = 0; let coordinates, coords, i, ii, j, jj; switch (type) { case 'Point': case 'MultiPoint': { break; } case 'LineString': case 'LinearRing': { coordinates = /** @type {import("./geom/SimpleGeometry.js").default} */ ( geometry ).getCoordinates(); length = getLengthInternal(coordinates, radius); break; } case 'MultiLineString': case 'Polygon': { coordinates = /** @type {import("./geom/SimpleGeometry.js").default} */ ( geometry ).getCoordinates(); for (i = 0, ii = coordinates.length; i < ii; ++i) { length += getLengthInternal(coordinates[i], radius); } break; } case 'MultiPolygon': { coordinates = /** @type {import("./geom/SimpleGeometry.js").default} */ ( geometry ).getCoordinates(); for (i = 0, ii = coordinates.length; i < ii; ++i) { coords = coordinates[i]; for (j = 0, jj = coords.length; j < jj; ++j) { length += getLengthInternal(coords[j], radius); } } break; } case 'GeometryCollection': { const geometries = /** @type {import("./geom/GeometryCollection.js").default} */ ( geometry ).getGeometries(); for (i = 0, ii = geometries.length; i < ii; ++i) { length += getLength(geometries[i], options); } break; } default: { throw new Error('Unsupported geometry type: ' + type); } } return length; } /** * Returns the spherical area for a list of coordinates. * * [Reference](https://trs.jpl.nasa.gov/handle/2014/40409) * 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 * * @param {Array<import("./coordinate.js").Coordinate>} coordinates List of coordinates of a linear * ring. If the ring is oriented clockwise, the area will be positive, * otherwise it will be negative. * @param {number} radius The sphere radius. * @return {number} Area (in square meters). */ function getAreaInternal(coordinates, radius) { let area = 0; const len = coordinates.length; let x1 = coordinates[len - 1][0]; let y1 = coordinates[len - 1][1]; for (let i = 0; i < len; i++) { const x2 = coordinates[i][0]; const y2 = coordinates[i][1]; area += toRadians(x2 - x1) * (2 + Math.sin(toRadians(y1)) + Math.sin(toRadians(y2))); x1 = x2; y1 = y2; } return (area * radius * radius) / 2.0; } /** * Get the spherical area of a geometry. This is the area (in meters) assuming * that polygon edges are segments of great circles on a sphere. * @param {import("./geom/Geometry.js").default} geometry A geometry. * @param {SphereMetricOptions} [options] Options for the area * calculation. By default, geometries are assumed to be in 'EPSG:3857'. * You can change this by providing a `projection` option. * @return {number} The spherical area (in square meters). * @api */ export function getArea(geometry, options) { options = options || {}; const radius = options.radius || DEFAULT_RADIUS; const projection = options.projection || 'EPSG:3857'; const type = geometry.getType(); if (type !== 'GeometryCollection') { geometry = geometry.clone().transform(projection, 'EPSG:4326'); } let area = 0; let coordinates, coords, i, ii, j, jj; switch (type) { case 'Point': case 'MultiPoint': case 'LineString': case 'MultiLineString': case 'LinearRing': { break; } case 'Polygon': { coordinates = /** @type {import("./geom/Polygon.js").default} */ ( geometry ).getCoordinates(); area = Math.abs(getAreaInternal(coordinates[0], radius)); for (i = 1, ii = coordinates.length; i < ii; ++i) { area -= Math.abs(getAreaInternal(coordinates[i], radius)); } break; } case 'MultiPolygon': { coordinates = /** @type {import("./geom/SimpleGeometry.js").default} */ ( geometry ).getCoordinates(); for (i = 0, ii = coordinates.length; i < ii; ++i) { coords = coordinates[i]; area += Math.abs(getAreaInternal(coords[0], radius)); for (j = 1, jj = coords.length; j < jj; ++j) { area -= Math.abs(getAreaInternal(coords[j], radius)); } } break; } case 'GeometryCollection': { const geometries = /** @type {import("./geom/GeometryCollection.js").default} */ ( geometry ).getGeometries(); for (i = 0, ii = geometries.length; i < ii; ++i) { area += getArea(geometries[i], options); } break; } default: { throw new Error('Unsupported geometry type: ' + type); } } return area; } /** * Returns the coordinate at the given distance and bearing from `c1`. * * @param {import("./coordinate.js").Coordinate} c1 The origin point (`[lon, lat]` in degrees). * @param {number} distance The great-circle distance between the origin * point and the target point. * @param {number} bearing The bearing (in radians). * @param {number} [radius] The sphere radius to use. Defaults to the Earth's * mean radius using the WGS84 ellipsoid. * @return {import("./coordinate.js").Coordinate} The target point. */ export function offset(c1, distance, bearing, radius) { radius = radius || DEFAULT_RADIUS; const lat1 = toRadians(c1[1]); const lon1 = toRadians(c1[0]); const dByR = distance / radius; const lat = Math.asin( Math.sin(lat1) * Math.cos(dByR) + Math.cos(lat1) * Math.sin(dByR) * Math.cos(bearing) ); const lon = lon1 + Math.atan2( Math.sin(bearing) * Math.sin(dByR) * Math.cos(lat1), Math.cos(dByR) - Math.sin(lat1) * Math.sin(lat) ); return [toDegrees(lon), toDegrees(lat)]; }