def test_raster_closer_than_resolution_to_roi(): raster_close_to_roi = make_test_raster( 1, [1], height=2255, width=6500, affine=Affine(1.000056241624503, -0.0001677700491717716, 251130.52371896777, -0.00011325628093143738, -1.0000703876618153, 2703061.4308057753), crs=CRS.from_epsg(32613), ) raster_intersecting_roi = make_test_raster( 1, [1], height=3515, width=6497, affine=Affine(1.000063460933417, -2.935588943753421e-05, 250953.40276071787, -3.26265458078499e-05, -1.000053742629815, 2703428.138070052), crs=CRS.from_epsg(32613), ) roi = GeoVector.from_bounds(251726, 2696110, 256422, 2700806, CRS.from_epsg(32613)) merge_all( [raster_close_to_roi, raster_intersecting_roi], roi=roi, dest_resolution=(1, 1), merge_strategy=MergeStrategy.INTERSECTION, )
def test_merge_all_different_crs(crop, recwarn): roi = GeoVector( Polygon.from_bounds(-6321833, -3092272, -6319273, -3089712), WEB_MERCATOR_CRS) affine = Affine.translation(-57, -26) * Affine.scale(0.00083, -0.00083) expected_resolution = 10 expected_crs = WEB_MERCATOR_CRS # from memory raster_0 = make_test_raster(1, [1], height=1200, width=1200, affine=affine, crs=WGS84_CRS) result_0 = merge_all([raster_0], roi=roi, dest_resolution=expected_resolution, crs=expected_crs, crop=crop) assert (result_0.resolution() == expected_resolution) assert (result_0.crs == expected_crs) assert (result_0.footprint().envelope.almost_equals(roi.envelope, decimal=3)) # from file path = "/vsimem/raster_for_test.tif" result_0.save(path) raster_1 = GeoRaster2.open(path) result_1 = merge_all([raster_1], roi=roi, dest_resolution=expected_resolution, crs=expected_crs, crop=crop) assert (result_1.resolution() == expected_resolution) assert (result_1.crs == expected_crs) assert (result_1.footprint().envelope.almost_equals(roi.envelope, decimal=3)) assert (result_0 == result_1) # preserve the original resolution if dest_resolution is not provided raster_2 = make_test_raster(1, [1], height=1200, width=1200, affine=affine, crs=WGS84_CRS) result_2 = merge_all([raster_2], roi=roi, crs=expected_crs, crop=crop) assert pytest.approx(result_2.resolution()) == 97.9691
def test_merge_multi_band_single_raster_returns_itself_for_all_strategies(): for ms in MergeStrategy: raster = black_and_white_raster([1, 2, 3]) raster2 = merge_all([raster], roi=raster.footprint(), merge_strategy=ms) assert (raster2 == raster)
def test_merge_does_not_uncover_masked_pixels(): # See https://github.com/satellogic/telluric/issues/65 affine = Affine.translation(0, 2) * Affine.scale(1, -1) rs_a = GeoRaster2( image=np.ma.masked_array( [[[100, 89], [100, 89]], [[110, 99], [110, 99]]], [[[False, True], [False, True]], [[False, True], [False, True]]], dtype=np.uint8), affine=affine, crs=WGS84_CRS, band_names=['red', 'green'], ) rs_b = GeoRaster2( image=np.array([[[0, 210], [0, 210]]], dtype=np.uint8), affine=affine, crs=WGS84_CRS, band_names=['green'], ) expected_image = np.ma.masked_array( [[[100, 89], [100, 89]], [[110, 99], [110, 99]]], [[[False, True], [False, True]], [[False, True], [False, True]]], dtype=np.uint8) result = merge_all([rs_a, rs_b], rs_a.footprint()).limit_to_bands(['red', 'green']) assert_array_equal(np.ma.filled(result.image, 0), np.ma.filled(expected_image, 0)) assert_array_equal(result.image.mask, expected_image.mask)
def test_merge_all_on_non_overlapping_rasters_returns_first_raster(): affine1 = Affine.translation(10, 12) * Affine.scale(1, -1) affine2 = Affine.translation(100, 120) * Affine.scale(1, -1) raster1 = make_test_raster(value=1, band_names=['blue'], affine=affine1, height=30, width=40) raster2 = make_test_raster(value=2, band_names=['blue'], affine=affine2, height=30, width=40) merged = merge_all([raster1, raster2], raster1.footprint()) assert merged == raster1
def test_get_tile_merge_tiles(tile): raster1_path = './tests/data/raster/overlap1.tif' raster2_path = './tests/data/raster/overlap2.tif' raster1 = GeoRaster2.open(raster1_path) raster2 = GeoRaster2.open(raster2_path) features = [ GeoFeature(raster1.footprint().reproject(new_crs=WGS84_CRS), {'raster_url': raster1_path, 'created': datetime.now()}), GeoFeature(raster2.footprint().reproject(new_crs=WGS84_CRS), {'raster_url': raster2_path, 'created': datetime.now()}), ] fc = FeatureCollection(features) bounds = mercantile.xy_bounds(*tile) eroi = GeoVector.from_bounds(xmin=bounds.left, xmax=bounds.right, ymin=bounds.bottom, ymax=bounds.top, crs=WEB_MERCATOR_CRS) expected_tile = merge_all([raster1.get_tile(*tile), raster2.get_tile(*tile)], roi=eroi) merged = fc.get_tile(*tile, sort_by='created') if merged is not None: assert merged == expected_tile else: assert expected_tile.image.mask.all() assert (expected_tile.image.data == 0).all()
def test_merge_single_band_single_raster_returns_itself_for_all_strategies(): for ms in MergeStrategy: raster = make_test_raster(88, [1]) raster2 = merge_all([raster], roi=raster.footprint(), merge_strategy=ms) assert (raster2 == raster)
def _merge_rasters(self, rasters, z): # the import is here to eliminate recursive import from telluric.georaster import merge_all actual_roi = rasters[0].footprint() merge_params = { 'dest_resolution': MERCATOR_RESOLUTION_MAPPING[z], 'ul_corner': (actual_roi.left, actual_roi.top), 'shape': (256, 256), 'crs': WEB_MERCATOR_CRS, } return merge_all(rasters, **merge_params)
def test_rasters_covering_different_overlapping_areas_on_y(): affine_a = Affine.translation(1, 2) * Affine.scale(1, -1) raster_a = make_test_raster(1, [1], height=20, width=20, affine=affine_a) affine_b = Affine.translation(1, -9) * Affine.scale(1, -1) raster_b = make_test_raster(2, [1], height=20, width=20, affine=affine_b) roi = GeoVector.from_bounds(xmin=1, ymin=-29, xmax=21, ymax=2, crs=constants.WEB_MERCATOR_CRS) rasters = [raster_a, raster_b] merged = merge_all(rasters, roi) assert(merged.affine.almost_equals(affine_a)) assert(not merged.image.mask.all()) assert((merged.image.data[0, 0:20, 0:20] == 1).all()) assert((merged.image.data[0, 21:30, 0:20] == 2).all())
def get_tile(self, x, y, z, sort_by=None, desc=False, bands=None): """Generate mercator tile from rasters in FeatureCollection. Parameters ---------- x: int x coordinate of tile y: int y coordinate of tile z: int zoom level sort_by: str attribute in feature to sort by desc: bool True for descending order, False for ascending bands: list list of indices of requested bads, default None which returns all bands Returns ------- GeoRaster2 """ bb = mercantile.xy_bounds(x, y, z) roi = GeoVector.from_bounds(xmin=bb.left, ymin=bb.bottom, xmax=bb.right, ymax=bb.top, crs=WEB_MERCATOR_CRS) filtered_fc = self.filter(roi) def _get_tiled_feature(feature): return feature.get_tiled_feature(x, y, z, bands) with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executer: tiled_features = list( executer.map(_get_tiled_feature, filtered_fc, timeout=CONCURRENCY_TIMEOUT)) # tiled_features can be sort for different merge strategies if sort_by is not None: tiled_features = sorted(tiled_features, reverse=desc, key=lambda f: f[sort_by]) tiles = [f['tile'] for f in tiled_features] if tiles: tile = merge_all(tiles, roi) return tile else: return None
def test_merge_all_non_overlapping_has_correct_metadata(): # See https://github.com/satellogic/telluric/issues/65 affine = Affine.translation(0, 2) * Affine.scale(1, -1) rs1 = GeoRaster2( image=np.array([[[100, 0], [100, 0]]], dtype=np.uint8), affine=affine, crs=WGS84_CRS, band_names=['red'], nodata=0, ) rs2 = GeoRaster2( image=np.array([[[110, 0], [110, 0]]], dtype=np.uint8), affine=affine, crs=WGS84_CRS, band_names=['green'], nodata=0, ) rs3 = GeoRaster2( image=np.array([[[0, 200], [0, 200]]], dtype=np.uint8), affine=affine, crs=WGS84_CRS, band_names=['red'], nodata=0, ) rs4 = GeoRaster2( image=np.array([[[0, 210], [0, 210]]], dtype=np.uint8), affine=affine, crs=WGS84_CRS, band_names=['green'], nodata=0, ) expected_metadata = GeoRaster2(image=np.ma.masked_array([[ [0, 2], [0, 2], ], [ [1, 3], [1, 3], ]], np.ma.nomask), affine=affine, crs=WGS84_CRS, band_names=['red', 'green']) metadata = merge_all([rs1, rs2, rs3, rs4], rs1.footprint(), pixel_strategy=PixelStrategy.INDEX) assert metadata == expected_metadata
def test_merge_all_non_overlapping_covers_all(): # See https://github.com/satellogic/telluric/issues/65 affine = Affine.translation(0, 2) * Affine.scale(1, -1) rs1 = GeoRaster2( image=np.array([[[100, 0], [100, 0]]], dtype=np.uint8), affine=affine, crs=WGS84_CRS, band_names=['red'], nodata=0, ) rs2 = GeoRaster2( image=np.array([[[110, 0], [110, 0]]], dtype=np.uint8), affine=affine, crs=WGS84_CRS, band_names=['green'], nodata=0, ) rs3 = GeoRaster2( image=np.array([[[0, 200], [0, 200]]], dtype=np.uint8), affine=affine, crs=WGS84_CRS, band_names=['red'], nodata=0, ) rs4 = GeoRaster2( image=np.array([[[0, 210], [0, 210]]], dtype=np.uint8), affine=affine, crs=WGS84_CRS, band_names=['green'], nodata=0, ) expected_image = np.ma.masked_array( [[[100, 200], [100, 200]], [[110, 210], [110, 210]]], False) result = merge_all([rs1, rs2, rs3, rs4], rs1.footprint()).limit_to_bands(['red', 'green']) assert_array_equal(result.image.data, expected_image.data) assert_array_equal(result.image.mask, expected_image.mask)
def test_rasters_covering_different_areas_with_gap_on_x(): affine_a = Affine.translation(1, 2) * Affine.scale(1, -1) raster_a = make_test_raster(1, [1], height=10, width=10, affine=affine_a) affine_b = Affine.translation(21, 2) * Affine.scale(1, -1) raster_b = make_test_raster(2, [1], height=10, width=10, affine=affine_b) roi = GeoVector.from_bounds(xmin=1, ymin=-8, xmax=30, ymax=2, crs=WEB_MERCATOR_CRS) rasters = [raster_a, raster_b] merged = merge_all(rasters, roi) assert (merged.affine.almost_equals(affine_a)) assert (not merged.image.mask[0, 0:10, 0:10].all()) assert (merged.image.mask[0, 0:10, 10:20].all()) assert (not merged.image.mask[0, 0:10, 20:30].all()) assert ((merged.image.data[0, 0:10, 0:10] == 1).all()) assert ((merged.image.data[0, 0:10, 11:20] == 0).all()) assert ((merged.image.data[0, 0:10, 21:30] == 2).all())
def test_rasters_close_than_resolution_to_roi_2(): one_affine = Affine(1, 0, 598847, 0, -1, 3471062) other_affine = Affine(0.9998121393135052104028, -0.000563382202975665213, 596893.24732190347276628, -0.000249934917683214408, -1.000473374252140335016, 3466367.0648421039804816) one = make_test_raster(1, [1], height=4696, width=4696, affine=one_affine, crs=CRS.from_epsg(32641)) other = make_test_raster(2, [1], height=2616, width=5402, affine=other_affine, crs=CRS.from_epsg(32641)) roi = GeoVector.from_bounds(598847.0000000002, 3466365.999999999, 603542.9999999995, 3471062, crs=one.crs) merged = merge_all([one, other], dest_resolution=(1, 1), roi=roi) assert merged == one
def test_merge_multi_band_multi_size_raster_3(): rasters = get_rasters() raster2 = merge_all(rasters, roi=rasters[3].footprint()) assert(raster2 == rasters[3])
def test_merge_multi_band_multi_raster_smaller_roi_returns_itself(): rasters = [black_and_white_raster([1, 2, 3])] raster = black_and_white_raster([1, 2, 3], height=7, width=6) raster2 = merge_all(rasters, roi=raster.footprint()) assert(raster2 == raster)
def test_merge_multi_band_multi_raster_returns_itself(): rasters = [black_and_white_raster([1, 2, 3]) for i in range(10)] raster = black_and_white_raster([1, 2, 3]) raster2 = merge_all(rasters, roi=raster.footprint()) assert(raster2 == black_and_white_raster([1, 2, 3]))
def rasterize(self, dest_resolution, *, polygonize_width=0, crs=WEB_MERCATOR_CRS, fill_value=None, bounds=None, dtype=None, **polygonize_kwargs): """Binarize a FeatureCollection and produce a raster with the target resolution. Parameters ---------- dest_resolution: float Resolution in units of the CRS. polygonize_width : int, optional Width for the polygonized features (lines and points) in pixels, default to 0 (they won't appear). crs : ~rasterio.crs.CRS, dict (optional) Coordinate system, default to :py:data:`telluric.constants.WEB_MERCATOR_CRS`. fill_value : float or function, optional Value that represents data, default to None (will default to :py:data:`telluric.rasterization.FILL_VALUE`. If given a function, it must accept a single :py:class:`~telluric.features.GeoFeature` and return a numeric value. nodata_value : float, optional Nodata value, default to None (will default to :py:data:`telluric.rasterization.NODATA_VALUE`. bounds : GeoVector, optional Optional bounds for the target image, default to None (will use the FeatureCollection convex hull). dtype : numpy.dtype, optional dtype of the result, required only if fill_value is a function. polygonize_kwargs : dict Extra parameters to the polygonize function. """ # Avoid circular imports from telluric.georaster import merge_all, MergeStrategy from telluric.rasterization import rasterize, NODATA_DEPRECATION_WARNING # Compute the size in real units and polygonize the features if not isinstance(polygonize_width, int): raise TypeError("The width in pixels must be an integer") if polygonize_kwargs.pop("nodata_value", None): warnings.warn(NODATA_DEPRECATION_WARNING, DeprecationWarning) # If the pixels width is 1, render points as squares to avoid missing data if polygonize_width == 1: polygonize_kwargs.update(cap_style_point=CAP_STYLE.square) # Reproject collection to target CRS if (self.crs is not None and self.crs != crs): reprojected = self.reproject(crs) else: reprojected = self width = polygonize_width * dest_resolution polygonized = [ feature.polygonize(width, **polygonize_kwargs) for feature in reprojected ] # Discard the empty features shapes = [ feature.geometry.get_shape(crs) for feature in polygonized if not feature.is_empty ] if bounds is None: bounds = self.envelope if bounds.area == 0.0: raise ValueError("Specify non-empty ROI") if not len(self): fill_value = None if callable(fill_value): if dtype is None: raise ValueError( "dtype must be specified for multivalue rasterization") rasters = [] for feature in self: rasters.append( feature.geometry.rasterize(dest_resolution, fill_value=fill_value(feature), bounds=bounds, dtype=dtype, crs=crs)) return merge_all(rasters, bounds.reproject(crs), dest_resolution, merge_strategy=MergeStrategy.INTERSECTION) else: return rasterize(shapes, crs, bounds.get_shape(crs), dest_resolution, fill_value=fill_value, dtype=dtype)