From 94fb7ca5a6e6f987f2bdc3cc06e929b541b67ad8 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 10 Aug 2017 18:30:42 -0600 Subject: [PATCH] Function for getting spherical area --- externs/olx.js | 9 ++-- src/ol/sphere.js | 104 +++++++++++++++++++++++++++++++----- test/spec/ol/sphere.test.js | 20 +++++++ 3 files changed, 116 insertions(+), 17 deletions(-) diff --git a/externs/olx.js b/externs/olx.js index 95d445f216..c002f9543c 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -437,11 +437,12 @@ olx.MapOptions.prototype.view; /** - * Object literal with options for the {@link ol.Sphere.getLength}. + * Object literal with options for the {@link ol.Sphere.getLength} or + * {@link ol.Sphere.getArea} functions. * @typedef {{projection: (ol.ProjectionLike|undefined), * radius: (number|undefined)}} */ -olx.SphereLengthOptions; +olx.SphereMetricOptions; /** @@ -450,7 +451,7 @@ olx.SphereLengthOptions; * @type {(ol.ProjectionLike|undefined)} * @api */ -olx.SphereLengthOptions.prototype.projection; +olx.SphereMetricOptions.prototype.projection; /** @@ -459,7 +460,7 @@ olx.SphereLengthOptions.prototype.projection; * @type {(number|undefined)} * @api */ -olx.SphereLengthOptions.prototype.radius; +olx.SphereMetricOptions.prototype.radius; /** diff --git a/src/ol/sphere.js b/src/ol/sphere.js index 2a9e715b88..61c3a92a98 100644 --- a/src/ol/sphere.js +++ b/src/ol/sphere.js @@ -52,18 +52,7 @@ ol.Sphere = function(radius) { * @api */ ol.Sphere.prototype.geodesicArea = function(coordinates) { - var area = 0, len = coordinates.length; - var x1 = coordinates[len - 1][0]; - var y1 = coordinates[len - 1][1]; - for (var i = 0; i < len; i++) { - var x2 = coordinates[i][0], y2 = coordinates[i][1]; - area += ol.math.toRadians(x2 - x1) * - (2 + Math.sin(ol.math.toRadians(y1)) + - Math.sin(ol.math.toRadians(y2))); - x1 = x2; - y1 = y2; - } - return area * this.radius * this.radius / 2.0; + return ol.Sphere.getArea_(coordinates, this.radius); }; @@ -117,7 +106,7 @@ ol.Sphere.DEFAULT_RADIUS = 6371008.8; * 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 {ol.geom.Geometry} geometry A geometry. - * @param {olx.SphereLengthOptions} opt_options Options for the length + * @param {olx.SphereMetricOptions} opt_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). @@ -206,3 +195,92 @@ ol.Sphere.getDistance_ = function(c1, c2, radius) { Math.cos(lat1) * Math.cos(lat2); return 2 * radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); }; + + +/** + * 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 {ol.geom.Geometry} geometry A geometry. + * @param {olx.SphereMetricOptions} opt_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 meters). + */ +ol.Sphere.getArea = function(geometry, opt_options) { + var options = opt_options || {}; + var radius = options.radius || ol.Sphere.DEFAULT_RADIUS; + var projection = options.projection || 'EPSG:3857'; + geometry = geometry.clone().transform(projection, 'EPSG:4326'); + var type = geometry.getType(); + var area = 0; + var coordinates, coords, i, ii, j, jj; + switch (type) { + case ol.geom.GeometryType.POINT: + case ol.geom.GeometryType.MULTI_POINT: + case ol.geom.GeometryType.LINE_STRING: + case ol.geom.GeometryType.MULTI_LINE_STRING: + case ol.geom.GeometryType.LINEAR_RING: { + break; + } + case ol.geom.GeometryType.POLYGON: { + coordinates = /** @type {ol.geom.Polygon} */ (geometry).getCoordinates(); + area = Math.abs(ol.Sphere.getArea_(coordinates[0], radius)); + for (i = 1, ii = coordinates.length; i < ii; ++i) { + area -= Math.abs(ol.Sphere.getArea_(coordinates[i], radius)); + } + break; + } + case ol.geom.GeometryType.MULTI_POLYGON: { + coordinates = /** @type {ol.geom.SimpleGeometry} */ (geometry).getCoordinates(); + for (i = 0, ii = coordinates.length; i < ii; ++i) { + coords = coordinates[i]; + area += Math.abs(ol.Sphere.getArea_(coords[0], radius)); + for (j = 1, jj = coords.length; j < jj; ++j) { + area -= Math.abs(ol.Sphere.getArea_(coords[j], radius)); + } + } + break; + } + case ol.geom.GeometryType.GEOMETRY_COLLECTION: { + var geometries = /** @type {ol.geom.GeometryCollection} */ (geometry).getGeometries(); + for (i = 0, ii = geometries.length; i < ii; ++i) { + area += ol.Sphere.getArea(geometries[i], opt_options); + } + break; + } + default: { + throw new Error('Unsupported geometry type: ' + type); + } + } + return area; +}; + + +/** + * Returns the spherical area for a list of coordinates. + * + * [Reference](https://trs-new.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.} 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 meters). + */ +ol.Sphere.getArea_ = function(coordinates, radius) { + var area = 0, len = coordinates.length; + var x1 = coordinates[len - 1][0]; + var y1 = coordinates[len - 1][1]; + for (var i = 0; i < len; i++) { + var x2 = coordinates[i][0], y2 = coordinates[i][1]; + area += ol.math.toRadians(x2 - x1) * + (2 + Math.sin(ol.math.toRadians(y1)) + + Math.sin(ol.math.toRadians(y2))); + x1 = x2; + y1 = y2; + } + return area * radius * radius / 2.0; +}; diff --git a/test/spec/ol/sphere.test.js b/test/spec/ol/sphere.test.js index 4fb9ce5347..771c871e5a 100644 --- a/test/spec/ol/sphere.test.js +++ b/test/spec/ol/sphere.test.js @@ -177,3 +177,23 @@ describe('ol.Sphere.getLength()', function() { }); }); + +describe('ol.Sphere.getArea()', function() { + var geometry; + before(function(done) { + afterLoadText('spec/ol/format/wkt/illinois.wkt', function(wkt) { + try { + var format = new ol.format.WKT(); + geometry = format.readGeometry(wkt); + } catch (e) { + done(e); + } + done(); + }); + }); + + it('calculates the area of Ilinois', function() { + var area = ol.Sphere.getArea(geometry, {projection: 'EPSG:4326'}); + expect(area).to.equal(145652224192.4434); + }); +});