Cubes & Clouds logo

Resample Operators with Pangeo#

Resample Operators#

Sometimes we need to align the spatial or temporal dimension of two datacubes, so that they can be merged together.

Spatial resampling Sentinel-2 to match Landsat#

Start importing the necessary libraries and initialize a local connection for Client-Side Processing.

# STAC Catalogue Libraries
import pystac_client
import stackstac
from rasterio.enums import Resampling

Create two datacubes, one for Sentinel-2 and one for Landsat

spatial_extent = [11.4, 45.5, 11.42, 45.52]
temporal_extent = ["2023-06-01", "2023-06-30"]

Datacube for Sentinel-2#

URL = "https://earth-search.aws.element84.com/v1"
catalog = pystac_client.Client.open(URL)
s2_items = catalog.search(
    bbox=spatial_extent,
    datetime=temporal_extent,
    collections=["sentinel-2-l2a"]
).item_collection()

s2_cube = stackstac.stack(s2_items,
                     bounds_latlon=spatial_extent,
                     assets=["red","nir"]
)
s2_cube
/srv/conda/envs/notebook/lib/python3.11/site-packages/stackstac/prepare.py:408: UserWarning: The argument 'infer_datetime_format' is deprecated and will be removed in a future version. A strict version of it is now the default, see https://pandas.pydata.org/pdeps/0004-consistent-to-datetime-parsing.html. You can safely remove this argument.
  times = pd.to_datetime(
<xarray.DataArray 'stackstac-e2f40d96c48e834b1e77d88ce8ce66ff' (time: 6,
                                                                band: 2,
                                                                y: 228, x: 164)>
dask.array<fetch_raster_window, shape=(6, 2, 228, 164), dtype=float64, chunksize=(1, 1, 228, 164), chunktype=numpy.ndarray>
Coordinates: (12/54)
  * time                                     (time) datetime64[ns] 2023-06-02...
    id                                       (time) <U24 'S2A_32TPR_20230602_...
  * band                                     (band) <U3 'red' 'nir'
  * x                                        (x) float64 6.874e+05 ... 6.891e+05
  * y                                        (y) float64 5.044e+06 ... 5.041e+06
    mgrs:latitude_band                       <U1 'T'
    ...                                       ...
    raster:bands                             object {'nodata': 0, 'data_type'...
    proj:shape                               object {10980}
    common_name                              (band) <U3 'red' 'nir'
    center_wavelength                        (band) float64 0.665 0.842
    full_width_half_max                      (band) float64 0.038 0.145
    epsg                                     int64 32632
Attributes:
    spec:        RasterSpec(epsg=32632, bounds=(687430.0, 5041290.0, 689070.0...
    crs:         epsg:32632
    transform:   | 10.00, 0.00, 687430.00|\n| 0.00,-10.00, 5043570.00|\n| 0.0...
    resolution:  10.0

Datacube for Landsat#

url = "https://planetarycomputer.microsoft.com/api/stac/v1"
catalog = pystac_client.Client.open(URL)
l8_items = catalog.search(
    bbox=spatial_extent,
    datetime=temporal_extent,
    collections=["landsat-c2-l2"]
).item_collection()

l8_cube = stackstac.stack(l8_items,
                     bounds_latlon=spatial_extent,
                     assets=["red","nir08"]
)
l8_cube
/srv/conda/envs/notebook/lib/python3.11/site-packages/stackstac/prepare.py:408: UserWarning: The argument 'infer_datetime_format' is deprecated and will be removed in a future version. A strict version of it is now the default, see https://pandas.pydata.org/pdeps/0004-consistent-to-datetime-parsing.html. You can safely remove this argument.
  times = pd.to_datetime(
<xarray.DataArray 'stackstac-31be86bd1beff02963ed0d75e074c4f9' (time: 15,
                                                                band: 2, y: 76,
                                                                x: 55)>
dask.array<fetch_raster_window, shape=(15, 2, 76, 55), dtype=float64, chunksize=(1, 1, 76, 55), chunktype=numpy.ndarray>
Coordinates: (12/36)
  * time                         (time) datetime64[ns] 2023-06-03T10:03:52.60...
    id                           (time) <U31 'LC08_L2SP_193028_20230603_02_T1...
  * band                         (band) <U5 'red' 'nir08'
  * x                            (x) float64 6.874e+05 6.874e+05 ... 6.89e+05
  * y                            (y) float64 5.044e+06 5.044e+06 ... 5.041e+06
    view:off_nadir               int64 0
    ...                           ...
    title                        (band) <U22 'Red Band' 'Near Infrared Band 0.8'
    storage:platform             <U3 'AWS'
    storage:requester_pays       bool True
    raster:bands                 object {'nodata': 0, 'data_type': 'uint16', ...
    storage:region               <U9 'us-west-2'
    epsg                         int64 32632
Attributes:
    spec:        RasterSpec(epsg=32632, bounds=(687420.0, 5041290.0, 689070.0...
    crs:         epsg:32632
    transform:   | 30.00, 0.00, 687420.00|\n| 0.00,-30.00, 5043570.00|\n| 0.0...
    resolution:  30.0

From the previous outputs, notice the shape difference in the spatial dimensions x and y.

This is due to the different resolution of the two collections: 10m for Sentinel-2, 30m for Landsat.

Now we use the stackstac.stack to resample the Sentinel-2 data to match Landsat.

s2_cube_coarse = stackstac.stack(s2_items,
                                 bounds_latlon=spatial_extent,
                                 assets=["red","nir"],
                                 resolution=30.0, resampling=Resampling.average, epsg=32632)
s2_cube_coarse
/srv/conda/envs/notebook/lib/python3.11/site-packages/stackstac/prepare.py:408: UserWarning: The argument 'infer_datetime_format' is deprecated and will be removed in a future version. A strict version of it is now the default, see https://pandas.pydata.org/pdeps/0004-consistent-to-datetime-parsing.html. You can safely remove this argument.
  times = pd.to_datetime(
<xarray.DataArray 'stackstac-1bf939077e08430e59af76748c4bcf5e' (time: 6,
                                                                band: 2, y: 76,
                                                                x: 55)>
dask.array<fetch_raster_window, shape=(6, 2, 76, 55), dtype=float64, chunksize=(1, 1, 76, 55), chunktype=numpy.ndarray>
Coordinates: (12/54)
  * time                                     (time) datetime64[ns] 2023-06-02...
    id                                       (time) <U24 'S2A_32TPR_20230602_...
  * band                                     (band) <U3 'red' 'nir'
  * x                                        (x) float64 6.874e+05 ... 6.89e+05
  * y                                        (y) float64 5.044e+06 ... 5.041e+06
    mgrs:latitude_band                       <U1 'T'
    ...                                       ...
    raster:bands                             object {'nodata': 0, 'data_type'...
    proj:shape                               object {10980}
    common_name                              (band) <U3 'red' 'nir'
    center_wavelength                        (band) float64 0.665 0.842
    full_width_half_max                      (band) float64 0.038 0.145
    epsg                                     int64 32632
Attributes:
    spec:        RasterSpec(epsg=32632, bounds=(687420.0, 5041290.0, 689070.0...
    crs:         epsg:32632
    transform:   | 30.00, 0.00, 687420.00|\n| 0.00,-30.00, 5043570.00|\n| 0.0...
    resolution:  30.0