Replace ol/Sphere with ol/sphere

This commit is contained in:
Tim Schaub
2017-12-20 08:44:01 -08:00
parent 43342cd133
commit 54b8622114
9 changed files with 330 additions and 402 deletions

View File

@@ -35,6 +35,33 @@ register(proj4);
The map and sources no longer accept a `logo` option. Instead, if you wish to append a logo to your map, add the desired markup directly in your HTML. In addition, you can use the `attributions` property of a source to display arbitrary markup per-source with the attribution control. The map and sources no longer accept a `logo` option. Instead, if you wish to append a logo to your map, add the desired markup directly in your HTML. In addition, you can use the `attributions` property of a source to display arbitrary markup per-source with the attribution control.
#### Replacement of `ol/Sphere` constructor with `ol/sphere` functions
The `ol/Sphere` constructor has been removed. If you were using the `getGeodesicArea` method, use the `getArea` function instead. If you were using the `haversineDistance` method, use the `getDistance` function instead. The `circular` method in `ol/Polygon` no longer takes a sphere as the first argument.
Examples before:
```js
// using ol@4
import Polygon from 'ol/Polygon';
import Sphere from 'ol/Sphere';
var sphere = new Sphere(Sphere.DEFAULT_RADIUS);
var area = sphere.getGeodesicArea(polygon);
var distance = sphere.haversineDistance(g1, g2);
var circle = Polygon.circular(sphere, center, radius);
```
Examples after:
```js
// using ol@5
import {circular as circularPolygon} from 'ol/Polygon';
import {getArea, getDistance} from 'ol/sphere'; // note the lowercase
var area = getArea(polygon);
var distance = getDistance(g1, g2);
var circle = circularPolygon(center, radius);
```
#### Removal of optional this arguments. #### Removal of optional this arguments.
The following methods did get the optional this (i.e. opt_this) arguments removed. Please use closures, the es6 arrow function or the bind method to achieve this effect (Bind is explained here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). The following methods did get the optional this (i.e. opt_this) arguments removed. Please use closures, the es6 arrow function or the bind method to achieve this effect (Bind is explained here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).

View File

@@ -3,7 +3,7 @@ layout: example.html
title: Measure title: Measure
shortdesc: Using a draw interaction to measure lengths and areas. shortdesc: Using a draw interaction to measure lengths and areas.
docs: > docs: >
<p>The <code>ol.Sphere.getLength()</code> and <code>ol.Sphere.getArea()</code> <p>The <code>getLength()</code> and <code>getArea()</code>
functions calculate spherical lengths and areas for geometries. Lengths are functions calculate spherical lengths and areas for geometries. Lengths are
calculated by assuming great circle segments between geometry coordinates. calculated by assuming great circle segments between geometry coordinates.
Areas are calculated as if edges of polygons were great circle segments.</p> Areas are calculated as if edges of polygons were great circle segments.</p>
@@ -11,7 +11,7 @@ docs: >
methods return measures of projected (planar) geometries. These can be very methods return measures of projected (planar) geometries. These can be very
different than on-the-ground measures in certain situations — in northern different than on-the-ground measures in certain situations — in northern
and southern latitudes using Web Mercator for example. For better results, and southern latitudes using Web Mercator for example. For better results,
use the functions on <code>ol.Sphere</code>.</p> use the functions in the <code>ol/sphere</code> module.</p>
tags: "draw, edit, measure, vector" tags: "draw, edit, measure, vector"
--- ---
<div id="map" class="map"></div> <div id="map" class="map"></div>

View File

@@ -1,7 +1,7 @@
import _ol_Map_ from '../src/ol/Map.js'; import _ol_Map_ from '../src/ol/Map.js';
import _ol_Observable_ from '../src/ol/Observable.js'; import _ol_Observable_ from '../src/ol/Observable.js';
import _ol_Overlay_ from '../src/ol/Overlay.js'; import _ol_Overlay_ from '../src/ol/Overlay.js';
import _ol_Sphere_ from '../src/ol/Sphere.js'; import {getArea, getLength} from '../src/ol/sphere.js';
import _ol_View_ from '../src/ol/View.js'; import _ol_View_ from '../src/ol/View.js';
import LineString from '../src/ol/geom/LineString.js'; import LineString from '../src/ol/geom/LineString.js';
import Polygon from '../src/ol/geom/Polygon.js'; import Polygon from '../src/ol/geom/Polygon.js';
@@ -144,7 +144,7 @@ var draw; // global so we can remove it later
* @return {string} The formatted length. * @return {string} The formatted length.
*/ */
var formatLength = function(line) { var formatLength = function(line) {
var length = _ol_Sphere_.getLength(line); var length = getLength(line);
var output; var output;
if (length > 100) { if (length > 100) {
output = (Math.round(length / 1000 * 100) / 100) + output = (Math.round(length / 1000 * 100) / 100) +
@@ -163,7 +163,7 @@ var formatLength = function(line) {
* @return {string} Formatted area. * @return {string} Formatted area.
*/ */
var formatArea = function(polygon) { var formatArea = function(polygon) {
var area = _ol_Sphere_.getArea(polygon); var area = getArea(polygon);
var output; var output;
if (area > 10000) { if (area > 10000) {
output = (Math.round(area / 1000000 * 100) / 100) + output = (Math.round(area / 1000000 * 100) / 100) +

View File

@@ -1,6 +1,5 @@
import _ol_Feature_ from '../src/ol/Feature.js'; import _ol_Feature_ from '../src/ol/Feature.js';
import _ol_Map_ from '../src/ol/Map.js'; import _ol_Map_ from '../src/ol/Map.js';
import _ol_Sphere_ from '../src/ol/Sphere.js';
import _ol_View_ from '../src/ol/View.js'; import _ol_View_ from '../src/ol/View.js';
import Polygon from '../src/ol/geom/Polygon.js'; import Polygon from '../src/ol/geom/Polygon.js';
import TileLayer from '../src/ol/layer/Tile.js'; import TileLayer from '../src/ol/layer/Tile.js';
@@ -57,13 +56,11 @@ var map3857 = new _ol_Map_({
}) })
}); });
var wgs84Sphere = new _ol_Sphere_(6378137);
var radius = 800000; var radius = 800000;
var x, y; var x, y;
for (x = -180; x < 180; x += 30) { for (x = -180; x < 180; x += 30) {
for (y = -90; y < 90; y += 30) { for (y = -90; y < 90; y += 30) {
var circle4326 = Polygon.circular(wgs84Sphere, [x, y], radius, 64); var circle4326 = Polygon.circular([x, y], radius, 64);
var circle3857 = circle4326.clone().transform('EPSG:4326', 'EPSG:3857'); var circle3857 = circle4326.clone().transform('EPSG:4326', 'EPSG:3857');
vectorLayer4326.getSource().addFeature(new _ol_Feature_(circle4326)); vectorLayer4326.getSource().addFeature(new _ol_Feature_(circle4326));
vectorLayer3857.getSource().addFeature(new _ol_Feature_(circle3857)); vectorLayer3857.getSource().addFeature(new _ol_Feature_(circle3857));

View File

@@ -4,14 +4,12 @@
import {inherits} from './index.js'; import {inherits} from './index.js';
import _ol_GeolocationProperty_ from './GeolocationProperty.js'; import _ol_GeolocationProperty_ from './GeolocationProperty.js';
import _ol_Object_ from './Object.js'; import _ol_Object_ from './Object.js';
import _ol_Sphere_ from './Sphere.js';
import _ol_events_ from './events.js'; import _ol_events_ from './events.js';
import EventType from './events/EventType.js'; import EventType from './events/EventType.js';
import Polygon from './geom/Polygon.js'; import Polygon from './geom/Polygon.js';
import _ol_has_ from './has.js'; import _ol_has_ from './has.js';
import {toRadians} from './math.js'; import {toRadians} from './math.js';
import {get as getProjection, getTransformFromProjections, identityTransform} from './proj.js'; import {get as getProjection, getTransformFromProjections, identityTransform} from './proj.js';
import _ol_proj_EPSG4326_ from './proj/EPSG4326.js';
/** /**
@@ -74,12 +72,6 @@ var Geolocation = function(opt_options) {
*/ */
this.transform_ = identityTransform; this.transform_ = identityTransform;
/**
* @private
* @type {ol.Sphere}
*/
this.sphere_ = new _ol_Sphere_(_ol_proj_EPSG4326_.RADIUS);
/** /**
* @private * @private
* @type {number|undefined} * @type {number|undefined}
@@ -175,8 +167,7 @@ Geolocation.prototype.positionChange_ = function(position) {
this.set(_ol_GeolocationProperty_.POSITION, projectedPosition); this.set(_ol_GeolocationProperty_.POSITION, projectedPosition);
this.set(_ol_GeolocationProperty_.SPEED, this.set(_ol_GeolocationProperty_.SPEED,
coords.speed === null ? undefined : coords.speed); coords.speed === null ? undefined : coords.speed);
var geometry = Polygon.circular( var geometry = Polygon.circular(this.position_, coords.accuracy);
this.sphere_, this.position_, coords.accuracy);
geometry.applyTransform(this.transform_); geometry.applyTransform(this.transform_);
this.set(_ol_GeolocationProperty_.ACCURACY_GEOMETRY, geometry); this.set(_ol_GeolocationProperty_.ACCURACY_GEOMETRY, geometry);
this.changed(); this.changed();

View File

@@ -9,6 +9,7 @@ import GeometryType from '../geom/GeometryType.js';
import LinearRing from '../geom/LinearRing.js'; import LinearRing from '../geom/LinearRing.js';
import Point from '../geom/Point.js'; import Point from '../geom/Point.js';
import SimpleGeometry from '../geom/SimpleGeometry.js'; import SimpleGeometry from '../geom/SimpleGeometry.js';
import {offset as sphereOffset} from '../sphere.js';
import _ol_geom_flat_area_ from '../geom/flat/area.js'; import _ol_geom_flat_area_ from '../geom/flat/area.js';
import _ol_geom_flat_closest_ from '../geom/flat/closest.js'; import _ol_geom_flat_closest_ from '../geom/flat/closest.js';
import _ol_geom_flat_contains_ from '../geom/flat/contains.js'; import _ol_geom_flat_contains_ from '../geom/flat/contains.js';
@@ -369,22 +370,23 @@ Polygon.prototype.setFlatCoordinates = function(layout, flatCoordinates, ends) {
/** /**
* Create an approximation of a circle on the surface of a sphere. * Create an approximation of a circle on the surface of a sphere.
* @param {ol.Sphere} sphere The sphere.
* @param {ol.Coordinate} center Center (`[lon, lat]` in degrees). * @param {ol.Coordinate} center Center (`[lon, lat]` in degrees).
* @param {number} radius The great-circle distance from the center to * @param {number} radius The great-circle distance from the center to
* the polygon vertices. * the polygon vertices.
* @param {number=} opt_n Optional number of vertices for the resulting * @param {number=} opt_n Optional number of vertices for the resulting
* polygon. Default is `32`. * polygon. Default is `32`.
* @param {number=} opt_sphereRadius Optional radius for the sphere (defaults to
* the Earth's mean radius using the WGS84 ellipsoid).
* @return {ol.geom.Polygon} The "circular" polygon. * @return {ol.geom.Polygon} The "circular" polygon.
* @api * @api
*/ */
Polygon.circular = function(sphere, center, radius, opt_n) { Polygon.circular = function(center, radius, opt_n, opt_sphereRadius) {
var n = opt_n ? opt_n : 32; var n = opt_n ? opt_n : 32;
/** @type {Array.<number>} */ /** @type {Array.<number>} */
var flatCoordinates = []; var flatCoordinates = [];
var i; var i;
for (i = 0; i < n; ++i) { for (i = 0; i < n; ++i) {
extend(flatCoordinates, sphere.offset(center, radius, 2 * Math.PI * i / n)); extend(flatCoordinates, sphereOffset(center, radius, 2 * Math.PI * i / n, opt_sphereRadius));
} }
flatCoordinates.push(flatCoordinates[0], flatCoordinates[1]); flatCoordinates.push(flatCoordinates[0], flatCoordinates[1]);
var polygon = new Polygon(null); var polygon = new Polygon(null);

View File

@@ -1,7 +1,7 @@
/** /**
* @module ol/proj * @module ol/proj
*/ */
import Sphere from './Sphere.js'; import {getDistance} from './sphere.js';
import {applyTransform} from './extent.js'; import {applyTransform} from './extent.js';
import {modulo} from './math.js'; import {modulo} from './math.js';
import EPSG3857 from './proj/EPSG3857.js'; import EPSG3857 from './proj/EPSG3857.js';
@@ -21,13 +21,6 @@ import {add as addTransformFunc, clear as clearTransformFuncs, get as getTransfo
export var METERS_PER_UNIT = Units.METERS_PER_UNIT; export var METERS_PER_UNIT = Units.METERS_PER_UNIT;
/**
* A place to store the mean radius of the Earth.
* @type {ol.Sphere}
*/
var SPHERE = new Sphere(Sphere.DEFAULT_RADIUS);
/** /**
* @param {Array.<number>} input Input coordinate array. * @param {Array.<number>} input Input coordinate array.
* @param {Array.<number>=} opt_output Output array of coordinate values. * @param {Array.<number>=} opt_output Output array of coordinate values.
@@ -150,10 +143,8 @@ export function getPointResolution(projection, resolution, point, opt_units) {
point[0], point[1] + resolution / 2 point[0], point[1] + resolution / 2
]; ];
vertices = toEPSG4326(vertices, vertices, 2); vertices = toEPSG4326(vertices, vertices, 2);
var width = SPHERE.haversineDistance( var width = getDistance(vertices.slice(0, 2), vertices.slice(2, 4));
vertices.slice(0, 2), vertices.slice(2, 4)); var height = getDistance(vertices.slice(4, 6), vertices.slice(6, 8));
var height = SPHERE.haversineDistance(
vertices.slice(4, 6), vertices.slice(6, 8));
pointResolution = (width + height) / 2; pointResolution = (width + height) / 2;
var metersPerUnit = opt_units ? var metersPerUnit = opt_units ?
Units.METERS_PER_UNIT[opt_units] : Units.METERS_PER_UNIT[opt_units] :

View File

@@ -1,113 +1,69 @@
/**
* @module ol/Sphere
*/
/** /**
* @license * @license
* Latitude/longitude spherical geodesy formulae taken from * Latitude/longitude spherical geodesy formulae taken from
* http://www.movable-type.co.uk/scripts/latlong.html * http://www.movable-type.co.uk/scripts/latlong.html
* Licensed under CC-BY-3.0. * Licensed under CC-BY-3.0.
*/ */
/**
* @module ol/Sphere
*/
import {toRadians, toDegrees} from './math.js'; import {toRadians, toDegrees} from './math.js';
import GeometryType from './geom/GeometryType.js'; import GeometryType from './geom/GeometryType.js';
/** /**
* Object literal with options for the {@link ol.Sphere.getLength} or * Object literal with options for the {@link getLength} or
* {@link ol.Sphere.getArea} functions. * {@link getArea} functions.
* @typedef {{projection: (ol.ProjectionLike|undefined), * @typedef {{projection: (ol.ProjectionLike|undefined),
* radius: (number|undefined)}} * radius: (number|undefined)}}
*/ */
export var SphereMetricOptions; export var SphereMetricOptions;
/**
* @classdesc
* Class to create objects that can be used with {@link
* ol.geom.Polygon.circular}.
*
* For example to create a sphere whose radius is equal to the semi-major
* axis of the WGS84 ellipsoid:
*
* ```js
* var wgs84Sphere= new ol.Sphere(6378137);
* ```
*
* @constructor
* @param {number} radius Radius.
* @api
*/
var _ol_Sphere_ = function(radius) {
/**
* @type {number}
*/
this.radius = radius;
};
/**
* Returns the geodesic 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.<ol.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.
* @return {number} Area.
* @api
*/
_ol_Sphere_.prototype.geodesicArea = function(coordinates) {
return _ol_Sphere_.getArea_(coordinates, this.radius);
};
/**
* Returns the distance from c1 to c2 using the haversine formula.
*
* @param {ol.Coordinate} c1 Coordinate 1.
* @param {ol.Coordinate} c2 Coordinate 2.
* @return {number} Haversine distance.
* @api
*/
_ol_Sphere_.prototype.haversineDistance = function(c1, c2) {
return _ol_Sphere_.getDistance_(c1, c2, this.radius);
};
/**
* Returns the coordinate at the given distance and bearing from `c1`.
*
* @param {ol.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).
* @return {ol.Coordinate} The target point.
*/
_ol_Sphere_.prototype.offset = function(c1, distance, bearing) {
var lat1 = toRadians(c1[1]);
var lon1 = toRadians(c1[0]);
var dByR = distance / this.radius;
var lat = Math.asin(
Math.sin(lat1) * Math.cos(dByR) +
Math.cos(lat1) * Math.sin(dByR) * Math.cos(bearing));
var 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)];
};
/** /**
* The mean Earth radius (1/3 * (2a + b)) for the WGS84 ellipsoid. * The mean Earth radius (1/3 * (2a + b)) for the WGS84 ellipsoid.
* https://en.wikipedia.org/wiki/Earth_radius#Mean_radius * https://en.wikipedia.org/wiki/Earth_radius#Mean_radius
* @type {number} * @type {number}
*/ */
_ol_Sphere_.DEFAULT_RADIUS = 6371008.8; export var 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=} opt_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, opt_radius) {
var radius = opt_radius || DEFAULT_RADIUS;
var lat1 = toRadians(c1[1]);
var lat2 = toRadians(c2[1]);
var deltaLatBy2 = (lat2 - lat1) / 2;
var deltaLonBy2 = toRadians(c2[0] - c1[0]) / 2;
var 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) {
var length = 0;
for (var i = 0, ii = coordinates.length; i < ii - 1; ++i) {
length += getDistance(coordinates[i], coordinates[i + 1], radius);
}
return length;
}
/** /**
@@ -122,17 +78,19 @@ _ol_Sphere_.DEFAULT_RADIUS = 6371008.8;
* @param {(ol.ProjectionLike|undefined)} opt_options.projection Projection of * @param {(ol.ProjectionLike|undefined)} opt_options.projection Projection of
* the geometry. By default, the geometry is assumed to be in EPSG:3857 * the geometry. By default, the geometry is assumed to be in EPSG:3857
* (Web Mercator). * (Web Mercator).
* @param {(number|undefined)} opt_options.radius Sphere radius. By default, * @param {(number|undefined)} opt_options.radius Sphere radius. Defaults to
* the radius of the earth is used (Clarke 1866 Authalic Sphere). * the Earth's mean radius using the WGS84 ellipsoid.
* @return {number} The spherical length (in meters). * @return {number} The spherical length (in meters).
* @api * @api
*/ */
_ol_Sphere_.getLength = function(geometry, opt_options) { export function getLength(geometry, opt_options) {
var options = opt_options || {}; var options = opt_options || {};
var radius = options.radius || _ol_Sphere_.DEFAULT_RADIUS; var radius = options.radius || DEFAULT_RADIUS;
var projection = options.projection || 'EPSG:3857'; var projection = options.projection || 'EPSG:3857';
geometry = geometry.clone().transform(projection, 'EPSG:4326');
var type = geometry.getType(); var type = geometry.getType();
if (type !== GeometryType.GEOMETRY_COLLECTION) {
geometry = geometry.clone().transform(projection, 'EPSG:4326');
}
var length = 0; var length = 0;
var coordinates, coords, i, ii, j, jj; var coordinates, coords, i, ii, j, jj;
switch (type) { switch (type) {
@@ -143,14 +101,14 @@ _ol_Sphere_.getLength = function(geometry, opt_options) {
case GeometryType.LINE_STRING: case GeometryType.LINE_STRING:
case GeometryType.LINEAR_RING: { case GeometryType.LINEAR_RING: {
coordinates = /** @type {ol.geom.SimpleGeometry} */ (geometry).getCoordinates(); coordinates = /** @type {ol.geom.SimpleGeometry} */ (geometry).getCoordinates();
length = _ol_Sphere_.getLength_(coordinates, radius); length = getLengthInternal(coordinates, radius);
break; break;
} }
case GeometryType.MULTI_LINE_STRING: case GeometryType.MULTI_LINE_STRING:
case GeometryType.POLYGON: { case GeometryType.POLYGON: {
coordinates = /** @type {ol.geom.SimpleGeometry} */ (geometry).getCoordinates(); coordinates = /** @type {ol.geom.SimpleGeometry} */ (geometry).getCoordinates();
for (i = 0, ii = coordinates.length; i < ii; ++i) { for (i = 0, ii = coordinates.length; i < ii; ++i) {
length += _ol_Sphere_.getLength_(coordinates[i], radius); length += getLengthInternal(coordinates[i], radius);
} }
break; break;
} }
@@ -159,7 +117,7 @@ _ol_Sphere_.getLength = function(geometry, opt_options) {
for (i = 0, ii = coordinates.length; i < ii; ++i) { for (i = 0, ii = coordinates.length; i < ii; ++i) {
coords = coordinates[i]; coords = coordinates[i];
for (j = 0, jj = coords.length; j < jj; ++j) { for (j = 0, jj = coords.length; j < jj; ++j) {
length += _ol_Sphere_.getLength_(coords[j], radius); length += getLengthInternal(coords[j], radius);
} }
} }
break; break;
@@ -167,7 +125,7 @@ _ol_Sphere_.getLength = function(geometry, opt_options) {
case GeometryType.GEOMETRY_COLLECTION: { case GeometryType.GEOMETRY_COLLECTION: {
var geometries = /** @type {ol.geom.GeometryCollection} */ (geometry).getGeometries(); var geometries = /** @type {ol.geom.GeometryCollection} */ (geometry).getGeometries();
for (i = 0, ii = geometries.length; i < ii; ++i) { for (i = 0, ii = geometries.length; i < ii; ++i) {
length += _ol_Sphere_.getLength(geometries[i], opt_options); length += getLength(geometries[i], opt_options);
} }
break; break;
} }
@@ -176,101 +134,7 @@ _ol_Sphere_.getLength = function(geometry, opt_options) {
} }
} }
return length; return length;
};
/**
* 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).
*/
_ol_Sphere_.getLength_ = function(coordinates, radius) {
var length = 0;
for (var i = 0, ii = coordinates.length; i < ii - 1; ++i) {
length += _ol_Sphere_.getDistance_(coordinates[i], coordinates[i + 1], radius);
} }
return length;
};
/**
* Get the great circle distance between two geographic coordinates.
* @param {Array} c1 Starting coordinate.
* @param {Array} c2 Ending coordinate.
* @param {number} radius The sphere radius to use.
* @return {number} The great circle distance between the points (in meters).
*/
_ol_Sphere_.getDistance_ = function(c1, c2, radius) {
var lat1 = toRadians(c1[1]);
var lat2 = toRadians(c2[1]);
var deltaLatBy2 = (lat2 - lat1) / 2;
var deltaLonBy2 = toRadians(c2[0] - c1[0]) / 2;
var 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 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 {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 square meters).
* @api
*/
_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 GeometryType.POINT:
case GeometryType.MULTI_POINT:
case GeometryType.LINE_STRING:
case GeometryType.MULTI_LINE_STRING:
case GeometryType.LINEAR_RING: {
break;
}
case 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 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 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;
};
/** /**
@@ -287,7 +151,7 @@ _ol_Sphere_.getArea = function(geometry, opt_options) {
* @param {number} radius The sphere radius. * @param {number} radius The sphere radius.
* @return {number} Area (in square meters). * @return {number} Area (in square meters).
*/ */
_ol_Sphere_.getArea_ = function(coordinates, radius) { function getAreaInternal(coordinates, radius) {
var area = 0, len = coordinates.length; var area = 0, len = coordinates.length;
var x1 = coordinates[len - 1][0]; var x1 = coordinates[len - 1][0];
var y1 = coordinates[len - 1][1]; var y1 = coordinates[len - 1][1];
@@ -300,5 +164,92 @@ _ol_Sphere_.getArea_ = function(coordinates, radius) {
y1 = y2; y1 = y2;
} }
return area * radius * radius / 2.0; return area * radius * radius / 2.0;
}; }
export default _ol_Sphere_;
/**
* 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 {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 square meters).
* @api
*/
export function getArea(geometry, opt_options) {
var options = opt_options || {};
var radius = options.radius || DEFAULT_RADIUS;
var projection = options.projection || 'EPSG:3857';
var type = geometry.getType();
if (type !== GeometryType.GEOMETRY_COLLECTION) {
geometry = geometry.clone().transform(projection, 'EPSG:4326');
}
var area = 0;
var coordinates, coords, i, ii, j, jj;
switch (type) {
case GeometryType.POINT:
case GeometryType.MULTI_POINT:
case GeometryType.LINE_STRING:
case GeometryType.MULTI_LINE_STRING:
case GeometryType.LINEAR_RING: {
break;
}
case GeometryType.POLYGON: {
coordinates = /** @type {ol.geom.Polygon} */ (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 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(getAreaInternal(coords[0], radius));
for (j = 1, jj = coords.length; j < jj; ++j) {
area -= Math.abs(getAreaInternal(coords[j], radius));
}
}
break;
}
case GeometryType.GEOMETRY_COLLECTION: {
var geometries = /** @type {ol.geom.GeometryCollection} */ (geometry).getGeometries();
for (i = 0, ii = geometries.length; i < ii; ++i) {
area += getArea(geometries[i], opt_options);
}
break;
}
default: {
throw new Error('Unsupported geometry type: ' + type);
}
}
return area;
}
/**
* Returns the coordinate at the given distance and bearing from `c1`.
*
* @param {ol.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=} opt_radius The sphere radius to use. Defaults to the Earth's
* mean radius using the WGS84 ellipsoid.
* @return {ol.Coordinate} The target point.
*/
export function offset(c1, distance, bearing, opt_radius) {
var radius = opt_radius || DEFAULT_RADIUS;
var lat1 = toRadians(c1[1]);
var lon1 = toRadians(c1[0]);
var dByR = distance / radius;
var lat = Math.asin(
Math.sin(lat1) * Math.cos(dByR) +
Math.cos(lat1) * Math.sin(dByR) * Math.cos(bearing));
var 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)];
}

View File

@@ -1,119 +1,86 @@
// See http://www.movable-type.co.uk/scripts/latlong.html import {getArea, getDistance, getLength} from '../../../src/ol/sphere.js';
// FIXME add tests for offset
import _ol_Sphere_ from '../../../src/ol/Sphere.js';
import WKT from '../../../src/ol/format/WKT.js'; import WKT from '../../../src/ol/format/WKT.js';
import GeometryCollection from '../../../src/ol/geom/GeometryCollection.js'; import GeometryCollection from '../../../src/ol/geom/GeometryCollection.js';
import LineString from '../../../src/ol/geom/LineString.js'; import LineString from '../../../src/ol/geom/LineString.js';
import MultiLineString from '../../../src/ol/geom/MultiLineString.js'; import MultiLineString from '../../../src/ol/geom/MultiLineString.js';
import MultiPoint from '../../../src/ol/geom/MultiPoint.js'; import MultiPoint from '../../../src/ol/geom/MultiPoint.js';
import Point from '../../../src/ol/geom/Point.js'; import Point from '../../../src/ol/geom/Point.js';
import _ol_proj_EPSG4326_ from '../../../src/ol/proj/EPSG4326.js';
describe('ol.Sphere', function() { describe('ol/sphere', function() {
describe('getDistance()', function() {
var sphere = new _ol_Sphere_(6371);
var expected = [{ var expected = [{
c1: [0, 0], c1: [0, 0],
c2: [0, 0], c2: [0, 0],
haversineDistance: 0 distance: 0
}, { }, {
c1: [0, 0], c1: [0, 0],
c2: [45, 45], c2: [45, 45],
haversineDistance: 6671.695598673525 distance: 6671704.814011975
}, { }, {
c1: [0, 0], c1: [0, 0],
c2: [-45, 45], c2: [-45, 45],
haversineDistance: 6671.695598673525 distance: 6671704.814011975
}, { }, {
c1: [0, 0], c1: [0, 0],
c2: [-45, -45], c2: [-45, -45],
haversineDistance: 6671.695598673525 distance: 6671704.814011975
}, { }, {
c1: [0, 0], c1: [0, 0],
c2: [45, -45], c2: [45, -45],
haversineDistance: 6671.695598673525 distance: 6671704.814011975
}, { }, {
c1: [45, 45], c1: [45, 45],
c2: [45, 45], c2: [45, 45],
haversineDistance: 0 distance: 0
}, { }, {
c1: [45, 45], c1: [45, 45],
c2: [-45, 45], c2: [-45, 45],
haversineDistance: 6671.695598673525 distance: 6671704.814011975
}, { }, {
c1: [45, 45], c1: [45, 45],
c2: [-45, -45], c2: [-45, -45],
haversineDistance: 13343.391197347048 distance: 13343409.628023949
}, { }, {
c1: [45, 45], c1: [45, 45],
c2: [45, -45], c2: [45, -45],
haversineDistance: 10007.543398010286 distance: 10007557.221017962
}, { }, {
c1: [-45, 45], c1: [-45, 45],
c2: [-45, 45], c2: [-45, 45],
haversineDistance: 0 distance: 0
}, { }, {
c1: [-45, 45], c1: [-45, 45],
c2: [-45, -45], c2: [-45, -45],
haversineDistance: 10007.543398010286 distance: 10007557.221017962
}, { }, {
c1: [-45, 45], c1: [-45, 45],
c2: [45, -45], c2: [45, -45],
haversineDistance: 13343.391197347048 distance: 13343409.628023949
}, { }, {
c1: [-45, -45], c1: [-45, -45],
c2: [-45, -45], c2: [-45, -45],
haversineDistance: 0 distance: 0
}, { }, {
c1: [-45, -45], c1: [-45, -45],
c2: [45, -45], c2: [45, -45],
haversineDistance: 6671.695598673525 distance: 6671704.814011975
}, { }, {
c1: [45, -45], c1: [45, -45],
c2: [45, -45], c2: [45, -45],
haversineDistance: 0 distance: 0
}]; }];
describe('haversineDistance', function() { expected.forEach(function(e, i) {
it('calculates the distance between two points: ' + i, function() {
it('results match Chris Veness\'s reference implementation', function() { expect(getDistance(e.c1, e.c2)).to.roughlyEqual(e.distance, 1e-6);
var e, i;
for (i = 0; i < expected.length; ++i) {
e = expected[i];
expect(sphere.haversineDistance(e.c1, e.c2)).to.roughlyEqual(
e.haversineDistance, 1e-9);
}
}); });
});
describe('Vincenty area', function() {
var geometry;
before(function(done) {
afterLoadText('spec/ol/format/wkt/illinois.wkt', function(wkt) {
try {
var format = new WKT();
geometry = format.readGeometry(wkt);
} catch (e) {
done(e);
}
done();
}); });
}); });
it('results match the expected area of Ilinois', function() { describe('getLength()', function() {
var coords = geometry.getPolygon(0).getLinearRing(0).getCoordinates();
var sphere = new _ol_Sphere_(_ol_proj_EPSG4326_.RADIUS);
expect(sphere.geodesicArea(coords)).to.equal(145978332359.37125);
});
});
});
describe('ol.Sphere.getLength()', function() {
var cases = [{ var cases = [{
geometry: new Point([0, 0]), geometry: new Point([0, 0]),
length: 0 length: 0
@@ -175,14 +142,14 @@ describe('ol.Sphere.getLength()', function() {
cases.forEach(function(c, i) { cases.forEach(function(c, i) {
it('works for case ' + i, function() { it('works for case ' + i, function() {
var c = cases[i]; var c = cases[i];
var length = _ol_Sphere_.getLength(c.geometry, c.options); var length = getLength(c.geometry, c.options);
expect(length).to.roughlyEqual(c.length, 1e-8); expect(length).to.roughlyEqual(c.length, 1e-8);
}); });
}); });
}); });
describe('ol.Sphere.getArea()', function() { describe('getArea()', function() {
var geometry; var geometry;
before(function(done) { before(function(done) {
afterLoadText('spec/ol/format/wkt/illinois.wkt', function(wkt) { afterLoadText('spec/ol/format/wkt/illinois.wkt', function(wkt) {
@@ -197,7 +164,9 @@ describe('ol.Sphere.getArea()', function() {
}); });
it('calculates the area of Ilinois', function() { it('calculates the area of Ilinois', function() {
var area = _ol_Sphere_.getArea(geometry, {projection: 'EPSG:4326'}); var area = getArea(geometry, {projection: 'EPSG:4326'});
expect(area).to.equal(145652224192.4434); expect(area).to.equal(145652224192.4434);
}); });
}); });
});