Transformations#
GeoUtils implements transformations on, and at the interface of, rasters, vectors and point clouds with scalable execution for large datasets and consistent resampling techniques.
For all geospatial data objects, Same-type transformations are exposed through the same functions
(e.g. reproject()). Operations between different object types are
grouped separately below in Raster–vector transformations, Raster–point transformations and Masking from vector.
Tip
For convenience, many operations accept another raster, vector or point cloud as a reference to match. See Match-reference operations for details.
Choosing a resampling method#
Resampling is shared among several transformations operations, such as reprojection or point gridding. It can use either interpolators (e.g. linear interpolation) or reducers (e.g. window average) to estimate values over a new spatial support. Below, we summarize available methods and backends.
Note
In GeoUtils, "linear" is the default resampling method.
Refer to Operators and neighborhoods for operator, neighborhood and nodata customization.
Interpolators#
The following interpolation methods and backends are supported, for point and raster inputs:
Note that “linear” and “cubic” interpolators for point inputs first use Delauney triangulation.
Reducers#
And the following reduction methods and backends:
Note that some reducer do not retain the unit of the input.
Tip
Due to loss of information, it is important to minimize the number of resampling during analysis (performing only one, if possible).
For the same reason, when comparing vector, point cloud and rasters in different CRSs, it is usually better to reproject the vector or point cloud with no loss of information, which is the default Co-sampling behaviour of GeoUtils.
Same-type transformations#
Reproject#
ds.rst.reproject() or Raster.reproject()
gdf.vct.reproject() or Vector.reproject()
gdf.pc.reproject() or PointCloud.reproject()
Reprojection transforms data from one coordinate reference system to another.
For vectors and point clouds, the transformation of geometry points is exact. However, in the case of rasters, the projected points do not necessarily fall on a regular grid and require resampling, which results in a slight loss of information.
For rasters, it can be useful to use reproject() in the same CRS simply for re-gridding,
for instance when downsampling to a new resolution res.
You can define the destination grid with a bounding box and resolution. Here, we stay in the source CRS and use 500 × 1000 m cells over a centered 10 × 10 km square.
# Reproject raster to smaller bounds and different X/Y resolution
center_x = (ds.rst.bbox.left + ds.rst.bbox.right) / 2
center_y = (ds.rst.bbox.bottom + ds.rst.bbox.top) / 2
ds_regridded = ds.rst.reproject(
res=(500, 1000),
bounds={"left": center_x - 5000, "bottom": center_y - 5000,
"right": center_x + 5000, "top": center_y + 5000},
resampling="cubic")
Note
GeoUtils resampling uses tolerance=0 by default to calculate exact pixel transformations and make results
consistent across chunks. Rasterio/GDAL’s default is 0.125 pixel, which allows faster computation but can
change results depending on chunk bounds. Passing the tolerance option requires Rasterio 1.5 or newer.
When values represent elevations and the transformation should also change their vertical datum,
either use apply_vertical=True or use xDEM’s reprojection which accounts
for vertical referencing implicitly.
Passing another raster as a reference matches its CRS, bounds, resolution and grid alignment.
# Reproject raster to match another raster by simply passing it as reference
ds_reproj = ds.rst.reproject(reference)
UserWarning: One raster has a pixel interpretation "Area" and the other "Point". To silence this warning, either correct the pixel interpretation of one raster, or deactivate warnings of pixel interpretation with geoutils.config["warn_area_or_point"]=False.
GeoUtils raises a warning because the rasters have different Pixel interpretation, to ensure this is intended. This warning can be turned off at the package-level using GeoUtils’ Configuration.
GCP/RPC reprojection#
Rasters referenced by ground control points (GCPs) or rational polynomial coefficients (RPCs) can also be reprojected.
GeoUtils reads and uses the GCP/RPC models from file automatically. The result has a regular affine grid. GCPs use their stored CRS, and RPCs describe WGS84 coordinates.
However, GCP/RPC reprojection requires a Rasterio resampling method, such as "bilinear". Use GDAL transformer options control how GCPs/RPCs map pixel positions to ground coordinates
(see Rasterio’s documentation).
Below, we give the ASTER DEM some GCPs at its four corners. The GCP_TPS option uses thin plate splines to map pixels smoothly through the
control points.
# Reproject using the assigned GCPs and thin plate splines
result = source.reproject(
crs=4326, resampling="bilinear", transformer_options={"SRC_METHOD": "GCP_TPS"}
)
# Show the regular affine grid created by reprojection
print(result.crs)
print(result.transform)
EPSG:4326
| 0.00, 0.00,-73.34|
| 0.00,-0.00,-46.47|
| 0.00, 0.00, 1.00|
Crop#
ds.rst.crop() or Raster.crop()
gdf.vct.crop() or Vector.crop()
gdf.pc.crop() or PointCloud.crop()
Cropping selects data in a rectangular extent without modifying the selected values or geometries.
For rasters, crop removes complete rows and columns outside the extent. For point clouds, it removes points outside
the extent. For vectors, the default mode="intersects" keeps every unchanged feature touching the extent;
mode="within" keeps only features fully contained by it.
Note
A file-backed raster, vector or point cloud stores this selection as metadata and defer reading until its data are requested, much
like Xarray’s isel().
See Deferred I/O for details.
First, we crop a raster to the bounding box of a vector:
# Crop the raster to the selected glacier's extent
ds_cropped = ds.rst.crop(crop_glacier)
Then, for a vector, we crop intersecting or contained features using the smaller raster as crop extent:
# Select whole features using the same extent with either mode
gdf_intersects = gdf_reproj.vct.crop(ds2, mode="intersects")
gdf_within = gdf_reproj.vct.crop(ds2, mode="within")
Clip#
ds.rst.clip() or Raster.clip()
gdf.vct.clip() or Vector.clip()
gdf.pc.clip() or PointCloud.clip()
Clipping cuts values within exact geometry.
It masks raster cells outside the geometry, removes point cloud rows outside it, and cuts vector geometries at its boundary. Raster, vector and point cloud clipping support lazy Dask chunks or partitions and file-backed multiprocessing blocks.
Below, we first clip a raster with an exact vector geometry, turning outside cells into NaNs.
# Clip the cropped raster to the glacier geometry
ds_clipped = ds_cropped.rst.clip(crop_glacier)
Then, we clip a vector with a raster footprint, cutting its geometries at the raster edges:
# Clip the vector to the raster
gdf_clipped = gdf_reproj.vct.clip(ds)
Translate#
ds.rst.translate() or Raster.translate()
gdf.vct.translate() or Vector.translate()
gdf.pc.translate() or PointCloud.translate()
Translation applies a horizontal offset to the georeferencing without modifying the underlying data, which is especially useful to align the data due to positioning errors.
# Translate the raster by a certain offset
ds_shift = ds.rst.translate(xoff=1000, yoff=1000)
See also
For 3D coregistration tailored to georeferenced elevation data, see xDEM’s coregistration module.
Merge#
geoutils.raster.merge_rasters()
Merge operations join multiple geospatial data spatially, possibly with different georeferencing, into a single geospatial data object.
For rasters, the merging operation consists in combining all rasters into a single, larger raster. Pixels that overlap are combined by a reductor function (defaults to the mean). The output georeferenced grid (CRS, transform and shape) can be set to that of any reference raster (defaults to the extent that contains exactly all rasters).
This standalone function currently accepts Raster objects.
# Merging all rasters, uses first raster's CRS, res, and the extent of all by default
merged_rast = gu.raster.merge_rasters([rast1, rast2, rast3, rast4])
Raster–vector transformations#
Rasterize#
gdf.vct.rasterize() or Vector.rasterize()
Rasterization sets the values of raster pixels intersecting a vector geometry feature to that of an attribute (e.g., feature ID, area or any other value), which is the geometry index by default.
Rasterization generally implies some loss of information, as there is no exact way of representing a vector on a grid. Rather, the choice of which pixels are attributed a value depends on the amount of intersection with the vector geometries and so includes several options (percent of area intersected, all touched, etc).
# Rasterize the vector features based on their glacier ID number
rasterized_gdf = gdf.vct.rasterize(ds)
Fractional rasterization#
Standard rasterization assigns a unique value to selected cells, while fractional rasterization instead returns the fraction of each cell covered by a geometry, from 0 to 1. This is useful when polygon edges cut through cells and using a binary intersection (0 or 1) would substantially distort the represented area.
# Calculate the fraction of each raster cell covered by the union of glacier polygons
glacier_fraction = gdf.vct.rasterize(ref=ds, fractional="union")
We zoom into a glacier margin to show fractional coverage of individual 30 m cells:
By default, the function returns one fractional layer per feature, but all features can also be combined together with fractional="union",
returning the sum of fractional coverages per pixel over all features.
See Cell coverage for coverage semantics and Fractional zonal statistics for calculating zonal statistics directly, without computing fractional coverage rasters manually.
Polygonize#
ds.rst.polygonize() or Raster.polygonize()
Polygonization of a raster consists of delimiting contiguous raster pixels with the same target values into vector polygon
geometries. By default, all raster values are used as targets. When using polygonize on a raster mask, i.e. a boolean Raster,
the targets are implicitly the valid values of the mask.
# Mask 0 values
rasterized_gdf = rasterized_gdf.where(rasterized_gdf != 0)
# Polygonize all non-zero values
gdf_repolygonized = rasterized_gdf.rst.polygonize()
Raster–point transformations#
Interpolation at points#
ds.rst.interp_at_points() or Raster.interp_at_points()
Point interpolation of a raster consists in estimating the values at exact point coordinates by 2D regular-grid interpolation such as nearest neighbour, bilinear (default), cubic, etc.
See interpolator comparisons and options to choose the prediction method.
Note
The default nodata_handling="nearest" masks a prediction when its nearest source observation is missing.
Use "ignore" to calculate from valid observations or "propagate" to mask results affected by missing inputs.
An integer or "half_order_up" / "half_order_down" instead spreads the missing-data mask by a distance in
source pixels. See Nodata propagation and Customize operators and neighborhoods for these choices.
Tuple coordinates are supplied in X/Y order. With input_latlon=True, use longitude, latitude in degrees;
a georeferenced point cloud supplies its own CRS and is projected automatically.
# We use a DEM, often requiring interpolation
ds = gu.open_raster(gu.examples.get_path("exploradores_aster_dem"))
# Get 50 random points to sample within the raster extent
rng = np.random.default_rng(42)
x_coords = rng.uniform(ds.rst.bbox.left, ds.rst.bbox.right, 50)
y_coords = rng.uniform(ds.rst.bbox.bottom, ds.rst.bbox.top, 50)
gdf_int = ds.rst.resample_at_points(
points=(x_coords, y_coords), resampling="linear", backend="scipy"
)
The interpolated points can be returned as a GeoDataFrame or
PointCloud, enabling quick interfacing, or as an array.
Reduction around point#
Point reduction of a raster is the estimation of the values at point coordinates by applying a reductor function (e.g., mean, median) to pixels contained in a window centered on the point. For a window smaller than the pixel size, the value of the closest pixel is returned.
ds.rst.reduce_at_points() or Raster.reduce_at_points()
See reducer comparisons and options to choose the statistic for each window.
gdf_red = ds.rst.resample_at_points(
(x_coords, y_coords), resampling="median", backend="scipy", window=5
)
The reduced points can be returned as a GeoDataFrame or PointCloud, enabling
quick interfacing, or as an array.
Raster to regular points#
ds.rst.to_pointcloud() or Raster.to_pointcloud()
A raster can be converted exactly into a regular point cloud, which each pixel in the raster is associated to its pixel values to create a point cloud on a regular grid. Optionally, it can also be subsampled at valid values only.
Note
For more details on subsampling, see the Sampling feature page
points = ds.rst.to_pointcloud(subsample=10000)
Regular points to raster#
ds.rst.from_pointcloud_regular() or Raster.from_pointcloud_regular()
If a point cloud is regularly spaced in X and Y coordinates, it can be converted exactly into a raster. Otherwise, it must be re-gridded using Point gridding described below. For a regular point cloud, every point is associated to a pixel in the raster grid, and the values are set to the raster. The point cloud does not necessarily need to contain points for all grid coordinates, as pixels with no corresponding point are set to nodata values.
ds_from_points = ds.rst.from_pointcloud_regular(points, transform=ds.rst.transform, shape=ds.rst.shape)
Point gridding#
Gridding of a point cloud consists in estimating the values at 2D regular gridded coordinates based on an irregular point cloud using Delaunay triangular interpolation with “bilinear” (default) or “cubic” resampling, inverse-distance weighting or circular statistics.
Choose an interpolator or reducer and a backend from the comparisons above.
Note
For gridding, GeoUtils introduces nodata values in distances surrounding initial point coordinates, defaulting to a distance of 1 pixel.
gdf.pc.grid() or PointCloud.grid()
# Grid the subsampled points back onto the raster reference
gridded_points = points.pc.grid(ds, resampling="linear", backend="scipy", radius=10)
Masking from vector#
gdf.vct.create_mask() or Vector.create_mask()
Mask creation from a vector classifies raster cells or point locations by whether they intersect any vector feature. It therefore depends only on the geometries, independently of their attribute values.
# Open vector features matching the raster and interpolated points used above
gdf = gu.open_vector(gu.examples.get_path("exploradores_rgi_outlines"))
# Create a boolean raster mask from all vector features
raster_mask = gdf.vct.create_mask(ds)
# Create a boolean mask on the interpolated point cloud used above
point_mask = gdf.vct.create_mask(gdf_int)
With a raster reference, this returns a georeferenced boolean xarray.DataArray for an accessor call, or a
Raster mask for an object call. With a point cloud reference, it returns the same point coordinates
with a boolean active data column.
Both outputs can be used as boolean selectors for their reference data.
# Mean of values in the mask
ds.where(raster_mask).mean().item()
1806.718017578125