def run_gdaldem(filepath: str, processing: str, options: str | None = None) -> np.ma.masked_array: """Run GDAL's DEMProcessing and return the read numpy array.""" # Rasterio strongly recommends against importing gdal along rio, so this is done here instead. from osgeo import gdal # Converting string into gdal processing options here to avoid import gdal outside this function: # Riley or Wilson for Terrain Ruggedness, and Zevenberg or Horn for slope, aspect and hillshade gdal_option_conversion = { "Riley": gdal.DEMProcessingOptions(alg="Riley"), "Wilson": gdal.DEMProcessingOptions(alg="Wilson"), "Zevenberg": gdal.DEMProcessingOptions(alg="ZevenbergenThorne"), "Horn": gdal.DEMProcessingOptions(alg="Horn"), "hillshade_Zevenberg": gdal.DEMProcessingOptions(azimuth=315, altitude=45, alg="ZevenbergenThorne"), "hillshade_Horn": gdal.DEMProcessingOptions(azimuth=315, altitude=45, alg="Horn") } if options is None: gdal_option = gdal.DEMProcessingOptions(options=None) else: gdal_option = gdal_option_conversion[options] temp_dir = tempfile.TemporaryDirectory() temp_path = os.path.join(temp_dir.name, "output.tif") gdal.DEMProcessing( destName=temp_path, srcDS=filepath, processing=processing, options=gdal_option, ) data = gu.Raster(temp_path).data temp_dir.cleanup() return data
def test_to_points(self) -> None: """Test the outputs of the to_points method and that it doesn't load if not needed.""" # Create a small raster to test point sampling on img1 = gu.Raster.from_array(np.arange(25, dtype="int32").reshape(5, 5), transform=rio.transform.from_origin( 0, 5, 1, 1), crs=4326) # Sample the whole raster (fraction==1) points = img1.to_points(1) # Validate that 25 points were sampled (equating to img1.height * img1.width) with x, y, and band0 values. assert isinstance(points, np.ndarray) assert points.shape == (25, 3) assert np.array_equal(np.asarray(points[:, 0]), np.tile(np.linspace(0.5, 4.5, 5), 5)) assert img1.to_points(0.2).shape == (5, 3) img2 = gu.Raster(datasets.get_path("landsat_RGB"), load_data=False) points = img2.to_points(10) assert points.shape == (10, 5) assert not img2.is_loaded points_frame = img2.to_points(10, as_frame=True) assert np.array_equal(points_frame.columns, ["b1", "b2", "b3", "geometry"]) assert points_frame.crs == img2.crs
def test_merge_bounds(self) -> None: """ Check that merge_bounds and bounds2poly work as expected for all kinds of bounds objects. """ img1 = gu.Raster(gu.datasets.get_path("landsat_B4")) img2 = gu.Raster(gu.datasets.get_path("landsat_B4_crop")) # Check union (default) - with Raster objects out_bounds = pt.merge_bounds((img1, img2)) assert out_bounds[0] == min(img1.bounds.left, img2.bounds.left) assert out_bounds[1] == min(img1.bounds.bottom, img2.bounds.bottom) assert out_bounds[2] == max(img1.bounds.right, img2.bounds.right) assert out_bounds[3] == max(img1.bounds.top, img2.bounds.top) # Check intersection - with Raster objects out_bounds = pt.merge_bounds((img1, img2), merging_algorithm="intersection") assert out_bounds[0] == max(img1.bounds.left, img2.bounds.left) assert out_bounds[1] == max(img1.bounds.bottom, img2.bounds.bottom) assert out_bounds[2] == min(img1.bounds.right, img2.bounds.right) assert out_bounds[3] == min(img1.bounds.top, img2.bounds.top) # Check that the results is the same with rio.BoundingBoxes out_bounds2 = pt.merge_bounds((img1.bounds, img2.bounds), merging_algorithm="intersection") assert out_bounds2 == out_bounds # Check that the results is the same with a list out_bounds2 = pt.merge_bounds((list(img1.bounds), list(img2.bounds)), merging_algorithm="intersection") assert out_bounds2 == out_bounds # Check with gpd.GeoDataFrame outlines = gu.Vector(gu.datasets.get_path("glacier_outlines")) outlines = gu.Vector(outlines.ds.to_crs( img1.crs)) # reproject to img1's CRS out_bounds = pt.merge_bounds((img1, outlines.ds)) assert out_bounds[0] == min(img1.bounds.left, outlines.ds.total_bounds[0]) assert out_bounds[1] == min(img1.bounds.bottom, outlines.ds.total_bounds[1]) assert out_bounds[2] == max(img1.bounds.right, outlines.ds.total_bounds[2]) assert out_bounds[3] == max(img1.bounds.top, outlines.ds.total_bounds[3])
def run_gdaldem(filepath: str, processing: str) -> np.ma.masked_array: """Run GDAL's DEMProcessing and return the read numpy array.""" # rasterio strongly recommends against importing gdal along rio, so this is done here instead. from osgeo import gdal temp_dir = tempfile.TemporaryDirectory() temp_path = os.path.join(temp_dir.name, "output.tif") gdal.DEMProcessing( destName=temp_path, srcDS=filepath, processing=processing, options=gdal.DEMProcessingOptions(azimuth=315, altitude=45), ) data = gu.Raster(temp_path).data temp_dir.cleanup() return data
def crop2raster(self, rst: gu.Raster) -> None: """ Update self so that features outside the extent of a raster file are cropped. Reprojection is done on the fly if both data set have different projections. :param rst: A Raster object or string to filename """ # If input is string, open as Raster if isinstance(rst, str): rst = gu.Raster(rst) # Convert raster extent into self CRS # Note: could skip this if we could test if rojections are same # Note: should include a method in Raster to get extent in other projections, not only using corners left, bottom, right, top = rst.bounds x1, y1, x2, y2 = warp.transform_bounds(rst.crs, self.ds.crs, left, bottom, right, top) self.ds = self.ds.cx[x1:x2, y1:y2]
def test_latlon_reproject(self) -> None: """ Check that to and from latlon projections are self consistent within tolerated rounding errors """ img = gu.Raster(gu.datasets.get_path("landsat_B4")) # Test on random points nsample = 100 randx = np.random.randint(low=img.bounds.left, high=img.bounds.right, size=(nsample, )) randy = np.random.randint(low=img.bounds.bottom, high=img.bounds.top, size=(nsample, )) lat, lon = pt.reproject_to_latlon([randx, randy], img.crs) x, y = pt.reproject_from_latlon([lat, lon], img.crs) assert np.all(x == randx) assert np.all(y == randy)
def test_read_paths_raster(test_dataset: str) -> None: assert isinstance(gu.Raster(datasets.get_path(test_dataset)), gu.Raster)
"""Example scripts to open and print information about a raster.""" import geoutils as gu # Fetch an example file filename = gu.datasets.get_path("landsat_B4") # Open the file image = gu.Raster(filename) #### TEXT information = image.info() #### TEXT information = image.info(stats=True) #### TEXT with open("file.txt", "w") as fh: fh.writelines(information)
def rasterize( self, rst: str | gu.georaster.RasterType | None = None, crs: CRS | None = None, xres: float | None = None, yres: float | None = None, bounds: tuple[float, float, float, float] | None = None, in_value: int | float | abc.Iterable[int | float] | None = None, out_value: int | float = 0, ) -> np.ndarray: """ Return an array with input geometries burned in. By default, output raster has the extent/dimensions of the provided raster file. Alternatively, user can specify a grid to rasterize on using xres, yres, bounds and crs. Only xres is mandatory, by default yres=xres and bounds/crs are set to self's. Burn value is set by user and can be either a single number, or an iterable of same length as self.ds. Default is an index from 1 to len(self.ds). :param rst: A raster to be used as reference for the output grid :param crs: A pyproj or rasterio CRS object (Default to rst.crs if not None then self.crs) :param xres: Output raster spatial resolution in x. Only is rst is None. :param yres: Output raster spatial resolution in y. Only if rst is None. (Default to xres) :param bounds: Output raster bounds (left, bottom, right, top). Only if rst is None (Default to self bounds) :param in_value: Value(s) to be burned inside the polygons (Default is self.ds.index + 1) :param out_value: Value to be burned outside the polygons (Default is 0) :returns: array containing the burned geometries """ # If input rst is string, open as Raster if isinstance(rst, str): rst = gu.Raster(rst) # type: ignore # If no rst given, use provided dimensions if rst is None: # At minimum, xres must be set if xres is None: raise ValueError("at least rst or xres must be set") if yres is None: yres = xres # By default, use self's CRS and bounds if crs is None: crs = self.ds.crs if bounds is None: bounds = self.ds.total_bounds # Calculate raster shape left, bottom, right, top = bounds height = abs((right - left) / xres) width = abs((top - bottom) / yres) if width % 1 != 0 or height % 1 != 0: warnings.warn( "Bounds not a multiple of xres/yres, use rounded bounds") width = int(np.round(width)) height = int(np.round(height)) out_shape = (height, width) # Calculate raster transform transform = rio.transform.from_bounds(left, bottom, right, top, width, height) # otherwise use directly rst's dimensions else: out_shape = rst.shape # type: ignore transform = rst.transform # type: ignore crs = rst.crs # type: ignore # Reproject vector into rst CRS # Note: would need to check if CRS are different vect = self.ds.to_crs(crs) # Set default burn value, index from 1 to len(self.ds) if in_value is None: in_value = self.ds.index + 1 # Rasterize geometry if isinstance(in_value, abc.Iterable): if len(in_value) != len(vect.geometry): # type: ignore raise ValueError( "in_value must have same length as self.ds.geometry, currently {} != {}" .format( len(in_value), len(vect.geometry) # type: ignore )) out_geom = ((geom, value) for geom, value in zip(vect.geometry, in_value)) mask = features.rasterize(shapes=out_geom, fill=out_value, out_shape=out_shape, transform=transform) elif isinstance(in_value, Number): mask = features.rasterize(shapes=vect.geometry, fill=out_value, out_shape=out_shape, transform=transform, default_value=in_value) else: raise ValueError( "in_value must be a single number or an iterable with same length as self.ds.geometry" ) return mask
def create_mask( self, rst: str | gu.georaster.RasterType | None = None, crs: CRS | None = None, xres: float | None = None, yres: float | None = None, bounds: tuple[float, float, float, float] | None = None, ) -> np.ndarray: """ Rasterize the vector features into a boolean raster which has the extent/dimensions of \ the provided raster file. Alternatively, user can specify a grid to rasterize on using xres, yres, bounds and crs. Only xres is mandatory, by default yres=xres and bounds/crs are set to self's. Vector features which fall outside the bounds of the raster file are not written to the new mask file. :param rst: A Raster object or string to filename :param crs: A pyproj or rasterio CRS object (Default to rst.crs if not None then self.crs) :param xres: Output raster spatial resolution in x. Only is rst is None. :param yres: Output raster spatial resolution in y. Only if rst is None. (Default to xres) :param bounds: Output raster bounds (left, bottom, right, top). Only if rst is None (Default to self bounds) :returns: array containing the mask """ # If input rst is string, open as Raster if isinstance(rst, str): rst = gu.Raster(rst) # type: ignore # If no rst given, use provided dimensions if rst is None: # At minimum, xres must be set if xres is None: raise ValueError("at least rst or xres must be set") if yres is None: yres = xres # By default, use self's CRS and bounds if crs is None: crs = self.ds.crs if bounds is None: bounds = self.ds.total_bounds # Calculate raster shape left, bottom, right, top = bounds height = abs((right - left) / xres) width = abs((top - bottom) / yres) if width % 1 != 0 or height % 1 != 0: warnings.warn( "Bounds not a multiple of xres/yres, use rounded bounds") width = int(np.round(width)) height = int(np.round(height)) out_shape = (height, width) # Calculate raster transform transform = rio.transform.from_bounds(left, bottom, right, top, width, height) # otherwise use directly rst's dimensions else: out_shape = rst.shape # type: ignore transform = rst.transform # type: ignore crs = rst.crs # type: ignore # Reproject vector into rst CRS # Note: would need to check if CRS are different vect = self.ds.to_crs(crs) # Rasterize geometry mask = features.rasterize(shapes=vect.geometry, fill=0, out_shape=out_shape, transform=transform, default_value=1, dtype="uint8").astype("bool") # Force output mask to be of same dimension as input rst if rst is not None: mask = mask.reshape( (rst.count, rst.height, rst.width)) # type: ignore return mask
import geoutils as gu filename = gu.datasets.get_path("landsat_B4") raster = gu.Raster(filename) print(raster)
""" Loading and understanding Rasters ================================= This is (right now) a dummy example for showing the functionality of :class:`geoutils.Raster`. """ import matplotlib.pyplot as plt import geoutils as gu # %% # Example raster: img = gu.Raster(gu.datasets.get_path("landsat_B4")) # %% # Info: print(img) # %% # A plot: img.show(cmap="Greys_r") plt.show()
"""Exemplify uses of crop and reproject.""" import geoutils as gu large_image = gu.Raster(gu.datasets.get_path("landsat_B4")) smaller_image = gu.Raster(gu.datasets.get_path("landsat_B4_crop")) # TEXT large_image_orig = large_image.copy( ) # Since it gets modified inplace, we want to keep it to print stats. large_image.crop(smaller_image) # TEXT large_image_reprojected = large_image.reproject(smaller_image)