def test_without_shapely(self): geom = { 'coordinates': (((-93.52300099792355, 41.241436141055345), (-93.7138666, 40.703737), (-94.37053769704536, 40.83098709945576), (-94.2036617, 41.3717716), (-93.52300099792355, 41.241436141055345)), ), 'type': 'Polygon' } bounds_wgs84 = (-94.37053769704536, 40.703737, -93.52300099792355, 41.3717716) ctx = geocontext.AOI(geom, bounds=bounds_wgs84, resolution=40, crs="EPSG:3857") self.assertIs(ctx.geometry, geom) self.assertEqual(ctx.__geo_interface__, geom) self.assertEqual(ctx.bounds, bounds_wgs84) raster_params = ctx.raster_params self.assertEqual(raster_params["cutline"], geom) with self.assertRaises(NotImplementedError): geocontext.AOI(geom)
def test_validate_bounds_values_for_bounds_crs__latlon(self): # invalid latlon bounds with pytest.raises(ValueError, match="Bounds must be in lat-lon coordinates"): geocontext.AOI( bounds_crs="EPSG:4326", bounds=[500000, 2000000, 501000, 2001000] ) # valid latlon bounds, no error should raise geocontext.AOI(bounds_crs="EPSG:4326", bounds=[12, -41, 14, -40])
def test_default_ctx(self): # test doesn't fail with nothing ctx = MockScene({}, {}).default_ctx() self.assertEqual(ctx, geocontext.AOI(bounds_crs=None, align_pixels=False)) # no geotrans ctx = MockScene({}, { 'crs': 'EPSG:4326' }).default_ctx() self.assertEqual(ctx, geocontext.AOI(crs="EPSG:4326", bounds_crs=None, align_pixels=False)) # north-up geotrans - resolution with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") # otherwise, the duplicate warning is suppressed the second time ctx = MockScene({}, { 'crs': 'EPSG:4326', # origin: (0, 0), pixel size: 2, rotation: 0 degrees 'geotrans': [0, 2, 0, 0, 0, -2], }).default_ctx() self.assertEqual(len(w), 0) self.assertEqual(ctx.resolution, 2) # non-north-up geotrans - resolution with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") ctx = MockScene({}, { 'crs': 'EPSG:4326', # origin: (0, 0), pixel size: 2, rotation: 30 degrees 'geotrans': (0.0, 1.7320508075688774, -1, 0.0, 1, 1.7320508075688774) }).default_ctx() warning = w[0] self.assertIn("The GeoContext will *not* return this Scene's original data", str(warning.message)) self.assertEqual(ctx.resolution, 2) # north-up geotrans - bounds ctx = MockScene({}, { 'crs': 'EPSG:4326', # origin: (10, 20), pixel size: 2, rotation: 0 degrees 'geotrans': [10, 2, 0, 20, 0, -2], 'raster_size': [1, 2] }).default_ctx() self.assertEqual(ctx.bounds, (10, 16, 12, 20)) # non-north-up geotrans - bounds with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") ctx = MockScene({}, { 'crs': 'EPSG:4326', # origin: (0, 0), pixel size: 2, rotation: 45 degrees 'geotrans': (0.0, np.sqrt(2), np.sqrt(2), 0.0, np.sqrt(2), -np.sqrt(2)), 'raster_size': [1, 1] }).default_ctx() warning = w[0] self.assertIn("The GeoContext will *not* return this Scene's original data", str(warning.message)) diagonal = np.sqrt(2**2 + 2**2) self.assertEqual(ctx.bounds, (0, -diagonal / 2, diagonal, diagonal / 2))
def test_assign_update_bounds_crs(self): ctx = geocontext.AOI(bounds_crs="EPSG:32615") assert ctx.bounds_crs == "EPSG:32615" geom = shapely.geometry.Point(-20, 30).buffer(1).envelope ctx_no_update_bounds = ctx.assign(geometry=geom) assert ctx_no_update_bounds.bounds_crs == "EPSG:32615" ctx_update_bounds = ctx.assign(geometry=geom, bounds="update") assert ctx_update_bounds.bounds_crs == "EPSG:4326" with pytest.raises(ValueError, match="Can't compute bounds from a geometry while also explicitly setting"): ctx = geocontext.AOI(geometry=geom, resolution=40, bounds_crs="EPSG:32615")
def test_validate_bounds_values_for_bounds_crs__non_latlon(self): # valid latlon bounds, should warn with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") ctx = geocontext.AOI(bounds_crs="EPSG:32615", bounds=(12, -41, 14, -40)) assert ctx.bounds_crs == "EPSG:32615" assert ctx.bounds == (12, -41, 14, -40) warning = w[0] assert "You might have the wrong `bounds_crs` set." in str(warning.message) # not latlon bounds, no error should raise geocontext.AOI( bounds_crs="EPSG:32615", bounds=[500000, 2000000, 501000, 2001000] )
def test_validate_reasonable_resolution(self): # different CRSs --- no error ctx = geocontext.AOI( crs="EPSG:32615", bounds_crs="EPSG:4326", bounds=[0, 0, 1.5, 1.5], resolution=15, ) assert ctx.crs == "EPSG:32615" assert ctx.bounds_crs == "EPSG:4326" assert ctx.bounds == (0, 0, 1.5, 1.5) assert ctx.resolution == 15 # same CRSs, bounds < resolution --- no error geocontext.AOI( crs="EPSG:32615", bounds_crs="EPSG:32615", bounds=[200000, 5000000, 200100, 5000300], resolution=15, ) # same CRSs, width < resolution --- error with pytest.raises(ValueError, match="less than one pixel wide"): geocontext.AOI( crs="EPSG:32615", bounds_crs="EPSG:32615", bounds=[200000, 5000000, 200001, 5000300], resolution=15, ) # same CRSs, height < resolution --- error with pytest.raises(ValueError, match="less than one pixel tall"): geocontext.AOI( crs="EPSG:32615", bounds_crs="EPSG:32615", bounds=[200000, 5000000, 200100, 5000001], resolution=15, ) # same CRSs, width < resolution, CRS is lat-lon --- error including "decimal degrees" with pytest.raises( ValueError, match="resolution must be given in decimal degrees" ): geocontext.AOI( crs="EPSG:4326", bounds_crs="EPSG:4326", bounds=[10, 10, 11, 11], resolution=15, )
def test_validate_reasonable_resolution(self): # different CRSs --- no error ctx = geocontext.AOI( crs="EPSG:32615", bounds_crs="EPSG:4326", bounds=[0, 0, 1.5, 1.5], resolution=15, ) self.assertEqual(ctx.crs, "EPSG:32615") self.assertEqual(ctx.bounds_crs, "EPSG:4326") self.assertEqual(ctx.bounds, (0, 0, 1.5, 1.5)) self.assertEqual(ctx.resolution, 15) # same CRSs, bounds < resolution --- no error geocontext.AOI( crs="EPSG:32615", bounds_crs="EPSG:32615", bounds=[200000, 5000000, 200100, 5000300], resolution=15, ) # same CRSs, width < resolution --- error with self.assertRaisesRegexp(ValueError, "less than one pixel wide"): geocontext.AOI( crs="EPSG:32615", bounds_crs="EPSG:32615", bounds=[200000, 5000000, 200001, 5000300], resolution=15, ) # same CRSs, height < resolution --- error with self.assertRaisesRegexp(ValueError, "less than one pixel tall"): geocontext.AOI( crs="EPSG:32615", bounds_crs="EPSG:32615", bounds=[200000, 5000000, 200100, 5000001], resolution=15, ) # same CRSs, width < resolution, CRS is lat-lon --- error including "decimal degrees" with self.assertRaisesRegexp( ValueError, "resolution must be given in decimal degrees"): geocontext.AOI( crs="EPSG:4326", bounds_crs="EPSG:4326", bounds=[10, 10, 11, 11], resolution=15, )
def test_assign_update_bounds(self): geom = shapely.geometry.Point(-90, 30).buffer(1).envelope ctx = geocontext.AOI(geometry=geom, resolution=40) geom_overlaps = shapely.affinity.translate(geom, xoff=1) self.assertTrue(geom.intersects(geom_overlaps)) ctx_overlap = ctx.assign(geometry=geom_overlaps) self.assertEqual(ctx_overlap.bounds, ctx.bounds) ctx_updated = ctx.assign(geometry=geom_overlaps, bounds="update") self.assertEqual(ctx_updated.bounds, geom_overlaps.bounds) geom_doesnt_overlap = shapely.affinity.translate(geom, xoff=3) with self.assertRaisesRegexp(ValueError, "Geometry and bounds do not intersect"): ctx.assign(geometry=geom_doesnt_overlap) ctx_doesnt_overlap_updated = ctx.assign(geometry=geom_doesnt_overlap, bounds="update") self.assertEqual(ctx_doesnt_overlap_updated.bounds, geom_doesnt_overlap.bounds) with self.assertRaisesRegexp( ValueError, "A geometry must be given with which to update the bounds"): ctx.assign(bounds="update")
def test_geojson_featurecollection(self): feature = { 'type': 'Feature', 'geometry': { 'coordinates': (((-93.52300099792355, 41.241436141055345), (-93.7138666, 40.703737), (-94.37053769704536, 40.83098709945576), (-94.2036617, 41.3717716), (-93.52300099792355, 41.241436141055345)), ), 'type': 'Polygon' } } collection = { 'type': 'FeatureCollection', 'features': [feature, feature, feature], } bounds_wgs84 = (-94.37053769704536, 40.703737, -93.52300099792355, 41.3717716) ctx = geocontext.AOI(collection, bounds=bounds_wgs84, resolution=40, crs="EPSG:3857") self.assertEqual(ctx.__geo_interface__['type'], 'GeometryCollection') self.assertEqual(ctx.__geo_interface__['geometries'][0], feature['geometry'])
def test_assign_update_bounds(self): geom = shapely.geometry.Point(-90, 30).buffer(1).envelope ctx = geocontext.AOI(geometry=geom, resolution=40) geom_overlaps = shapely.affinity.translate(geom, xoff=1) assert geom.intersects(geom_overlaps) ctx_overlap = ctx.assign(geometry=geom_overlaps) assert ctx_overlap.bounds == ctx.bounds ctx_updated = ctx.assign(geometry=geom_overlaps, bounds="update") assert ctx_updated.bounds == geom_overlaps.bounds geom_doesnt_overlap = shapely.affinity.translate(geom, xoff=3) with pytest.raises(ValueError, match="Geometry and bounds do not intersect"): ctx.assign(geometry=geom_doesnt_overlap) ctx_doesnt_overlap_updated = ctx.assign(geometry=geom_doesnt_overlap, bounds="update") assert ctx_doesnt_overlap_updated.bounds == geom_doesnt_overlap.bounds with pytest.raises( ValueError, match="A geometry must be given with which to update the bounds" ): ctx.assign(bounds="update")
def test_raster_params(self): geom = { "coordinates": ( ( (-93.52300099792355, 41.241436141055345), (-93.7138666, 40.703737), (-94.37053769704536, 40.83098709945576), (-94.2036617, 41.3717716), (-93.52300099792355, 41.241436141055345), ), ), "type": "Polygon", } bounds_wgs84 = (-94.37053769704536, 40.703737, -93.52300099792355, 41.3717716) resolution = 40 crs = "EPSG:32615" align_pixels = False ctx = geocontext.AOI(geom, resolution, crs, align_pixels) raster_params = ctx.raster_params expected = { "cutline": geom, "resolution": resolution, "srs": crs, "bounds_srs": "EPSG:4326", "align_pixels": align_pixels, "bounds": bounds_wgs84, "dimensions": None, } assert raster_params == expected
def test_init(self): feature = { "type": "Feature", "geometry": { "coordinates": ( ( (-93.52300099792355, 41.241436141055345), (-93.7138666, 40.703737), (-94.37053769704536, 40.83098709945576), (-94.2036617, 41.3717716), (-93.52300099792355, 41.241436141055345), ), ), "type": "Polygon", }, } collection = { "type": "FeatureCollection", "features": [feature, feature, feature], } bounds_wgs84 = (-94.37053769704536, 40.703737, -93.52300099792355, 41.3717716) resolution = 40 ctx = geocontext.AOI(collection, resolution=resolution) assert ctx.resolution == resolution assert tuple(round(e, 5) for e in ctx.bounds) == tuple( round(e, 5) for e in bounds_wgs84 ) assert ctx.bounds_crs == "EPSG:4326" assert isinstance(ctx.geometry, shapely.geometry.GeometryCollection) assert ctx.__geo_interface__["type"] == "GeometryCollection" assert ctx.__geo_interface__["geometries"][0] == feature["geometry"]
def test_assign(self): geom = { "coordinates": [ [ [-93.52300099792355, 41.241436141055345], [-93.7138666, 40.703737], [-94.37053769704536, 40.83098709945576], [-94.2036617, 41.3717716], [-93.52300099792355, 41.241436141055345], ] ], "type": "Polygon", } ctx = geocontext.AOI(resolution=40) ctx2 = ctx.assign(geometry=geom) assert ( ctx2.geometry.__geo_interface__ == shapely.geometry.shape(geom).__geo_interface__ ) assert ctx2.resolution == 40 assert ctx2.align_pixels assert ctx2.shape is None ctx3 = ctx2.assign(geometry=None) assert ctx3.geometry is None
def test_init(self): feature = { 'type': 'Feature', 'geometry': { 'coordinates': (( (-93.52300099792355, 41.241436141055345), (-93.7138666, 40.703737), (-94.37053769704536, 40.83098709945576), (-94.2036617, 41.3717716), (-93.52300099792355, 41.241436141055345)), ), 'type': 'Polygon' } } collection = { 'type': 'FeatureCollection', 'features': [feature, feature, feature], } bounds_wgs84 = (-94.37053769704536, 40.703737, -93.52300099792355, 41.3717716) resolution = 40 ctx = geocontext.AOI(collection, resolution=resolution) self.assertEqual(ctx.resolution, resolution) self.assertEqual(ctx.bounds, bounds_wgs84) self.assertIsInstance(ctx.geometry, shapely.geometry.GeometryCollection) self.assertEqual(ctx.__geo_interface__['type'], 'GeometryCollection') self.assertEqual(ctx.__geo_interface__['geometries'][0], feature['geometry'])
def test_help_with_too_big_resolution(self): aoi = geocontext.AOI(resolution=10, bounds=(-100, 35, -99.9, 35.1), crs="EPSG:4326") with self.assertRaises(ValueError): aoi.raster_params aoi = aoi.assign(crs="EPSG:3857") aoi.raster_params
def test_search_AOI(self): aoi = geocontext.AOI(self.geom, resolution=5) sc, ctx = search(aoi, products="landsat:LC08:PRE:TOAR", limit=4) self.assertGreater(len(sc), 0) self.assertLessEqual(len(sc), 4) # test client only has 2 scenes available self.assertEqual(ctx.resolution, 5) self.assertEqual(ctx.crs, "EPSG:32615")
def test_search_AOI(self): aoi = geocontext.AOI(self.geom, resolution=5) sc, ctx = search(aoi, products="landsat:LC08:PRE:TOAR", limit=4) assert len(sc) > 0 assert len(sc) <= 4 # test client only has 2 scenes available assert ctx.resolution == 5 assert ctx.crs == "EPSG:32615"
def test_assign_update_bounds_crs(self): ctx = geocontext.AOI(bounds_crs="EPSG:32615") self.assertEqual(ctx.bounds_crs, "EPSG:32615") geom = shapely.geometry.Point(-20, 30).buffer(1).envelope ctx_no_update_bounds = ctx.assign(geometry=geom) self.assertEqual(ctx_no_update_bounds.bounds_crs, "EPSG:32615") ctx_update_bounds = ctx.assign(geometry=geom, bounds="update") self.assertEqual(ctx_update_bounds.bounds_crs, "EPSG:4326") with self.assertRaisesRegexp( ValueError, "Can't compute bounds from a geometry while also explicitly setting" ): ctx = geocontext.AOI(geometry=geom, resolution=40, bounds_crs="EPSG:32615")
def test_search_AOI_with_shape(self): aoi = geocontext.AOI(self.geom, shape=(100, 100)) sc, ctx = search(aoi, products="landsat:LC08:PRE:TOAR", limit=4) assert len(sc) > 0 assert len(sc) <= 4 # test client only has 2 scenes available assert ctx.resolution is None assert ctx.shape == aoi.shape assert ctx.crs == "EPSG:32615"
def test_filter_coverage(self): polygon = shapely.geometry.Point(0.0, 0.0).buffer(1) ctx = geocontext.AOI(geometry=polygon) scenes = SceneCollection([ Scene(dict(id='foo', geometry=polygon, properties={}), {}), Scene(dict(id='bar', geometry=polygon.buffer(-0.1), properties={}), {}), ]) self.assertEqual(len(scenes.filter_coverage(ctx)), 1)
def test_download(self, mock_geotiff): scene = MockScene({}, { "id": "foo:bar", "bands": { "nir": {"dtype": "UInt16"}, "yellow": {"dtype": "UInt16"}, } }) ctx = geocontext.AOI(bounds=[30, 40, 50, 60], resolution=2, crs="EPSG:4326") scene.download("nir yellow", ctx) mock_geotiff.assert_called_once()
def setUp(self): properties = [{ "id": "foo:bar" + str(i), "bands": { "nir": {"dtype": "UInt16"}, "yellow": {"dtype": "UInt16"}, } } for i in range(3)] self.scenes = SceneCollection([MockScene({}, p) for p in properties]) self.ctx = geocontext.AOI(bounds=[30, 40, 50, 60], resolution=2, crs="EPSG:4326")
def test_coverage(self): scene_geometry = shapely.geometry.mapping( shapely.geometry.Point(0.0, 0.0).buffer(1)) scene = Scene(dict(id='foo', geometry=scene_geometry, properties={}), {}) # same geometry ctx = geocontext.AOI(scene_geometry) self.assertEqual(scene.coverage(ctx), 1.0) # ctx is larger ctx = geocontext.AOI( shapely.geometry.mapping( shapely.geometry.Point(0.0, 0.0).buffer(2))) self.assertEqual(scene.coverage(ctx), 0.25) # ctx is smaller ctx = geocontext.AOI( shapely.geometry.mapping( shapely.geometry.Point(0.0, 0.0).buffer(0.5))) self.assertEqual(scene.coverage(ctx), 1.0)
def test_validate_bound_geom_intersection(self): # bounds don't intersect with pytest.raises(ValueError, match="Geometry and bounds do not intersect"): geocontext.AOI( geometry=shapely.geometry.box(0, 0, 1, 1), bounds=[5, 5, 6, 6], bounds_crs="EPSG:4326", ) # bounds do intersect; no error should raise geocontext.AOI( geometry=shapely.geometry.box(0, 0, 1, 1), bounds=[0.5, 0.5, 3, 4], bounds_crs="EPSG:4326", ) # bounds_crs is not WGS84, so we can't check if bounds and geometry intersect or not---no error should raise geocontext.AOI( geometry=shapely.geometry.box(0, 0, 1, 1), bounds_crs="EPSG:32615", bounds=[500000, 2000000, 501000, 2001000], )
def test_assign_update_bounds_no_shapely_failes(self): geom = { 'coordinates': (((-93.52300099792355, 41.241436141055345), (-93.7138666, 40.703737), (-94.37053769704536, 40.83098709945576), (-94.2036617, 41.3717716), (-93.52300099792355, 41.241436141055345)), ), 'type': 'Polygon' } bounds_wgs84 = (-94.37053769704536, 40.703737, -93.52300099792355, 41.3717716) ctx = geocontext.AOI(geom, bounds=bounds_wgs84) with self.assertRaises(NotImplementedError): ctx.assign(geometry=geom, bounds="update")
def aoi_factory(): return geocontext.AOI( { 'coordinates': [[ [-93.52300099792355, 41.241436141055345], [-93.7138666, 40.703737], [-94.37053769704536, 40.83098709945576], [-94.2036617, 41.3717716], [-93.52300099792355, 41.241436141055345], ]], 'type': 'Polygon' }, crs="EPSG:3857", resolution=10 )
def test_geointerface_without_shapely(self): geom = { 'coordinates': (((-93.52300099792355, 41.241436141055345), (-93.7138666, 40.703737), (-94.37053769704536, 40.83098709945576), (-94.2036617, 41.3717716), (-93.52300099792355, 41.241436141055345)), ), 'type': 'Polygon' } geointerfaceable = mock.Mock() geointerfaceable.__geo_interface__ = geom bounds_wgs84 = (-94.37053769704536, 40.703737, -93.52300099792355, 41.3717716) ctx = geocontext.AOI(geometry=geointerfaceable, bounds=bounds_wgs84) self.assertEqual(ctx.geometry, geom)
def test_init_base_params(self): geom = { 'coordinates': [ [[-93.52300099792355, 41.241436141055345], [-93.7138666, 40.703737], [-94.37053769704536, 40.83098709945576], [-94.2036617, 41.3717716], [-93.52300099792355, 41.241436141055345]], ], 'type': 'Polygon' } resolution = 40 ctx = geocontext.AOI(geom, resolution) self.assertEqual(ctx.resolution, resolution) self.assertEqual( ctx.bounds, (-94.37053769704536, 40.703737, -93.52300099792355, 41.3717716)) self.assertIsInstance(ctx.geometry, shapely.geometry.Polygon)
def test_assign(self): geom = { 'coordinates': [[ [-93.52300099792355, 41.241436141055345], [-93.7138666, 40.703737], [-94.37053769704536, 40.83098709945576], [-94.2036617, 41.3717716], [-93.52300099792355, 41.241436141055345]], ], 'type': 'Polygon' } ctx = geocontext.AOI(resolution=40) ctx2 = ctx.assign(geometry=geom) self.assertEqual(ctx2.geometry, shapely.geometry.shape(geom)) self.assertEqual(ctx2.resolution, 40) self.assertEqual(ctx2.align_pixels, True) self.assertEqual(ctx2.shape, None) ctx3 = ctx2.assign(geometry=None) self.assertEqual(ctx3.geometry, None)
def test_validate_resolution_shape(self): with pytest.raises(ValueError): geocontext.AOI(resolution=40, shape=(120, 280))