mirror of
https://github.com/google/earthengine-api.git
synced 2025-12-08 19:26:12 +00:00
1061 lines
37 KiB
JavaScript
1061 lines
37 KiB
JavaScript
/**
|
|
* @fileoverview An object representing EE Geometries.
|
|
* @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed
|
|
*/
|
|
|
|
goog.provide('ee.Geometry');
|
|
goog.provide('ee.Geometry.LineString');
|
|
goog.provide('ee.Geometry.LinearRing');
|
|
goog.provide('ee.Geometry.MultiLineString');
|
|
goog.provide('ee.Geometry.MultiPoint');
|
|
goog.provide('ee.Geometry.MultiPolygon');
|
|
goog.provide('ee.Geometry.Point');
|
|
goog.provide('ee.Geometry.Polygon');
|
|
goog.provide('ee.Geometry.Rectangle');
|
|
|
|
goog.require('ee.ApiFunction');
|
|
goog.require('ee.ComputedObject');
|
|
goog.require('ee.Serializer');
|
|
goog.require('ee.Types');
|
|
goog.require('ee.arguments');
|
|
goog.require('goog.array');
|
|
goog.require('goog.json.Serializer');
|
|
goog.require('goog.object');
|
|
|
|
|
|
|
|
/**
|
|
* Creates a geometry.
|
|
* @param {Object} geoJson The GeoJSON object describing the geometry or
|
|
* a ComputedObject to be reinterpreted as a Geometry. Supports
|
|
* CRS specifications as per the GeoJSON spec, but only allows named
|
|
* (rather than "linked" CRSs). If this includes a 'geodesic' field,
|
|
* and opt_geodesic is not specified, it will be used as opt_geodesic.
|
|
* @param {ee.Projection=} opt_proj An optional projection specification, either
|
|
* as a CRS ID code or as a WKT string. If specified, overrides any CRS
|
|
* found in the geoJson parameter. If unspecified and the geoJson does not
|
|
* declare a CRS, defaults to "EPSG:4326" (x=longitude, y=latitude).
|
|
* @param {boolean=} opt_geodesic Whether line segments should be interpreted
|
|
* as spherical geodesics. If false, indicates that line segments should
|
|
* be interpreted as planar lines in the specified CRS. If absent, defaults
|
|
* to true if the CRS is geographic (including the default EPSG:4326),
|
|
* or to false if the CRS is projected.
|
|
* @param {boolean=} opt_evenOdd If true, polygon interiors will be determined
|
|
* by the even/odd rule, where a point is inside if it crosses an odd number
|
|
* of edges to reach a point at infinity. Otherwise polygons use the left-
|
|
* inside rule, where interiors are on the left side of the shell's edges
|
|
* when walking the vertices in the given order. If unspecified, defaults to
|
|
* true.
|
|
* @constructor
|
|
* @extends {ee.ComputedObject}
|
|
* @export
|
|
*/
|
|
ee.Geometry = function(geoJson, opt_proj, opt_geodesic, opt_evenOdd) {
|
|
if (!(this instanceof ee.Geometry)) {
|
|
return ee.ComputedObject.construct(ee.Geometry, arguments);
|
|
}
|
|
|
|
// Note: evenOdd is a parameter name and may be a key in the
|
|
// first argument, the geoJson object. This means
|
|
// ee.arguments.extractFromFunction() cannot reliably differentiate:
|
|
//
|
|
// 1) ee.Geometry(myGeoJsonObject)
|
|
// from 2) ee.Geometry({geoJson: myGeoJsonObject})
|
|
//
|
|
// However, we know that the geoJson object MUST contain the "type" key,
|
|
// which is not an expected param name. If we see this key in the first
|
|
// argument, we know the arguments were passed in sequence. If not, we
|
|
// assume the user intended to pass a named argument dictionary and use
|
|
// ee.arguments.extractFromFunction() to validate and extract the keys.
|
|
if (!('type' in geoJson)) {
|
|
var args = ee.arguments.extractFromFunction(ee.Geometry, arguments);
|
|
geoJson = args['geoJson'];
|
|
opt_proj = args['proj'];
|
|
opt_geodesic = args['geodesic'];
|
|
opt_evenOdd = args['evenOdd'];
|
|
}
|
|
|
|
ee.Geometry.initialize();
|
|
|
|
var computed = geoJson instanceof ee.ComputedObject &&
|
|
!(geoJson instanceof ee.Geometry && geoJson.type_);
|
|
var options =
|
|
(opt_proj != null || opt_geodesic != null || opt_evenOdd != null);
|
|
if (computed) {
|
|
if (options) {
|
|
throw new Error(
|
|
'Setting the CRS, geodesic, or evenOdd flag on a computed Geometry ' +
|
|
'is not supported. Use Geometry.transform().');
|
|
} else {
|
|
ee.Geometry.base(this, 'constructor', geoJson.func, geoJson.args, geoJson.varName);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Below here, we're working with a GeoJSON literal.
|
|
if (geoJson instanceof ee.Geometry) {
|
|
geoJson = /** @type {Object} */(geoJson.encode());
|
|
}
|
|
|
|
if (!ee.Geometry.isValidGeometry_(geoJson)) {
|
|
throw Error('Invalid GeoJSON geometry: ' + JSON.stringify(geoJson));
|
|
}
|
|
|
|
ee.Geometry.base(this, 'constructor', null, null);
|
|
|
|
/**
|
|
* The type of the geometry.
|
|
* @type {string}
|
|
* @private
|
|
*/
|
|
this.type_ = geoJson['type'];
|
|
|
|
/**
|
|
* The coordinates of the geometry, up to 4 nested levels with numbers at
|
|
* the last level. Null iff type is GeometryCollection.
|
|
* @type {Array?}
|
|
* @private
|
|
*/
|
|
this.coordinates_ = (geoJson['coordinates'] != null) ?
|
|
goog.object.unsafeClone(geoJson['coordinates']) :
|
|
null;
|
|
|
|
/**
|
|
* The subgeometries, non-null iff type is GeometryCollection.
|
|
* @type {Array?}
|
|
* @private
|
|
*/
|
|
this.geometries_ = geoJson['geometries'] || null;
|
|
|
|
/**
|
|
* The projection of the geometry.
|
|
* @type {String|undefined}
|
|
* @private
|
|
*/
|
|
this.proj_;
|
|
if (opt_proj != null) {
|
|
this.proj_ = opt_proj;
|
|
} else if ('crs' in geoJson) {
|
|
if (goog.isObject(geoJson['crs']) && geoJson['crs']['type'] == 'name' &&
|
|
goog.isObject(geoJson['crs']['properties']) &&
|
|
typeof geoJson['crs']['properties']['name'] === 'string') {
|
|
this.proj_ = geoJson['crs']['properties']['name'];
|
|
} else {
|
|
throw Error('Invalid CRS declaration in GeoJSON: ' +
|
|
(new goog.json.Serializer()).serialize(geoJson['crs']));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Whether the geometry has spherical geodesic edges.
|
|
* @type {boolean|undefined}
|
|
* @private
|
|
*/
|
|
this.geodesic_ = opt_geodesic;
|
|
if (this.geodesic_ === undefined && 'geodesic' in geoJson) {
|
|
this.geodesic_ = Boolean(geoJson['geodesic']);
|
|
}
|
|
|
|
/**
|
|
* Whether polygon interiors are based on the even/odd rule. If false,
|
|
* the left-inside rule is used. If unspecified, defaults to true.
|
|
* @type {boolean|undefined}
|
|
* @private
|
|
*/
|
|
this.evenOdd_ = opt_evenOdd;
|
|
if (this.evenOdd_ === undefined && 'evenOdd' in geoJson) {
|
|
this.evenOdd_ = Boolean(geoJson['evenOdd']);
|
|
}
|
|
};
|
|
goog.inherits(ee.Geometry, ee.ComputedObject);
|
|
|
|
|
|
/**
|
|
* Whether the class has been initialized with API functions.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
ee.Geometry.initialized_ = false;
|
|
|
|
|
|
/**
|
|
* Imports API functions to this class.
|
|
*/
|
|
ee.Geometry.initialize = function() {
|
|
if (!ee.Geometry.initialized_) {
|
|
ee.ApiFunction.importApi(ee.Geometry, 'Geometry', 'Geometry');
|
|
ee.Geometry.initialized_ = true;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Removes imported API functions from this class.
|
|
*/
|
|
ee.Geometry.reset = function() {
|
|
ee.ApiFunction.clearApi(ee.Geometry);
|
|
ee.Geometry.initialized_ = false;
|
|
};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Subclass constructors. //
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
/**
|
|
* Constructs an ee.Geometry describing a point.
|
|
*
|
|
* For convenience, varargs may be used when all arguments are numbers. This
|
|
* allows creating EPSG:4326 points, e.g. ee.Geometry.Point(lng, lat).
|
|
*
|
|
* @param {!Array<number>} coords A list of two [x,y] coordinates in the given
|
|
* projection.
|
|
* @param {ee.Projection=} opt_proj The projection of this geometry, or
|
|
* EPSG:4326 if unspecified.
|
|
* @constructor
|
|
* @extends {ee.Geometry}
|
|
* @export
|
|
*/
|
|
ee.Geometry.Point = function(coords, opt_proj) {
|
|
if (!(this instanceof ee.Geometry.Point)) {
|
|
return ee.Geometry.createInstance_(ee.Geometry.Point, arguments);
|
|
}
|
|
var init = ee.Geometry.construct_(ee.Geometry.Point, 'Point', 1, arguments);
|
|
if (!(init instanceof ee.ComputedObject)) {
|
|
var xy = init['coordinates'];
|
|
if (!Array.isArray(xy) || xy.length != 2) {
|
|
throw Error('The Geometry.Point constructor requires 2 coordinates.');
|
|
}
|
|
}
|
|
ee.Geometry.Point.base(this, 'constructor', init);
|
|
};
|
|
goog.inherits(ee.Geometry.Point, ee.Geometry);
|
|
|
|
|
|
|
|
/**
|
|
* Constructs an ee.Geometry describing a MultiPoint.
|
|
*
|
|
* For convenience, varargs may be used when all arguments are numbers. This
|
|
* allows creating EPSG:4326 MultiPoints given an even number of arguments,
|
|
* e.g. ee.Geometry.MultiPoint(aLng, aLat, bLng, bLat, ...).
|
|
*
|
|
* @param {!Array<!Array<number>>|
|
|
* Array<!ee.Geometry>|
|
|
* Array<number>} coords
|
|
* A list of points, each in the GeoJSON 'coordinates' format of a Point, or
|
|
* a list of the x,y coordinates in the given projection, or an ee.Geometry
|
|
* describing a point.
|
|
* @param {ee.Projection=} opt_proj The projection of this geometry. If
|
|
* unspecified, the default is the projection of the input ee.Geometry, or
|
|
* EPSG:4326 if there are no ee.Geometry inputs.
|
|
* @constructor
|
|
* @extends {ee.Geometry}
|
|
* @export
|
|
*/
|
|
ee.Geometry.MultiPoint = function(coords, opt_proj) {
|
|
if (!(this instanceof ee.Geometry.MultiPoint)) {
|
|
return ee.Geometry.createInstance_(ee.Geometry.MultiPoint, arguments);
|
|
}
|
|
ee.Geometry.MultiPoint.base(this, 'constructor', ee.Geometry.construct_(
|
|
ee.Geometry.MultiPoint, 'MultiPoint', 2, arguments));
|
|
};
|
|
goog.inherits(ee.Geometry.MultiPoint, ee.Geometry);
|
|
|
|
|
|
|
|
/**
|
|
* Constructs an ee.Geometry describing a rectangular polygon.
|
|
*
|
|
* For convenience, varargs may be used when all arguments are numbers. This
|
|
* allows creating EPSG:4326 Polygons given exactly four coordinates,
|
|
* e.g. ee.Geometry.Rectangle(minLng, minLat, maxLng, maxLat).
|
|
*
|
|
* @param {!Array<!Array<number>>|
|
|
* Array<!ee.Geometry>|
|
|
* Array<number>} coords
|
|
* The minimum and maximum corners of the rectangle, as a list of two points
|
|
* each in the format of GeoJSON 'Point' coordinates, or a list of two
|
|
* ee.Geometry objects describing a point, or a list of four numbers in the
|
|
* order xMin, yMin, xMax, yMax.
|
|
* @param {ee.Projection=} opt_proj The projection of this geometry. If
|
|
* unspecified, the default is the projection of the input ee.Geometry, or
|
|
* EPSG:4326 if there are no ee.Geometry inputs.
|
|
* @param {boolean=} opt_geodesic If false, edges are straight in the
|
|
* projection. If true, edges are curved to follow the shortest path on the
|
|
* surface of the Earth. The default is the geodesic state of the inputs, or
|
|
* true if the inputs are numbers.
|
|
* @param {boolean=} opt_evenOdd If true, polygon interiors will be determined
|
|
* by the even/odd rule, where a point is inside if it crosses an odd number
|
|
* of edges to reach a point at infinity. Otherwise polygons use the left-
|
|
* inside rule, where interiors are on the left side of the shell's edges
|
|
* when walking the vertices in the given order. If unspecified, defaults to
|
|
* true.
|
|
* @constructor
|
|
* @extends {ee.Geometry}
|
|
* @export
|
|
*/
|
|
ee.Geometry.Rectangle = function(coords, opt_proj, opt_geodesic, opt_evenOdd) {
|
|
if (!(this instanceof ee.Geometry.Rectangle)) {
|
|
return ee.Geometry.createInstance_(ee.Geometry.Rectangle, arguments);
|
|
}
|
|
var init = ee.Geometry.construct_(
|
|
ee.Geometry.Rectangle, 'Rectangle', 2, arguments);
|
|
if (!(init instanceof ee.ComputedObject)) {
|
|
// GeoJSON does not have a 'Rectangle' type, so expand it into a Polygon.
|
|
var xy = init['coordinates'];
|
|
if (xy.length != 2) {
|
|
throw Error('The Geometry.Rectangle constructor requires 2 points or 4 ' +
|
|
'coordinates.');
|
|
}
|
|
var x1 = xy[0][0];
|
|
var y1 = xy[0][1];
|
|
var x2 = xy[1][0];
|
|
var y2 = xy[1][1];
|
|
init['coordinates'] = [[[x1, y2], [x1, y1], [x2, y1], [x2, y2]]];
|
|
init['type'] = 'Polygon';
|
|
}
|
|
ee.Geometry.Rectangle.base(this, 'constructor', init);
|
|
};
|
|
goog.inherits(ee.Geometry.Rectangle, ee.Geometry);
|
|
|
|
|
|
/**
|
|
* Constructs a rectangle whose edges are lines of latitude and longitude.
|
|
*
|
|
* The result is a planar WGS84 rectangle.
|
|
*
|
|
* If (east - west) ≥ 360 then the longitude range will be normalized to -180 to
|
|
* +180; otherwise they will be treated as designating points on a circle (e.g.
|
|
* east may be numerically less than west).
|
|
*
|
|
* @param {number} west The westernmost enclosed longitude. Will be adjusted to
|
|
* lie in the range -180° to 180°.
|
|
* @param {number} south The southernmost enclosed latitude. If less than -90°
|
|
* (south pole), will be treated as -90°.
|
|
* @param {number} east The easternmost enclosed longitude.
|
|
* @param {number} north The northernmost enclosed longitude. If greater than
|
|
* +90° (north pole), will be treated as +90°.
|
|
* @constructor
|
|
* @extends {ee.Geometry.Rectangle}
|
|
* @export
|
|
*/
|
|
ee.Geometry.BBox = function(west, south, east, north) {
|
|
if (!(this instanceof ee.Geometry.BBox)) {
|
|
return ee.Geometry.createInstance_(ee.Geometry.BBox, arguments);
|
|
}
|
|
|
|
const coordinates = [west, south, east, north];
|
|
if (ee.Geometry.hasServerValue_(coordinates)) {
|
|
// Some arguments cannot be handled in the client, so make a server call.
|
|
return new ee.ApiFunction('GeometryConstructors.BBox').apply(coordinates);
|
|
}
|
|
// Else proceed with client-side implementation.
|
|
|
|
// Reject NaN and positive (west) or negative (east) infinities before they
|
|
// become bad JSON. The other two infinities are acceptable because we support
|
|
// the general idea of a around-the-globe latitude band. By writing them
|
|
// negated, we also reject NaN.
|
|
if (!(west < Infinity)) {
|
|
throw new Error("Geometry.BBox: west must not be " + west);
|
|
}
|
|
if (!(east > -Infinity)) {
|
|
throw new Error("Geometry.BBox: east must not be " + east);
|
|
}
|
|
// Reject cases which, if we clamped them instead, would move a box whose
|
|
// bounds lie entirely "past" a pole to being at the pole. By writing them
|
|
// negated, we also reject NaN.
|
|
if (!(south <= 90)) {
|
|
throw new Error(
|
|
'Geometry.BBox: south must be at most +90°, but was ' + south + '°');
|
|
}
|
|
if (!(north >= -90)) {
|
|
throw new Error(
|
|
'Geometry.BBox: north must be at least -90°, but was ' + north + '°');
|
|
}
|
|
// On the other hand, allow a box whose extent lies past the pole, but
|
|
// canonicalize it to being exactly the pole.
|
|
south = Math.max(south, -90);
|
|
north = Math.min(north, 90);
|
|
|
|
if (east - west >= 360) {
|
|
// We conclude from seeing more than 360 degrees that the user intends to
|
|
// specify the entire globe (or a band of latitudes, at least).
|
|
// Canonicalize to standard global form.
|
|
west = -180;
|
|
east = 180;
|
|
} else {
|
|
// Not the entire globe. Canonicalize coordinate ranges.
|
|
west = ee.Geometry.canonicalizeLongitude_(west);
|
|
east = ee.Geometry.canonicalizeLongitude_(east);
|
|
if (east < west) {
|
|
east += 360;
|
|
}
|
|
}
|
|
|
|
// Construct GeoJSON.
|
|
var geoJson = {
|
|
'type': 'Polygon',
|
|
'coordinates':
|
|
[[[west, north], [west, south], [east, south], [east, north]]],
|
|
};
|
|
|
|
// Skip Rectangle super constructor because we did our own argument
|
|
// processing; go direct to general Geometry constructor.
|
|
ee.Geometry.call(
|
|
this,
|
|
geoJson,
|
|
/* proj= */ undefined,
|
|
/* geodesic= */ false,
|
|
/* evenOdd= */ true);
|
|
};
|
|
goog.inherits(ee.Geometry.BBox, ee.Geometry.Rectangle);
|
|
|
|
|
|
|
|
/**
|
|
* Canonicalize a longitude so that it is in the range [-180, 180). Helper for
|
|
* ee.Geometry.BBox.
|
|
*
|
|
* @param {number} longitude
|
|
* @return {number}
|
|
* @private
|
|
*/
|
|
ee.Geometry.canonicalizeLongitude_ = function(longitude) {
|
|
longitude = longitude % 360;
|
|
if (longitude > 180) {
|
|
longitude -= 360;
|
|
} else if (longitude < -180) {
|
|
longitude += 360;
|
|
}
|
|
return longitude;
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* Constructs an ee.Geometry describing a LineString.
|
|
*
|
|
* For convenience, varargs may be used when all arguments are numbers. This
|
|
* allows creating geodesic EPSG:4326 LineStrings given an even number of
|
|
* arguments, e.g. ee.Geometry.LineString(aLng, aLat, bLng, bLat, ...).
|
|
*
|
|
* @param {!Array<!Array<number>>|
|
|
* Array<!ee.Geometry>|
|
|
* Array<number>} coords
|
|
* A list of at least two points. May be a list of coordinates in the
|
|
* GeoJSON 'LineString' format, a list of at least two ee.Geometry objects
|
|
* describing a point, or a list of at least four numbers defining the [x,y]
|
|
* coordinates of at least two points.
|
|
* @param {ee.Projection=} opt_proj The projection of this geometry. If
|
|
* unspecified, the default is the projection of the input ee.Geometry, or
|
|
* EPSG:4326 if there are no ee.Geometry inputs.
|
|
* @param {boolean=} opt_geodesic If false, edges are straight in the
|
|
* projection. If true, edges are curved to follow the shortest path on the
|
|
* surface of the Earth. The default is the geodesic state of the inputs, or
|
|
* true if the inputs are numbers.
|
|
* @param {ee.ErrorMargin=} opt_maxError Max error when input geometry must be
|
|
* reprojected to an explicitly requested result projection or geodesic
|
|
* state.
|
|
* @constructor
|
|
* @extends {ee.Geometry}
|
|
* @export
|
|
*/
|
|
ee.Geometry.LineString = function(
|
|
coords, opt_proj, opt_geodesic, opt_maxError) {
|
|
if (!(this instanceof ee.Geometry.LineString)) {
|
|
return ee.Geometry.createInstance_(ee.Geometry.LineString, arguments);
|
|
}
|
|
ee.Geometry.LineString.base(this, 'constructor', ee.Geometry.construct_(
|
|
ee.Geometry.LineString, 'LineString', 2, arguments));
|
|
};
|
|
goog.inherits(ee.Geometry.LineString, ee.Geometry);
|
|
|
|
|
|
|
|
/**
|
|
* Constructs an ee.Geometry describing a LinearRing. If the last point is not
|
|
* equal to the first, a duplicate of the first point will be added at the end.
|
|
*
|
|
* For convenience, varargs may be used when all arguments are numbers. This
|
|
* allows creating geodesic EPSG:4326 LinearRings given an even number of
|
|
* arguments, e.g.
|
|
* ee.Geometry.LinearRing(aLng, aLat, bLng, bLat, ..., aLng, aLat).
|
|
*
|
|
* @param {!Array<!Array<number>>|
|
|
* Array<!ee.Geometry>|
|
|
* Array<number>} coords
|
|
* A list of points in the ring. May be a list of coordinates in the GeoJSON
|
|
* 'LinearRing' format, a list of at least three ee.Geometry objects
|
|
* describing a point, or a list of at least six numbers defining the [x,y]
|
|
* coordinates of at least three points.
|
|
* @param {ee.Projection=} opt_proj The projection of this geometry. If
|
|
* unspecified, the default is the projection of the input ee.Geometry, or
|
|
* EPSG:4326 if there are no ee.Geometry inputs.
|
|
* @param {boolean=} opt_geodesic If false, edges are straight in the
|
|
* projection. If true, edges are curved to follow the shortest path on the
|
|
* surface of the Earth. The default is the geodesic state of the inputs, or
|
|
* true if the inputs are numbers.
|
|
* @param {ee.ErrorMargin=} opt_maxError Max error when input geometry must be
|
|
* reprojected to an explicitly requested result projection or geodesic
|
|
* state.
|
|
* @constructor
|
|
* @extends {ee.Geometry}
|
|
* @export
|
|
*/
|
|
ee.Geometry.LinearRing = function(
|
|
coords, opt_proj, opt_geodesic, opt_maxError) {
|
|
if (!(this instanceof ee.Geometry.LinearRing)) {
|
|
return ee.Geometry.createInstance_(ee.Geometry.LinearRing, arguments);
|
|
}
|
|
ee.Geometry.LinearRing.base(this, 'constructor', ee.Geometry.construct_(
|
|
ee.Geometry.LinearRing, 'LinearRing', 2, arguments));
|
|
};
|
|
goog.inherits(ee.Geometry.LinearRing, ee.Geometry);
|
|
|
|
|
|
|
|
/**
|
|
* Constructs an ee.Geometry describing a MultiLineString.
|
|
*
|
|
* For convenience, varargs may be used when all arguments are numbers. This
|
|
* allows creating geodesic EPSG:4326 MultiLineStrings with a single LineString,
|
|
* given an even number of arguments, e.g.
|
|
* ee.Geometry.MultiLineString(aLng, aLat, bLng, bLat, ...).
|
|
*
|
|
* @param {!Array<!Array<!Array<number>>>|
|
|
* Array<!ee.Geometry>|
|
|
* Array.<number>}
|
|
* coords A list of linestrings. May be a list of coordinates in the GeoJSON
|
|
* 'MultiLineString' format, a list of at least two ee.Geometry objects
|
|
* describing a LineString, or a list of numbers defining a single
|
|
* linestring.
|
|
* @param {ee.Projection=} opt_proj The projection of this geometry. If
|
|
* unspecified, the default is the projection of the input ee.Geometry, or
|
|
* EPSG:4326 if there are no ee.Geometry inputs.
|
|
* @param {boolean=} opt_geodesic If false, edges are straight in the
|
|
* projection. If true, edges are curved to follow the shortest path on the
|
|
* surface of the Earth. The default is the geodesic state of the inputs, or
|
|
* true if the inputs are numbers.
|
|
* @param {ee.ErrorMargin=} opt_maxError Max error when input geometry must be
|
|
* reprojected to an explicitly requested result projection or geodesic
|
|
* state.
|
|
* @constructor
|
|
* @extends {ee.Geometry}
|
|
* @export
|
|
*/
|
|
ee.Geometry.MultiLineString = function(
|
|
coords, opt_proj, opt_geodesic, opt_maxError) {
|
|
if (!(this instanceof ee.Geometry.MultiLineString)) {
|
|
return ee.Geometry.createInstance_(ee.Geometry.MultiLineString, arguments);
|
|
}
|
|
ee.Geometry.MultiLineString.base(this, 'constructor', ee.Geometry.construct_(
|
|
ee.Geometry.MultiLineString, 'MultiLineString', 3, arguments));
|
|
};
|
|
goog.inherits(ee.Geometry.MultiLineString, ee.Geometry);
|
|
|
|
|
|
|
|
/**
|
|
* Constructs an ee.Geometry describing a polygon.
|
|
*
|
|
* For convenience, varargs may be used when all arguments are numbers. This
|
|
* allows creating geodesic EPSG:4326 Polygons with a single LinearRing
|
|
* given an even number of arguments, e.g.
|
|
* ee.Geometry.Polygon(aLng, aLat, bLng, bLat, ..., aLng, aLat).
|
|
*
|
|
* @param {!Array<!Array<!Array<number>>>|
|
|
* Array<!ee.Geometry>|
|
|
* Array<number>}
|
|
* coords A list of rings defining the boundaries of the polygon. May be a
|
|
* list of coordinates in the GeoJSON 'Polygon' format, a list of
|
|
* ee.Geometry objects describing a LinearRing, or a list of numbers
|
|
* defining a single polygon boundary.
|
|
* @param {ee.Projection=} opt_proj The projection of this geometry. The
|
|
* default is the projection of the inputs, where Numbers are assumed to be
|
|
* EPSG:4326.
|
|
* @param {boolean=} opt_geodesic If false, edges are straight in the
|
|
* projection. If true, edges are curved to follow the shortest path on the
|
|
* surface of the Earth. The default is the geodesic state of the inputs, or
|
|
* true if the inputs are numbers.
|
|
* @param {ee.ErrorMargin=} opt_maxError Max error when input geometry must be
|
|
* reprojected to an explicitly requested result projection or geodesic
|
|
* state.
|
|
* @param {boolean=} opt_evenOdd If true, polygon interiors will be determined
|
|
* by the even/odd rule, where a point is inside if it crosses an odd number
|
|
* of edges to reach a point at infinity. Otherwise polygons use the left-
|
|
* inside rule, where interiors are on the left side of the shell's edges
|
|
* when walking the vertices in the given order. If unspecified, defaults to
|
|
* true.
|
|
* @constructor
|
|
* @extends {ee.Geometry}
|
|
* @export
|
|
*/
|
|
ee.Geometry.Polygon = function(
|
|
coords, opt_proj, opt_geodesic, opt_maxError, opt_evenOdd) {
|
|
if (!(this instanceof ee.Geometry.Polygon)) {
|
|
return ee.Geometry.createInstance_(ee.Geometry.Polygon, arguments);
|
|
}
|
|
ee.Geometry.Polygon.base(this, 'constructor', ee.Geometry.construct_(
|
|
ee.Geometry.Polygon, 'Polygon', 3, arguments));
|
|
};
|
|
goog.inherits(ee.Geometry.Polygon, ee.Geometry);
|
|
|
|
|
|
|
|
/**
|
|
* Constructs an ee.Geometry describing a MultiPolygon.
|
|
*
|
|
* For convenience, varargs may be used when all arguments are numbers. This
|
|
* allows creating geodesic EPSG:4326 MultiPolygons with a single Polygon with a
|
|
* single LinearRing given an even number of arguments, e.g.
|
|
* ee.Geometry.MultiPolygon(aLng, aLat, bLng, bLat, ..., aLng, aLat).
|
|
*
|
|
* @param {!Array<!Array<!Array<!Array<number>>>>|
|
|
* Array<ee.Geometry>|
|
|
* Array<number>}
|
|
* coords A list of polygons. May be a list of coordinates in the GeoJSON
|
|
* 'MultiPolygon' format, a list of ee.Geometry objects describing a
|
|
* Polygon, or a list of numbers defining a single polygon boundary.
|
|
* @param {ee.Projection=} opt_proj The projection of this geometry. The
|
|
* default is the projection of the inputs, where Numbers are assumed to be
|
|
* EPSG:4326.
|
|
* @param {boolean=} opt_geodesic If false, edges are straight in the
|
|
* projection. If true, edges are curved to follow the shortest path on the
|
|
* surface of the Earth. The default is the geodesic state of the inputs, or
|
|
* true if the inputs are numbers.
|
|
* @param {ee.ErrorMargin=} opt_maxError Max error when input geometry must be
|
|
* reprojected to an explicitly requested result projection or geodesic
|
|
* state.
|
|
* @param {boolean=} opt_evenOdd If true, polygon interiors will be determined
|
|
* by the even/odd rule, where a point is inside if it crosses an odd number
|
|
* of edges to reach a point at infinity. Otherwise polygons use the left-
|
|
* inside rule, where interiors are on the left side of the shell's edges
|
|
* when walking the vertices in the given order. If unspecified, defaults to
|
|
* true.
|
|
* @constructor
|
|
* @extends {ee.Geometry}
|
|
* @export
|
|
*/
|
|
ee.Geometry.MultiPolygon = function(
|
|
coords, opt_proj, opt_geodesic, opt_maxError, opt_evenOdd) {
|
|
if (!(this instanceof ee.Geometry.MultiPolygon)) {
|
|
return ee.Geometry.createInstance_(ee.Geometry.MultiPolygon, arguments);
|
|
}
|
|
ee.Geometry.MultiPolygon.base(this, 'constructor', ee.Geometry.construct_(
|
|
ee.Geometry.MultiPolygon, 'MultiPolygon', 4, arguments));
|
|
};
|
|
goog.inherits(ee.Geometry.MultiPolygon, ee.Geometry);
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Instance methods. //
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* @param {function(*): *=} opt_encoder A function that can be called to encode
|
|
* the components of an object.
|
|
* @return {*} An encoded representation of the geometry.
|
|
*/
|
|
ee.Geometry.prototype.encode = function(opt_encoder) {
|
|
if (!this.type_) {
|
|
// This is not a concrete Geometry.
|
|
if (!opt_encoder) {
|
|
throw Error('Must specify an encode function when encoding a ' +
|
|
'computed geometry.');
|
|
}
|
|
return ee.ComputedObject.prototype.encode.call(this, opt_encoder);
|
|
}
|
|
|
|
var result = {'type': this.type_};
|
|
if (this.type_ == 'GeometryCollection') {
|
|
result['geometries'] = this.geometries_;
|
|
} else {
|
|
result['coordinates'] = this.coordinates_;
|
|
}
|
|
|
|
if (this.proj_ != null) {
|
|
result['crs'] = {
|
|
'type': 'name',
|
|
'properties': {
|
|
'name': this.proj_
|
|
}
|
|
};
|
|
}
|
|
|
|
if (this.geodesic_ != null) {
|
|
result['geodesic'] = this.geodesic_;
|
|
}
|
|
|
|
if (this.evenOdd_ != null) {
|
|
result['evenOdd'] = this.evenOdd_;
|
|
}
|
|
|
|
return /** @type {ee.data.GeoJSONGeometry} */(result);
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {!ee.data.GeoJSONGeometry} A GeoJSON representation of the geometry.
|
|
* @export
|
|
*/
|
|
ee.Geometry.prototype.toGeoJSON = function() {
|
|
if (this.func) {
|
|
throw new Error('Can\'t convert a computed Geometry to GeoJSON. ' +
|
|
'Use evaluate() instead.');
|
|
}
|
|
return /** @type {!ee.data.GeoJSONGeometry} */ (this.encode());
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {string} A GeoJSON string representation of the geometry.
|
|
* @export
|
|
*/
|
|
ee.Geometry.prototype.toGeoJSONString = function() {
|
|
if (this.func) {
|
|
throw new Error('Can\'t convert a computed Geometry to GeoJSON. ' +
|
|
'Use evaluate() instead.');
|
|
}
|
|
return (new goog.json.Serializer()).serialize(this.toGeoJSON());
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {boolean=} legacy Enables legacy format.
|
|
* @return {string} The serialized representation of this object.
|
|
* @override
|
|
* @export
|
|
*/
|
|
ee.Geometry.prototype.serialize = function(legacy = false) {
|
|
return legacy ? ee.Serializer.toJSON(this) :
|
|
ee.Serializer.toCloudApiJSON(this);
|
|
};
|
|
|
|
|
|
/** @override */
|
|
ee.Geometry.prototype.toString = function() {
|
|
return 'ee.Geometry(' + this.toGeoJSONString() + ')';
|
|
};
|
|
|
|
|
|
/** @override */
|
|
ee.Geometry.prototype.encodeCloudValue = function(opt_encoder) {
|
|
if (!this.type_) {
|
|
// This is not a concrete Geometry.
|
|
if (!opt_encoder) {
|
|
throw Error('Must specify an encode function when encoding a ' +
|
|
'computed geometry.');
|
|
}
|
|
return ee.ComputedObject.prototype.encodeCloudValue.call(this, opt_encoder);
|
|
}
|
|
|
|
const args = {};
|
|
let func = '';
|
|
if (this.type_ === 'GeometryCollection') {
|
|
// Since there is no GeometryCollection constructor API function, we use the
|
|
// MultiGeometry constructor instead. However, the geometries argument
|
|
// expects a list of ee.Geometry objects and not GeoJSON, hence the map.
|
|
args['geometries'] =
|
|
this.geometries_.map(geometry => new ee.Geometry(geometry));
|
|
func = 'GeometryConstructors.MultiGeometry';
|
|
} else {
|
|
args['coordinates'] = this.coordinates_;
|
|
func = 'GeometryConstructors.' + this.type_;
|
|
}
|
|
|
|
if (this.proj_ != null) {
|
|
args['crs'] = (typeof this.proj_ === 'string') ?
|
|
new ee.ApiFunction('Projection').call(this.proj_) :
|
|
this.proj_;
|
|
}
|
|
|
|
const acceptsGeodesicArg =
|
|
this.type_ !== 'Point' && this.type_ !== 'MultiPoint';
|
|
|
|
if (this.geodesic_ != null && acceptsGeodesicArg) {
|
|
args['geodesic'] = this.geodesic_;
|
|
}
|
|
|
|
if (this.evenOdd_ != null) {
|
|
args['evenOdd'] = this.evenOdd_;
|
|
}
|
|
return new ee.ApiFunction(func).apply(args).encodeCloudValue(opt_encoder);
|
|
};
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Implementation. //
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
// TODO(user): Validation should ensure that a polygon has >2 points.
|
|
// Context at cl/158861478.
|
|
/**
|
|
* Checks if a geometry looks valid.
|
|
* @param {Object} geometry The geometry to validate.
|
|
* @return {boolean} whether the geometry looks valid.
|
|
* @private
|
|
*/
|
|
ee.Geometry.isValidGeometry_ = function(geometry) {
|
|
var type = geometry['type'];
|
|
if (type == 'GeometryCollection') {
|
|
var geometries = geometry['geometries'];
|
|
if (!Array.isArray(geometries)) {
|
|
return false;
|
|
}
|
|
for (var i = 0; i < geometries.length; i++) {
|
|
if (!ee.Geometry.isValidGeometry_(geometries[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
} else {
|
|
var coords = geometry['coordinates'];
|
|
var nesting = ee.Geometry.isValidCoordinates_(coords);
|
|
return (type == 'Point' && nesting == 1) ||
|
|
(type == 'MultiPoint' && (nesting == 2 || coords.length == 0)) ||
|
|
(type == 'LineString' && nesting == 2) ||
|
|
(type == 'LinearRing' && nesting == 2) ||
|
|
(type == 'MultiLineString' && (nesting == 3 || coords.length == 0)) ||
|
|
(type == 'Polygon' && nesting == 3) ||
|
|
(type == 'MultiPolygon' && (nesting == 4 || coords.length == 0));
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Validate the coordinates of a geometry.
|
|
* @param {number|!Array.<*>} shape The coordinates to validate.
|
|
* @return {number} The number of nested lists or -1 on error.
|
|
* @private
|
|
*/
|
|
ee.Geometry.isValidCoordinates_ = function(shape) {
|
|
if (!Array.isArray(shape)) {
|
|
return -1;
|
|
}
|
|
if (Array.isArray(shape[0])) {
|
|
var count = ee.Geometry.isValidCoordinates_(shape[0]);
|
|
// If more than 1 ring or polygon, they should have the same nesting.
|
|
for (var i = 1; i < shape.length; i++) {
|
|
if (ee.Geometry.isValidCoordinates_(shape[i]) != count) {
|
|
return -1;
|
|
}
|
|
}
|
|
return count + 1;
|
|
} else {
|
|
// Make sure the coordinates are all numbers.
|
|
for (var i = 0; i < shape.length; i++) {
|
|
if (typeof shape[i] !== 'number') {
|
|
return -1;
|
|
}
|
|
}
|
|
// Test that we have an even number of coordinates.
|
|
return (shape.length % 2 == 0) ? 1 : -1;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Create a line from a list of points.
|
|
* @param {IArrayLike} coordinates The points to convert. Must be a
|
|
* multiple of 2.
|
|
* @return {!Array<!Array<number>>} A list of pairs of points.
|
|
* @private
|
|
*/
|
|
ee.Geometry.coordinatesToLine_ = function(coordinates) {
|
|
if (typeof (coordinates[0]) !== 'number') {
|
|
return /** @type {!Array<!Array<number>>} */ (coordinates);
|
|
}
|
|
if (coordinates.length == 2) {
|
|
return /** @type {!Array<!Array<number>>} */ (coordinates);
|
|
}
|
|
if (coordinates.length % 2 != 0) {
|
|
throw Error('Invalid number of coordinates: ' + coordinates.length);
|
|
}
|
|
var line = [];
|
|
for (var i = 0; i < coordinates.length; i += 2) {
|
|
var pt = [coordinates[i], coordinates[i + 1]];
|
|
line.push(pt);
|
|
}
|
|
return line;
|
|
};
|
|
|
|
|
|
/**
|
|
* Constructs either a GeoJSON object or a ComputedObject for a JS geometry
|
|
* constructor and its arguments.
|
|
* @param {!Function} jsConstructorFn The JS geometry constructor called.
|
|
* @param {string} apiConstructorName The name of the server-side geometry
|
|
* constructor to use.
|
|
* @param {number} depth The nesting depth at which points are found within
|
|
* the coordinates array.
|
|
* @param {!Arguments} originalArgs The arguments to the JS constructor.
|
|
* @return {!Object|!ee.ComputedObject} If the arguments are simple,
|
|
* a GeoJSON object describing the geometry. Otherwise a
|
|
* ComputedObject calling the appropriate server-side constructor.
|
|
* @private
|
|
*/
|
|
ee.Geometry.construct_ = function(
|
|
jsConstructorFn, apiConstructorName, depth, originalArgs) {
|
|
var eeArgs = ee.Geometry.getEeApiArgs_(jsConstructorFn, originalArgs);
|
|
|
|
// Standardize the coordinates and test if they are simple enough for
|
|
// client-side initialization.
|
|
if (ee.Geometry.hasServerValue_(eeArgs['coordinates']) ||
|
|
eeArgs['crs'] != null || eeArgs['maxError'] != null) {
|
|
// Some arguments cannot be handled in the client, so make a server call.
|
|
// Note we don't declare a default evenOdd value, so the server can infer
|
|
// a default based on the projection.
|
|
var serverName = 'GeometryConstructors.' + apiConstructorName;
|
|
return new ee.ApiFunction(serverName).apply(eeArgs);
|
|
} else {
|
|
// Everything can be handled here, so init a simple GeoJSON object.
|
|
var geoJson = eeArgs;
|
|
geoJson['type'] = apiConstructorName;
|
|
geoJson['coordinates'] = ee.Geometry.fixDepth_(
|
|
depth, geoJson['coordinates']);
|
|
var isPolygon =
|
|
goog.array.contains(
|
|
['Polygon', 'Rectangle', 'MultiPolygon'],
|
|
apiConstructorName);
|
|
|
|
if (isPolygon && geoJson['evenOdd'] == null) {
|
|
// Default to evenOdd=true for any kind of polygon.
|
|
geoJson['evenOdd'] = true;
|
|
}
|
|
|
|
if (isPolygon &&
|
|
geoJson['geodesic'] === false &&
|
|
geoJson['evenOdd'] === false) {
|
|
throw new Error('Planar interiors must be even/odd.');
|
|
}
|
|
|
|
return geoJson;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Creates an argument dictionary for a server-side geometry constructor from
|
|
* the arguments to a JS geometry constructor. The arguments the JS constructor
|
|
* can be passed as either a list of coordinates (as var_args), a sequence of
|
|
* parameters, or a dictionary of named parameters.
|
|
* @param {!Function} jsConstructorFn The JS constructor to parse arguments for.
|
|
* @param {!Arguments} originalArgs The arguments to the JS constructor.
|
|
* @return {!Object} The named server-side geometry constructor arguments.
|
|
* @private
|
|
*/
|
|
ee.Geometry.getEeApiArgs_ = function(jsConstructorFn, originalArgs) {
|
|
if (goog.array.every(originalArgs, ee.Types.isNumber)) {
|
|
// All numbers, so convert them to a true array.
|
|
return {'coordinates': goog.array.toArray(originalArgs)};
|
|
} else {
|
|
var args = ee.arguments.extractFromFunction(jsConstructorFn, originalArgs);
|
|
// Convert the argument dictionary to proper GeoJSON. Some of the parameter
|
|
// names intentionally don't map precisely to GeoJSON key names.
|
|
// For example, the server expects different CRS values than GeoJSON.
|
|
args['coordinates'] = args['coords'];
|
|
delete args['coords'];
|
|
args['crs'] = args['proj'];
|
|
delete args['proj'];
|
|
return goog.object.filter(args, x => x != null);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns whether any of the coordinates are computed values or geometries;
|
|
* these types must be resolved by the server (evaluated in the case of computed
|
|
* values, and processed to a single projection and geodesic state in the case
|
|
* of geometries.)
|
|
*
|
|
* @param {!Array} coordinates A nested list of ... of number coordinates.
|
|
* @return {boolean} Whether all coordinates are lists or numbers.
|
|
* @private
|
|
*/
|
|
ee.Geometry.hasServerValue_ = function(coordinates) {
|
|
if (Array.isArray(coordinates)) {
|
|
return goog.array.some(coordinates, ee.Geometry.hasServerValue_);
|
|
} else {
|
|
return coordinates instanceof ee.ComputedObject;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Fixes the depth of the given coordinates, and checks that each element has
|
|
* the expected depth as all other elements at that depth.
|
|
*
|
|
* @private
|
|
* @param {number} depth The desired depth.
|
|
* @param {!Array} coords The coordinates to fix.
|
|
* @return {!Array} The fixed coordinates, with the deepest
|
|
* elements at the requested depth.
|
|
*/
|
|
ee.Geometry.fixDepth_ = function(depth, coords) {
|
|
if (depth < 1 || depth > 4) {
|
|
throw new Error('Unexpected nesting level.');
|
|
}
|
|
|
|
// Handle a list of numbers.
|
|
if (goog.array.every(coords, x => typeof x === 'number')) {
|
|
coords = ee.Geometry.coordinatesToLine_(coords);
|
|
}
|
|
|
|
// Make sure the number of nesting levels is correct.
|
|
var item = coords;
|
|
var count = 0;
|
|
while (Array.isArray(item)) {
|
|
item = item[0];
|
|
count++;
|
|
}
|
|
while (count < depth) {
|
|
coords = [coords];
|
|
count++;
|
|
}
|
|
|
|
if (ee.Geometry.isValidCoordinates_(coords) != depth) {
|
|
throw Error('Invalid geometry');
|
|
}
|
|
|
|
// Empty arrays should not be wrapped.
|
|
item = coords;
|
|
while (Array.isArray(item) && item.length == 1) {
|
|
item = item[0];
|
|
}
|
|
if (Array.isArray(item) && item.length == 0) {
|
|
return [];
|
|
}
|
|
|
|
return /** @type {!Array} */ (coords);
|
|
};
|
|
|
|
|
|
/**
|
|
* Creates an instance of an object given a constructor and a set of arguments.
|
|
* @param {function(this:T, ...?): T} klass The class constructor.
|
|
* @param {Arguments} args The arguments to pass to the constructor.
|
|
* @return {T} The new instance.
|
|
* @template T
|
|
* @private
|
|
*/
|
|
ee.Geometry.createInstance_ = function(klass, args) {
|
|
/** @constructor */
|
|
var f = function() {};
|
|
f.prototype = klass.prototype;
|
|
var instance = new f();
|
|
var result = klass.apply(instance, args);
|
|
return result !== undefined ? result : instance;
|
|
};
|
|
|
|
|
|
/** @override */
|
|
ee.Geometry.prototype.name = function() {
|
|
return 'Geometry';
|
|
};
|