⚠️ Our 0.1 release refactored several early-development functions for long-term stability, to update your code see here. ⚠️
Future changes will come with deprecation warnings! 🙂

Support of pythonic operators#

GeoUtils integrates pythonic operators for shorter, more intuitive code, and to perform arithmetic and logical operations consistently.

These operators work on Rasters much as they would on ndarrays, with some more details.

Arithmetic of Raster classes#

Arithmetic operators (+, -, /, //, *, **, %) can be used on a Raster in combination with any other Raster, ndarray or number.

For an operation with another Raster, the georeferencing (crs and transform) must match. For another ndarray, the shape must match. The operation always returns a Raster.

import geoutils as gu
import rasterio as rio
import pyproj
import numpy as np

# Create a random 3 x 3 masked array
np.random.seed(42)
arr = np.random.randint(0, 255, size=(3, 3), dtype="uint8")
mask = np.random.randint(0, 2, size=(3, 3), dtype="bool")
ma = np.ma.masked_array(data=arr, mask=mask)

# Create an example raster
rast = gu.Raster.from_array(
       data = ma,
       transform = rio.transform.from_bounds(0, 0, 1, 1, 3, 3),
       crs = pyproj.CRS.from_epsg(4326),
       nodata = 255
    )

rast
Raster(
  data=[[102 -- --]
        [-- 179 61]
        [234 203 --]]
  transform=| 0.33, 0.00, 0.00|
            | 0.00,-0.33, 1.00|
            | 0.00, 0.00, 1.00|
  crs=EPSG:4326
  nodata=255)
# Arithmetic with a number
rast + 1
Raster(
  data=[[103 -- --]
        [-- 180 62]
        [235 204 --]]
  transform=| 0.33, 0.00, 0.00|
            | 0.00,-0.33, 1.00|
            | 0.00, 0.00, 1.00|
  crs=EPSG:4326
  nodata=255)
# Arithmetic with an array
rast / arr
Raster(
  data=[[1.0 -- --]
        [-- 1.0 1.0]
        [1.0 1.0 --]]
  transform=| 0.33, 0.00, 0.00|
            | 0.00,-0.33, 1.00|
            | 0.00, 0.00, 1.00|
  crs=EPSG:4326
  nodata=255)
# Arithmetic with a raster
rast - (rast**0.5)
Raster(
  data=[[91.90049506163793 -- --]
        [-- 165.62091183974036 53.189750324093346]
        [218.70294145922165 188.752193151225 --]]
  transform=| 0.33, 0.00, 0.00|
            | 0.00,-0.33, 1.00|
            | 0.00, 0.00, 1.00|
  crs=EPSG:4326
  nodata=255)

If an unmasked ndarray is passed, it will internally be cast into a MaskedArray to respect the propagation of nodata values. Additionally, the dtype are also reconciled as they would for ndarray, following standard NumPy coercion rules.

Logical comparisons cast to Mask#

Logical comparison operators (==, !=, >=, >, <=, <) can be used on a Raster, also in combination with any other Raster, ndarray or number.

Those operation always return a Mask, a subclass of Raster with a boolean MaskedArray as data.

# Logical comparison with a number
mask = rast > 100
mask
Mask(
  data=[[True -- --]
        [-- True False]
        [True True --]]
  transform=| 0.33, 0.00, 0.00|
            | 0.00,-0.33, 1.00|
            | 0.00, 0.00, 1.00|
  crs=EPSG:4326)

Note

A Mask’s data remains a MaskedArray. Therefore, it still maps invalid values through its mask, but has no associated nodata.

Logical bitwise operations on Mask#

Logical bitwise operators (~, &, |, ^) can be used to combine a Mask with another Mask, and always output a Mask.

# Logical bitwise operation between masks
mask = (rast > 100) & ((rast % 2) == 0)
mask
Mask(
  data=[[True -- --]
        [-- False False]
        [True False --]]
  transform=| 0.33, 0.00, 0.00|
            | 0.00,-0.33, 1.00|
            | 0.00, 0.00, 1.00|
  crs=EPSG:4326)

Indexing a Raster with a Mask#

Finally, indexing and index assignment operations ([], []=) are both supported by Rasters.

For indexing, they can be passed either a Mask with the same georeferencing, or a boolean ndarray of the same shape. For assignment, either a Raster with the same georeferencing, or any ndarray of the same shape is expected.

When indexing, a flattened MaskedArray is returned with the indexed values of the Mask excluding those masked in its data’s MaskedArray (for instance, nodata values present during a previous logical comparison). To bypass this behaviour, simply index without the mask using Mask.data.data.

# Indexing the raster with the previous mask
rast[mask]
masked_array(data=[102, 234],
             mask=[False, False],
       fill_value=255,
            dtype=uint8)