mirror of
https://github.com/google/earthengine-api.git
synced 2025-12-08 19:26:12 +00:00
929 lines
31 KiB
Python
929 lines
31 KiB
Python
#!/usr/bin/env python3
|
|
"""Test for the ee.geometry module.
|
|
|
|
The API objects, like ee.Geometry, are usually tested by comparing the
|
|
serialized object to a golden. To be brief, the tests in this file just validate
|
|
the Geometry function's arguments instead of the serialized object.
|
|
"""
|
|
|
|
import math
|
|
|
|
from absl.testing import parameterized
|
|
|
|
import unittest
|
|
import ee
|
|
from ee import apitestcase
|
|
|
|
|
|
_EPSILON = 1e-5
|
|
|
|
|
|
class GeometryTest(apitestcase.ApiTestCase, parameterized.TestCase):
|
|
|
|
def epsg4326_rectangle(self, west, south, east, north):
|
|
# If we call ee.Geometry.Rectangle with geodesic=False we would get a
|
|
# computed call.
|
|
return ee.Geometry({
|
|
'coordinates': [
|
|
[[west, north], [west, south], [east, south], [east, north]]
|
|
],
|
|
'type': 'Polygon',
|
|
'geodesic': False,
|
|
})
|
|
|
|
@parameterized.named_parameters(
|
|
('_simple', (-10, -20, 10, 20), (-10, -20, 10, 20)),
|
|
(
|
|
'_latitude_widerThanPolesClamped_south',
|
|
(-10, -90, 10, 73),
|
|
(-10, -1000, 10, 73),
|
|
),
|
|
(
|
|
'_latitude_widerThanPolesClamped_north',
|
|
(-10, -34, 10, 90),
|
|
(-10, -34, 10, 10000),
|
|
),
|
|
('_latitude_zeroSpan', (-10, 20, 10, 20), (-10, 20, 10, 20)),
|
|
(
|
|
'_longitude_crossingMeridianWithOppositeSigns',
|
|
(170, -20, 190, 20),
|
|
(170, -20, -170, 20),
|
|
),
|
|
(
|
|
'_longitude_crossingMeridianWithNegativeSigns',
|
|
(170, -20, 190, 20),
|
|
(-190, -20, -170, 20),
|
|
),
|
|
(
|
|
'_longitude_crossingMeridianWithPositiveSigns',
|
|
(170, -20, 190, 20),
|
|
(170, -20, 190, 20),
|
|
),
|
|
('_longitude_exactlyGlobal', (-180, -20, 180, 20), (-180, -20, 180, 20)),
|
|
(
|
|
'_longitude_excessOfGlobalIsClamped',
|
|
(-180, -20, 180, 20),
|
|
(-180 - _EPSILON, -20, 180 + _EPSILON, 20),
|
|
),
|
|
('_longitude_zeroSpan', (10, -20, 10, 20), (10, -20, 10, 20)),
|
|
(
|
|
'_infinity_validDirection_isClamped_west',
|
|
(-180, -20, 180, 20),
|
|
(-math.inf, -20, 10, 20),
|
|
),
|
|
(
|
|
'_infinity_validDirection_isClamped_east',
|
|
(-180, -20, 180, 20),
|
|
(-10, -20, math.inf, 20),
|
|
),
|
|
(
|
|
'_infinity_validDirection_isClamped_south',
|
|
(-10, -90, 10, 20),
|
|
(-10, -math.inf, 10, 20),
|
|
),
|
|
(
|
|
'_infinity_validDirection_isClamped_north',
|
|
(-10, -20, 10, 90),
|
|
(-10, -20, 10, math.inf),
|
|
),
|
|
)
|
|
def testBBox(self, expected, coords):
|
|
self.assertEqual(
|
|
self.epsg4326_rectangle(*expected),
|
|
ee.Geometry.BBox(*coords),
|
|
)
|
|
|
|
def testBBox_computedArgs(self):
|
|
ten = ee.Number(5).add(5)
|
|
box = ee.Geometry.BBox(-10, -20, ten, 20)
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.BBox')
|
|
|
|
self.assertIsInstance(box, ee.Geometry)
|
|
self.assertEqual(func, box.func)
|
|
expected_args = {
|
|
'west': ee.Number(-10),
|
|
'south': ee.Number(-20),
|
|
'east': ten,
|
|
'north': ee.Number(20),
|
|
}
|
|
self.assertEqual(expected_args, box.args)
|
|
|
|
@parameterized.named_parameters(
|
|
# Reject cases which, if we clamped them instead, would move a box whose
|
|
# bounds lie past a pole to being a point at the pole.
|
|
(
|
|
'_latitude_notBeyondPoles_north',
|
|
(-10, -100, 10, -95),
|
|
r'Geometry\.BBox: north must be at least -90°, but was -95°',
|
|
),
|
|
(
|
|
'_latitude_notBeyondPoles_south',
|
|
(-10, 95, 10, 100),
|
|
r'Geometry\.BBox: south must be at most \+90°, but was 95°',
|
|
),
|
|
# NaN is rejected.
|
|
(
|
|
'_NaN_isRejected_west',
|
|
(math.nan, -20, 10, 20),
|
|
r'Geometry\.BBox: west must not be nan',
|
|
),
|
|
(
|
|
'_NaN_isRejected_south',
|
|
(-10, math.nan, 10, 20),
|
|
r'Geometry\.BBox: south must be at most \+90°, but was nan°',
|
|
),
|
|
(
|
|
'_NaN_isRejected_east',
|
|
(-10, -20, math.nan, 20),
|
|
r'Geometry\.BBox: east must not be nan',
|
|
),
|
|
(
|
|
'_NaN_isRejected_north',
|
|
(-10, -20, 10, math.nan),
|
|
r'Geometry.BBox: north must be at least -90°, but was nan°',
|
|
),
|
|
# Infinities in the wrong direction are rejected.
|
|
(
|
|
'_infinity_invalidDirection_isRejected_west',
|
|
(math.inf, -20, 10, 20),
|
|
r'Geometry\.BBox: west must not be inf',
|
|
),
|
|
(
|
|
'_infinity_invalidDirection_isRejected_south',
|
|
(-10, math.inf, 10, 20),
|
|
r'Geometry\.BBox: south must be at most \+90°, but was inf°',
|
|
),
|
|
(
|
|
'_infinity_invalidDirection_isRejected_east',
|
|
(-10, -20, -math.inf, 20),
|
|
r'Geometry\.BBox: east must not be -inf',
|
|
),
|
|
(
|
|
'_infinity_invalidDirection_isRejected_north',
|
|
(-10, -20, 10, -math.inf),
|
|
r'Geometry\.BBox: north must be at least -90°, but was -inf°',
|
|
),
|
|
)
|
|
def testBBoxInvalid(self, coords, message):
|
|
with self.assertRaisesRegex(ee.EEException, message):
|
|
ee.Geometry.BBox(*coords)
|
|
|
|
def testLineString(self):
|
|
coords = [1, 2, 3, 4]
|
|
proj = 'EPSG:4326'
|
|
max_error = 1000
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.LineString')
|
|
|
|
geometry = ee.Geometry.LineString(coords, proj, True, max_error)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{
|
|
'coordinates': ee.List(coords),
|
|
'crs': ee.Projection(proj),
|
|
'geodesic': True,
|
|
'maxError': ee.ErrorMargin(max_error),
|
|
},
|
|
geometry.args,
|
|
)
|
|
|
|
def testLineString_kwargs(self):
|
|
coords = [1, 2, 3, 4]
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.LineString')
|
|
|
|
geometry = ee.Geometry.LineString(coords, geodesic=True)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{'coordinates': ee.List(coords), 'geodesic': True},
|
|
geometry.args,
|
|
)
|
|
|
|
def testLineString_computedArgs(self):
|
|
"""Verifies that a LineString with computed inputs can be constructed."""
|
|
p1 = ee.Geometry.Point([1, 2])
|
|
p2 = ee.Geometry.Point([3, 4])
|
|
line = ee.Geometry.LineString([p1, p2])
|
|
|
|
self.assertIsInstance(line, ee.Geometry)
|
|
self.assertEqual(
|
|
ee.ApiFunction.lookup('GeometryConstructors.LineString'), line.func
|
|
)
|
|
self.assertEqual({'coordinates': ee.List([p1, p2])}, line.args)
|
|
|
|
@parameterized.named_parameters(
|
|
('_coords', (1, 2, 3, 4, 5), 'Invalid number of coordinates: 5'),
|
|
('_coordsList', [[1, 2, 3, 4, 5]], 'Invalid number of coordinates: 5'),
|
|
('_geometry', [[[1, 2], [3, 4], 5]], 'Invalid geometry'),
|
|
('_nesting', [[[[1, 2], [3, 4]]]], 'Invalid geometry'),
|
|
)
|
|
def testLineStringInvalid(self, coords, message):
|
|
with self.assertRaisesRegex(ee.EEException, message):
|
|
ee.Geometry.LineString(*coords)
|
|
|
|
def testLinearRing(self):
|
|
coords = [1, 2, 3, 4]
|
|
proj = 'EPSG:4326'
|
|
geodesic = True
|
|
max_error = 1000
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.LinearRing')
|
|
|
|
geometry = ee.Geometry.LinearRing(coords, proj, geodesic, max_error)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{
|
|
'coordinates': ee.List(coords),
|
|
'crs': ee.Projection(proj),
|
|
'geodesic': geodesic,
|
|
'maxError': ee.ErrorMargin(max_error),
|
|
},
|
|
geometry.args,
|
|
)
|
|
|
|
def testLinearRing_kwargs(self):
|
|
coords = [1, 2, 3, 4]
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.LinearRing')
|
|
geodesic = True
|
|
|
|
geometry = ee.Geometry.LinearRing(coords, geodesic=geodesic)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{'coordinates': ee.List(coords), 'geodesic': geodesic},
|
|
geometry.args,
|
|
)
|
|
|
|
def testLinearRing_computedArgs(self):
|
|
"""Verifies that a LinearRing with computed inputs can be constructed."""
|
|
p1 = ee.Geometry.Point([1, 2])
|
|
p2 = ee.Geometry.Point([3, 4])
|
|
ring = ee.Geometry.LinearRing([p1, p2])
|
|
|
|
self.assertIsInstance(ring, ee.Geometry)
|
|
self.assertEqual(
|
|
ee.ApiFunction.lookup('GeometryConstructors.LinearRing'), ring.func
|
|
)
|
|
self.assertEqual({'coordinates': ee.List([p1, p2])}, ring.args)
|
|
|
|
@parameterized.named_parameters(
|
|
('_coords', (1, 2, 3, 4, 5), 'Invalid number of coordinates: 5'),
|
|
('_coordsList', [[1, 2, 3, 4, 5]], 'Invalid number of coordinates: 5'),
|
|
('_geometry', [[[1, 2], [3, 4], 5]], 'Invalid geometry'),
|
|
('_nesting', [[[[1, 2], [3, 4]]]], 'Invalid geometry'),
|
|
)
|
|
def testLinearRingInvalid(self, coords, message):
|
|
with self.assertRaisesRegex(ee.EEException, message):
|
|
ee.Geometry.LinearRing(*coords)
|
|
|
|
def testMultiLineString(self):
|
|
coords = [1, 2, 3, 4]
|
|
proj = 'EPSG:4326'
|
|
geodesic = True
|
|
max_error = 1000
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.MultiLineString')
|
|
|
|
geometry = ee.Geometry.MultiLineString(coords, proj, geodesic, max_error)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{
|
|
'coordinates': ee.List(coords),
|
|
'crs': ee.Projection(proj),
|
|
'geodesic': geodesic,
|
|
'maxError': ee.ErrorMargin(max_error),
|
|
},
|
|
geometry.args,
|
|
)
|
|
|
|
def testMultiLineString_kwargs(self):
|
|
coords = [1, 2, 3, 4]
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.MultiLineString')
|
|
geodesic = True
|
|
|
|
geometry = ee.Geometry.MultiLineString(coords, geodesic=geodesic)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{'coordinates': ee.List(coords), 'geodesic': geodesic},
|
|
geometry.args,
|
|
)
|
|
|
|
def testMultiLineString_computedArgs(self):
|
|
"""Verifies that a MultiLineString with computed inputs can be constructed."""
|
|
p1 = ee.Geometry.Point([1, 2])
|
|
p2 = ee.Geometry.Point([3, 4])
|
|
line = ee.Geometry.MultiLineString([p1, p2])
|
|
|
|
self.assertIsInstance(line, ee.Geometry)
|
|
self.assertEqual(
|
|
ee.ApiFunction.lookup('GeometryConstructors.MultiLineString'), line.func
|
|
)
|
|
self.assertEqual({'coordinates': ee.List([p1, p2])}, line.args)
|
|
|
|
@parameterized.named_parameters(
|
|
('_coords', (1, 2, 3, 4, 5), 'Invalid number of coordinates: 5'),
|
|
('_coordsList', [[1, 2, 3, 4, 5]], 'Invalid number of coordinates: 5'),
|
|
('_geometry', [[[1, 2], [3, 4], 5]], 'Invalid geometry'),
|
|
('_nesting', [[[[[1, 2], [3, 4]]]]], 'Invalid geometry'),
|
|
)
|
|
def testMultiLineStringInvalid(self, coords, message):
|
|
"""Verifies MultiLineString constructor behavior with invalid arguments."""
|
|
with self.assertRaisesRegex(ee.EEException, message):
|
|
ee.Geometry.MultiLineString(*coords)
|
|
|
|
def testMultiPoint(self):
|
|
coords = [1, 2, 3, 4]
|
|
proj = 'EPSG:4326'
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.MultiPoint')
|
|
|
|
geometry = ee.Geometry.MultiPoint(coords, proj)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{
|
|
'coordinates': ee.List(coords),
|
|
'crs': ee.Projection(proj),
|
|
},
|
|
geometry.args,
|
|
)
|
|
|
|
def testMultiPoint_kwargs(self):
|
|
coords = [1, 2, 3, 4]
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.MultiPoint')
|
|
proj = 'EPSG:4326'
|
|
|
|
geometry = ee.Geometry.MultiPoint(coords, proj=proj)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{'coordinates': ee.List(coords), 'crs': ee.Projection(proj)},
|
|
geometry.args,
|
|
)
|
|
|
|
def testMultiPoint_computedArgs(self):
|
|
"""Verifies that a MultiPoint with computed inputs can be constructed."""
|
|
p1 = ee.Geometry.Point([1, 2])
|
|
p2 = ee.Geometry.Point([3, 4])
|
|
point = ee.Geometry.MultiPoint([p1, p2])
|
|
|
|
self.assertIsInstance(point, ee.Geometry)
|
|
self.assertEqual(
|
|
ee.ApiFunction.lookup('GeometryConstructors.MultiPoint'), point.func
|
|
)
|
|
self.assertEqual({'coordinates': ee.List([p1, p2])}, point.args)
|
|
|
|
@parameterized.named_parameters(
|
|
('_coords', (1, 2, 3, 4, 5), 'Invalid number of coordinates: 5'),
|
|
('_coordsList', [[1, 2, 3, 4, 5]], 'Invalid number of coordinates: 5'),
|
|
('_geometry', [[[1, 2], [3, 4], 5]], 'Invalid geometry'),
|
|
('_nesting', [[[[1, 2], [3, 4]]]], 'Invalid geometry'),
|
|
)
|
|
def testMultiPointInvalid(self, coords, message):
|
|
"""Verifies MultiPoint constructor behavior with invalid arguments."""
|
|
with self.assertRaisesRegex(ee.EEException, message):
|
|
ee.Geometry.MultiPoint(*coords)
|
|
|
|
def testMultiPolygon(self):
|
|
coords = [1, 2, 3, 4]
|
|
proj = 'EPSG:4326'
|
|
geodesic = True
|
|
max_error = 1000
|
|
even_odd = True
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.MultiPolygon')
|
|
|
|
geometry = ee.Geometry.MultiPolygon(
|
|
coords, proj, geodesic, max_error, even_odd
|
|
)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{
|
|
'coordinates': ee.List(coords),
|
|
'crs': ee.Projection(proj),
|
|
'geodesic': geodesic,
|
|
'maxError': ee.ErrorMargin(max_error),
|
|
'evenOdd': even_odd,
|
|
},
|
|
geometry.args,
|
|
)
|
|
|
|
def testMultiPolygon_kwargs(self):
|
|
coords = [1, 2, 3, 4]
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.MultiPolygon')
|
|
geodesic = True
|
|
|
|
geometry = ee.Geometry.MultiPolygon(coords, geodesic=geodesic)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{'coordinates': ee.List(coords), 'geodesic': geodesic},
|
|
geometry.args,
|
|
)
|
|
|
|
def testMultiPolygon_computedArgs(self):
|
|
"""Verifies that a MultiPolygon with computed inputs can be constructed."""
|
|
p1 = ee.Geometry.Point([1, 2])
|
|
p2 = ee.Geometry.Point([3, 4])
|
|
line = ee.Geometry.MultiPolygon([p1, p2])
|
|
|
|
self.assertIsInstance(line, ee.Geometry)
|
|
self.assertEqual(
|
|
ee.ApiFunction.lookup('GeometryConstructors.MultiPolygon'), line.func
|
|
)
|
|
self.assertEqual({'coordinates': ee.List([p1, p2])}, line.args)
|
|
|
|
@parameterized.named_parameters(
|
|
('_coords', (1, 2, 3, 4, 5), 'Invalid number of coordinates: 5'),
|
|
('_coordsList', [[1, 2, 3, 4, 5]], 'Invalid number of coordinates: 5'),
|
|
('_geometry', [[[[[[1, 2], [3, 4], [5, 6]]]]]], 'Invalid geometry'),
|
|
('_nesting', [[[[[1, 2], [3, 4]], [1, 2]]]], 'Invalid geometry'),
|
|
)
|
|
def testMultiPolygonInvalid(self, coords, message):
|
|
"""Verifies MultiPolygon constructor behavior with invalid arguments."""
|
|
with self.assertRaisesRegex(ee.EEException, message):
|
|
ee.Geometry.MultiPolygon(*coords)
|
|
|
|
def testPoint(self):
|
|
coords = [1, 2, 3, 4]
|
|
proj = 'EPSG:4326'
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.Point')
|
|
|
|
geometry = ee.Geometry.Point(coords, proj)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{
|
|
'coordinates': ee.List(coords),
|
|
'crs': ee.Projection(proj),
|
|
},
|
|
geometry.args,
|
|
)
|
|
|
|
def testPoint_kwargs(self):
|
|
coords = [1, 2, 3, 4]
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.Point')
|
|
proj = 'EPSG:4326'
|
|
|
|
geometry = ee.Geometry.Point(coords, proj=proj)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{'coordinates': ee.List(coords), 'crs': ee.Projection(proj)},
|
|
geometry.args,
|
|
)
|
|
|
|
def testPoint_oldKeywordArgs(self):
|
|
"""Verifies that Points still allow keyword lon/lat args."""
|
|
self.assertEqual(ee.Geometry.Point(1, 2), ee.Geometry.Point(lon=1, lat=2))
|
|
self.assertEqual(ee.Geometry.Point(1, 2), ee.Geometry.Point(1, lat=2))
|
|
|
|
def testPoint_computedArgs(self):
|
|
"""Verifies that a Point with computed inputs can be constructed."""
|
|
v1 = ee.Number(1)
|
|
v2 = ee.Number(2)
|
|
point = ee.Geometry.Point([v1, v2])
|
|
|
|
self.assertIsInstance(point, ee.Geometry)
|
|
self.assertEqual(
|
|
ee.ApiFunction.lookup('GeometryConstructors.Point'), point.func
|
|
)
|
|
self.assertEqual({'coordinates': ee.List([v1, v2])}, point.args)
|
|
|
|
@parameterized.named_parameters(
|
|
('_geometry', [['-78.204948', '40.966539']], 'Invalid geometry'),
|
|
)
|
|
def testPointInvalid(self, coords, message):
|
|
"""Verifies Point constructor behavior with invalid arguments."""
|
|
with self.assertRaisesRegex(ee.EEException, message):
|
|
ee.Geometry.Point(*coords)
|
|
|
|
def testPolygon(self):
|
|
coords = [1, 2, 3, 4]
|
|
proj = 'EPSG:4326'
|
|
geodesic = True
|
|
max_error = 1000
|
|
even_odd = True
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.Polygon')
|
|
|
|
geometry = ee.Geometry.Polygon(coords, proj, geodesic, max_error, even_odd)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{
|
|
'coordinates': ee.List(coords),
|
|
'crs': ee.Projection(proj),
|
|
'geodesic': geodesic,
|
|
'maxError': ee.ErrorMargin(max_error),
|
|
'evenOdd': even_odd,
|
|
},
|
|
geometry.args,
|
|
)
|
|
|
|
def testPolygon_kwargs(self):
|
|
coords = [1, 2, 3, 4]
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.Polygon')
|
|
geodesic = True
|
|
|
|
geometry = ee.Geometry.Polygon(coords, geodesic=geodesic)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{'coordinates': ee.List(coords), 'geodesic': geodesic},
|
|
geometry.args,
|
|
)
|
|
|
|
def testPolygon_computedArgs(self):
|
|
"""Verifies that a Polygon with computed inputs can be constructed."""
|
|
p1 = ee.Geometry.Point([1, 2])
|
|
p2 = ee.Geometry.Point([3, 4])
|
|
line = ee.Geometry.Polygon([p1, p2])
|
|
|
|
self.assertIsInstance(line, ee.Geometry)
|
|
self.assertEqual(
|
|
ee.ApiFunction.lookup('GeometryConstructors.Polygon'), line.func
|
|
)
|
|
self.assertEqual({'coordinates': ee.List([p1, p2])}, line.args)
|
|
|
|
def testPolygon_evenOdd(self):
|
|
poly1 = ee.Geometry.Polygon([0, 0, 0, 5, 5, 0])
|
|
self.assertTrue(poly1.toGeoJSON()['evenOdd'])
|
|
|
|
# TODO: Use kwargs instead of positional args for evenOdd.
|
|
poly2 = ee.Geometry.Polygon([0, 0, 0, 5, 5, 0], None, None, None, False)
|
|
self.assertFalse(poly2.toGeoJSON()['evenOdd'])
|
|
|
|
@parameterized.named_parameters(
|
|
('_coords', (1, 2, 3, 4, 5), 'Invalid number of coordinates: 5'),
|
|
('_coordsList', [[1, 2, 3, 4, 5]], 'Invalid number of coordinates: 5'),
|
|
('_geometry', [[[1, 2], [3, 4], 5]], 'Invalid geometry'),
|
|
('_tooMuchNesting', [[[[[1, 2], [3, 4], [5, 6]]]]], 'Invalid geometry'),
|
|
('_badNesting', [[[[1, 2], [3, 4]], [1, 2]]], 'Invalid geometry'),
|
|
)
|
|
def testPolygonInvalid(self, coords, message):
|
|
"""Verifies Polygon constructor behavior with invalid arguments."""
|
|
with self.assertRaisesRegex(ee.EEException, message):
|
|
ee.Geometry.Polygon(*coords)
|
|
|
|
def testRectangle(self):
|
|
coords = [1, 2, 3, 4]
|
|
proj = 'EPSG:4326'
|
|
geodesic = True
|
|
even_odd = True
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.Rectangle')
|
|
|
|
geometry = ee.Geometry.Rectangle(coords, proj, geodesic, even_odd)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{
|
|
'coordinates': ee.List(coords),
|
|
'crs': ee.Projection(proj),
|
|
'geodesic': geodesic,
|
|
'evenOdd': even_odd,
|
|
},
|
|
geometry.args,
|
|
)
|
|
|
|
def testRectangle_kwargs(self):
|
|
coords = [1, 2, 3, 4]
|
|
func = ee.ApiFunction.lookup('GeometryConstructors.Rectangle')
|
|
geodesic = True
|
|
|
|
geometry = ee.Geometry.Rectangle(coords, geodesic=geodesic)
|
|
self.assertEqual(func, geometry.func)
|
|
self.assertEqual(
|
|
{'coordinates': ee.List(coords), 'geodesic': geodesic},
|
|
geometry.args,
|
|
)
|
|
|
|
def testRectangle_oldKeywordArgs(self):
|
|
"""Verifies that Rectangles still allow keyword xlo/ylo/xhi/yhi args."""
|
|
self.assertEqual(
|
|
ee.Geometry.Rectangle(1, 2, 3, 4),
|
|
ee.Geometry.Rectangle(xlo=1, ylo=2, xhi=3, yhi=4),
|
|
)
|
|
self.assertEqual(
|
|
ee.Geometry.Rectangle(1, 2, 3, 4),
|
|
ee.Geometry.Rectangle(1, 2, xhi=3, yhi=4),
|
|
)
|
|
|
|
def testRectangle_computedArgs(self):
|
|
"""Verifies that a Rectangle with computed inputs can be constructed."""
|
|
p1 = ee.Geometry.Point([1, 2])
|
|
p2 = ee.Geometry.Point([3, 4])
|
|
line = ee.Geometry.Rectangle([p1, p2])
|
|
|
|
self.assertIsInstance(line, ee.Geometry)
|
|
self.assertEqual(
|
|
ee.ApiFunction.lookup('GeometryConstructors.Rectangle'), line.func
|
|
)
|
|
self.assertEqual({'coordinates': ee.List([p1, p2])}, line.args)
|
|
|
|
@parameterized.named_parameters(
|
|
('_coords', (1, 2, 3, 4, 5), 'Invalid number of coordinates: 5'),
|
|
('_coordsList', [[1, 2, 3, 4, 5]], 'Invalid number of coordinates: 5'),
|
|
('_geometry', [[[1, 2], [3, 4], 5]], 'Invalid geometry'),
|
|
('_tooMuchNesting', [[[[1, 2], [3, 4]]]], 'Invalid geometry'),
|
|
)
|
|
def testRectangleInvalid(self, coords, message):
|
|
"""Verifies Rectangle constructor behavior with invalid arguments."""
|
|
with self.assertRaisesRegex(ee.EEException, message):
|
|
ee.Geometry.Rectangle(*coords)
|
|
|
|
def testValid_GeometryCollection(self):
|
|
"""Verifies GeometryCollection constructor behavior with valid arguments."""
|
|
geometry = ee.Geometry({
|
|
'type': 'GeometryCollection',
|
|
'geometries': [
|
|
{
|
|
'type': 'Polygon',
|
|
'coordinates': [[[-1, -1], [0, 1], [1, -1]]],
|
|
'geodesic': True,
|
|
'evenOdd': True,
|
|
},
|
|
{'type': 'Point', 'coordinates': [0, 0]},
|
|
{
|
|
'type': 'GeometryCollection',
|
|
'geometries': [
|
|
{'type': 'Point', 'coordinates': [1, 2]},
|
|
{'type': 'Point', 'coordinates': [2, 1]},
|
|
],
|
|
},
|
|
],
|
|
'coordinates': [],
|
|
})
|
|
self.assertIsInstance(geometry, ee.Geometry)
|
|
|
|
def testArrayConstructors(self):
|
|
"""Verifies that constructors that take arrays fix nesting."""
|
|
get_coordinates_count = lambda g: len(g.toGeoJSON()['coordinates'])
|
|
|
|
point = ee.Geometry.Point([1, 2])
|
|
self.assertEqual(2, get_coordinates_count(point))
|
|
|
|
multipoint = ee.Geometry.MultiPoint([[1, 2], [3, 4], [5, 6]])
|
|
self.assertEqual(3, get_coordinates_count(multipoint))
|
|
|
|
line = ee.Geometry.LineString([[1, 2], [3, 4], [5, 6]])
|
|
self.assertEqual(3, get_coordinates_count(line))
|
|
|
|
ring = ee.Geometry.LinearRing([[1, 2], [3, 4], [5, 6]])
|
|
self.assertEqual(3, get_coordinates_count(ring))
|
|
|
|
multiline = ee.Geometry.MultiLineString(
|
|
[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
|
|
)
|
|
self.assertEqual(2, get_coordinates_count(multiline))
|
|
|
|
polygon = ee.Geometry.Polygon([[[1, 2], [3, 4], [5, 6]]])
|
|
self.assertEqual(1, get_coordinates_count(polygon))
|
|
|
|
mpolygon = ee.Geometry.MultiPolygon(
|
|
[[[[1, 2], [3, 4], [5, 6]]], [[[1, 2], [3, 4], [5, 6]]]]
|
|
)
|
|
self.assertEqual(2, get_coordinates_count(mpolygon))
|
|
|
|
def testGeodesicFlag(self):
|
|
"""Verifies that JSON parsing and generation preserves the geodesic flag."""
|
|
geodesic = ee.Geometry({
|
|
'type': 'LineString',
|
|
'coordinates': [[1, 2], [3, 4]],
|
|
'geodesic': True,
|
|
})
|
|
projected = ee.Geometry({
|
|
'type': 'LineString',
|
|
'coordinates': [[1, 2], [3, 4]],
|
|
'geodesic': False,
|
|
})
|
|
self.assertTrue(geodesic.toGeoJSON()['geodesic'])
|
|
self.assertFalse(projected.toGeoJSON()['geodesic'])
|
|
|
|
def testConstructor(self):
|
|
"""Check the behavior of the Geometry constructor.
|
|
|
|
There are 5 options:
|
|
1) A geoJSON object.
|
|
2) A not-computed geometry.
|
|
3) A not-computed geometry with overrides.
|
|
4) A computed geometry.
|
|
5) something to cast to geometry.
|
|
"""
|
|
line = ee.Geometry.LineString(1, 2, 3, 4)
|
|
|
|
# GeoJSON.
|
|
from_json = ee.Geometry(line.toGeoJSON())
|
|
self.assertIsNone(from_json.func)
|
|
self.assertEqual(from_json._type, 'LineString')
|
|
self.assertEqual(from_json._coordinates, [[1, 2], [3, 4]])
|
|
|
|
# GeoJSON with a CRS specified.
|
|
json_with_crs = line.toGeoJSON()
|
|
json_with_crs['crs'] = {
|
|
'type': 'name',
|
|
'properties': {'name': 'SR-ORG:6974'},
|
|
}
|
|
from_json_with_crs = ee.Geometry(json_with_crs)
|
|
self.assertIsNone(from_json_with_crs.func)
|
|
self.assertEqual(from_json_with_crs._type, 'LineString')
|
|
self.assertEqual(from_json_with_crs._proj, 'SR-ORG:6974')
|
|
|
|
# A not-computed geometry.
|
|
self.assertEqual(ee.Geometry(line), line)
|
|
|
|
# A not-computed geometry with an override.
|
|
with_override = ee.Geometry(line, 'SR-ORG:6974')
|
|
self.assertEqual(with_override._proj, 'SR-ORG:6974')
|
|
|
|
# A computed geometry.
|
|
self.assertEqual(ee.Geometry(line.bounds()), line.bounds())
|
|
|
|
# Something to cast to a geometry.
|
|
computed = ee.ComputedObject(ee.Function(), {'a': 1})
|
|
geom = ee.Geometry(computed)
|
|
self.assertEqual(computed.func, geom.func)
|
|
self.assertEqual(computed.args, geom.args)
|
|
|
|
def testComputedGeometries(self):
|
|
"""Verifies the computed object behavior of the Geometry constructor."""
|
|
line = ee.Geometry.LineString(1, 2, 3, 4)
|
|
bounds = line.bounds()
|
|
|
|
self.assertIsInstance(bounds, ee.Geometry)
|
|
self.assertEqual(ee.ApiFunction.lookup('Geometry.bounds'), bounds.func)
|
|
self.assertEqual(line, bounds.args['geometry'])
|
|
self.assertTrue(hasattr(bounds, 'bounds'))
|
|
|
|
def testComputedCoordinate(self):
|
|
"""Verifies that a computed coordinate produces a computed geometry."""
|
|
coords = [1, ee.Number(1).add(1)]
|
|
p = ee.Geometry.Point(coords)
|
|
|
|
self.assertIsInstance(p, ee.Geometry)
|
|
self.assertEqual(
|
|
ee.ApiFunction.lookup('GeometryConstructors.Point'), p.func
|
|
)
|
|
self.assertEqual({'coordinates': ee.List(coords)}, p.args)
|
|
|
|
def testComputedList(self):
|
|
"""Verifies that a computed coordinate produces a computed geometry."""
|
|
lst = ee.List([1, 2, 3, 4]).slice(0, 2)
|
|
p = ee.Geometry.Point(lst)
|
|
|
|
self.assertIsInstance(p, ee.Geometry)
|
|
self.assertEqual(
|
|
ee.ApiFunction.lookup('GeometryConstructors.Point'), p.func
|
|
)
|
|
self.assertEqual({'coordinates': lst}, p.args)
|
|
|
|
def testComputedProjection(self):
|
|
"""Verifies that a geometry with a projection can be constructed."""
|
|
p = ee.Geometry.Point([1, 2], 'epsg:4326')
|
|
|
|
self.assertIsInstance(p, ee.Geometry)
|
|
self.assertEqual(
|
|
ee.ApiFunction.lookup('GeometryConstructors.Point'), p.func
|
|
)
|
|
expected_args = {
|
|
'coordinates': ee.List([1, 2]),
|
|
'crs': ee.ApiFunction.lookup('Projection').call('epsg:4326'),
|
|
}
|
|
self.assertEqual(expected_args, p.args)
|
|
|
|
def testInvalidCrs(self):
|
|
geo_json = ee.Geometry.LineString(1, 2, 3, 4).toGeoJSON()
|
|
geo_json['crs'] = {'something': 'invalid-crs'}
|
|
with self.assertRaisesRegex(ee.EEException, 'Invalid CRS declaration'):
|
|
ee.Geometry(geo_json)
|
|
|
|
def testInternals(self):
|
|
"""Test eq(), ne() and hash()."""
|
|
a = ee.Geometry.Point(1, 2)
|
|
b = ee.Geometry.Point(2, 1)
|
|
c = ee.Geometry.Point(1, 2)
|
|
|
|
self.assertEqual(a, a)
|
|
self.assertNotEqual(a, b)
|
|
self.assertEqual(a, c)
|
|
self.assertNotEqual(b, c)
|
|
self.assertNotEqual(hash(a), hash(b))
|
|
|
|
def testCoordinatesToLine(self):
|
|
# pylint: disable=protected-access
|
|
self.assertEqual([], ee.Geometry._coordinatesToLine([]))
|
|
self.assertEqual([1, 2], ee.Geometry._coordinatesToLine([1, 2]))
|
|
# TODO: schwehr - Improve _coordinatesToLine typing.
|
|
self.assertEqual(
|
|
[[1, 2]],
|
|
ee.Geometry._coordinatesToLine([[1, 2]]), # pytype: disable=wrong-arg-types
|
|
)
|
|
self.assertEqual(
|
|
[[1, 2], [3, 4]], ee.Geometry._coordinatesToLine([1, 2, 3, 4])
|
|
)
|
|
self.assertEqual(
|
|
[[1, 2], [3, 4], [5, 6]],
|
|
ee.Geometry._coordinatesToLine([1, 2, 3, 4, 5, 6]),
|
|
)
|
|
with self.assertRaisesRegex(
|
|
ee.EEException, 'Invalid number of coordinates: 5'
|
|
):
|
|
ee.Geometry._coordinatesToLine([1, 2, 3, 4, 5])
|
|
# pylint: enable=protected-access
|
|
|
|
def testInit_optParams(self):
|
|
result = ee.Geometry(
|
|
geo_json={'type': 'Polygon', 'coordinates': [[[-2, 1]]]},
|
|
opt_proj='abc',
|
|
opt_geodesic=True,
|
|
opt_evenOdd=True,
|
|
).serialize()
|
|
self.assertIn('"crs": {"constantValue": "abc"}', result)
|
|
self.assertIn('"geodesic": {"constantValue": true}', result)
|
|
self.assertIn('"evenOdd": {"constantValue": true}', result)
|
|
|
|
@parameterized.named_parameters(
|
|
(
|
|
'BBox',
|
|
lambda: ee.Geometry.BBox(1, 2, 3, 4),
|
|
dict(
|
|
coordinates=[[[1, 4], [1, 2], [3, 2], [3, 4]]],
|
|
geodesic=False,
|
|
type='Polygon',
|
|
),
|
|
),
|
|
(
|
|
'LineString',
|
|
lambda: ee.Geometry.LineString(1, 2, 3, 4),
|
|
dict(coordinates=[[1, 2], [3, 4]], type='LineString'),
|
|
),
|
|
(
|
|
'LinearRing',
|
|
lambda: ee.Geometry.LinearRing(1, 2, 3, 4),
|
|
dict(coordinates=[[1, 2], [3, 4]], type='LinearRing'),
|
|
),
|
|
(
|
|
'MultiLineString',
|
|
lambda: ee.Geometry.MultiLineString(1, 2, 3, 4),
|
|
dict(coordinates=[[[1, 2], [3, 4]]], type='MultiLineString'),
|
|
),
|
|
(
|
|
'MultiLineString_Empty',
|
|
lambda: ee.Geometry.MultiLineString(), # pylint: disable=unnecessary-lambda
|
|
dict(coordinates=[], type='MultiLineString'),
|
|
),
|
|
(
|
|
'MultiPoint',
|
|
lambda: ee.Geometry.MultiPoint(1, 2, 3, 4),
|
|
dict(coordinates=[[1, 2], [3, 4]], type='MultiPoint'),
|
|
),
|
|
(
|
|
'MultiPoint_Empty',
|
|
lambda: ee.Geometry.MultiPoint(), # pylint: disable=unnecessary-lambda
|
|
dict(coordinates=[], type='MultiPoint'),
|
|
),
|
|
(
|
|
'MultiPolygon',
|
|
lambda: ee.Geometry.MultiPolygon(1, 2, 3, 4),
|
|
dict(
|
|
coordinates=[[[[1, 2], [3, 4]]]],
|
|
evenOdd=True,
|
|
type='MultiPolygon',
|
|
),
|
|
),
|
|
(
|
|
'MultiPolygon_Empty',
|
|
lambda: ee.Geometry.MultiPolygon(), # pylint: disable=unnecessary-lambda
|
|
dict(coordinates=[], evenOdd=True, type='MultiPolygon'),
|
|
),
|
|
(
|
|
'Point',
|
|
lambda: ee.Geometry.Point(coords=[1, 2]),
|
|
dict(coordinates=[1, 2], type='Point'),
|
|
),
|
|
(
|
|
'Polygon',
|
|
lambda: ee.Geometry.Polygon(1, 2, 3, 4),
|
|
dict(
|
|
coordinates=[[[1, 2], [3, 4]]],
|
|
evenOdd=True,
|
|
type='Polygon',
|
|
),
|
|
),
|
|
(
|
|
'Rectangle',
|
|
lambda: ee.Geometry.Rectangle(1, 2, 3, 4),
|
|
dict(
|
|
coordinates=[[[1, 4], [1, 2], [3, 2], [3, 4]]],
|
|
evenOdd=True,
|
|
type='Polygon',
|
|
),
|
|
),
|
|
)
|
|
def testToGeoJSON(self, geometry_func, expected_geojson):
|
|
self.assertEqual(expected_geojson, geometry_func().toGeoJSON())
|
|
|
|
@parameterized.named_parameters(
|
|
# BBox doesn't have any other arguments.
|
|
('LineString', lambda: ee.Geometry.LineString([], 'EPSG:4326')),
|
|
('LinearRing', lambda: ee.Geometry.LinearRing([], 'EPSG:4326')),
|
|
('MultiLineString', lambda: ee.Geometry.MultiLineString([], 'EPSG:4326')),
|
|
('MultiPoint', lambda: ee.Geometry.MultiPoint([], 'EPSG:4326')),
|
|
('MultiPolygon', lambda: ee.Geometry.MultiPolygon([], 'EPSG:4326')),
|
|
('Point', lambda: ee.Geometry.Point([], 'EPSG:4326')),
|
|
('Polygon', lambda: ee.Geometry.Polygon([], 'EPSG:4326')),
|
|
('Rectangle', lambda: ee.Geometry.Rectangle([], 'EPSG:4326')),
|
|
)
|
|
def testToGeoJSON_FailsOnComputed(self, geometry_func):
|
|
message = 'Cannot convert a computed geometry to GeoJSON'
|
|
with self.assertRaisesRegex(ee.EEException, message):
|
|
geometry_func().toGeoJSON()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|