Skip to content

rslearn.data_sources

data_sources

Data sources.

A DataSource represents a source from which raster and vector data corresponding to spatiotemporal windows can be retrieved.

A DataSource consists of items that can be ingested, like Sentinel-2 scenes or OpenStreetMap PBF files.

Each source supports operations to lookup items that match with spatiotemporal geometries, and ingest those items.

DataSource

Bases: ABC, Generic[ItemType]

A set of raster or vector files that can be retrieved.

Data sources should support at least one of ingest and materialize.

Source code in rslearn/data_sources/data_source.py
class DataSource(ABC, Generic[ItemType]):
    """A set of raster or vector files that can be retrieved.

    Data sources should support at least one of ingest and materialize.
    """

    @abstractmethod
    def get_items(
        self, geometries: list[STGeometry], query_config: QueryConfig
    ) -> list[list["MatchedItemGroup[ItemType]"]]:
        """Get a list of items in the data source intersecting the given geometries.

        Args:
            geometries: the spatiotemporal geometries
            query_config: the query configuration

        Returns:
            List of groups of items that should be retrieved for each geometry.
        """
        raise NotImplementedError

    @abstractmethod
    def deserialize_item(self, serialized_item: dict) -> ItemType:
        """Deserializes an item from JSON-decoded data."""
        raise NotImplementedError

    def ingest(
        self,
        tile_store: TileStoreWithLayer,
        items: list[ItemType],
        geometries: list[list[STGeometry]],
    ) -> None:
        """Ingest items into the given tile store.

        Args:
            tile_store: the tile store to ingest into
            items: the items to ingest
            geometries: a list of geometries needed for each item
        """
        raise NotImplementedError

    def materialize(
        self,
        window: Window,
        item_groups: list[list[ItemType]],
        layer_name: str,
        layer_cfg: LayerConfig,
        group_time_ranges: list[tuple[datetime, datetime] | None] | None = None,
    ) -> None:
        """Materialize data for the window.

        Args:
            window: the window to materialize
            item_groups: the items from get_items
            layer_name: the name of this layer
            layer_cfg: the config of this layer
            group_time_ranges: optional request time range for each item group
        """
        raise NotImplementedError

get_items abstractmethod

get_items(geometries: list[STGeometry], query_config: QueryConfig) -> list[list[MatchedItemGroup[ItemType]]]

Get a list of items in the data source intersecting the given geometries.

Parameters:

Name Type Description Default
geometries list[STGeometry]

the spatiotemporal geometries

required
query_config QueryConfig

the query configuration

required

Returns:

Type Description
list[list[MatchedItemGroup[ItemType]]]

List of groups of items that should be retrieved for each geometry.

Source code in rslearn/data_sources/data_source.py
@abstractmethod
def get_items(
    self, geometries: list[STGeometry], query_config: QueryConfig
) -> list[list["MatchedItemGroup[ItemType]"]]:
    """Get a list of items in the data source intersecting the given geometries.

    Args:
        geometries: the spatiotemporal geometries
        query_config: the query configuration

    Returns:
        List of groups of items that should be retrieved for each geometry.
    """
    raise NotImplementedError

deserialize_item abstractmethod

deserialize_item(serialized_item: dict) -> ItemType

Deserializes an item from JSON-decoded data.

Source code in rslearn/data_sources/data_source.py
@abstractmethod
def deserialize_item(self, serialized_item: dict) -> ItemType:
    """Deserializes an item from JSON-decoded data."""
    raise NotImplementedError

ingest

ingest(tile_store: TileStoreWithLayer, items: list[ItemType], geometries: list[list[STGeometry]]) -> None

Ingest items into the given tile store.

Parameters:

Name Type Description Default
tile_store TileStoreWithLayer

the tile store to ingest into

required
items list[ItemType]

the items to ingest

required
geometries list[list[STGeometry]]

a list of geometries needed for each item

required
Source code in rslearn/data_sources/data_source.py
def ingest(
    self,
    tile_store: TileStoreWithLayer,
    items: list[ItemType],
    geometries: list[list[STGeometry]],
) -> None:
    """Ingest items into the given tile store.

    Args:
        tile_store: the tile store to ingest into
        items: the items to ingest
        geometries: a list of geometries needed for each item
    """
    raise NotImplementedError

materialize

materialize(window: Window, item_groups: list[list[ItemType]], layer_name: str, layer_cfg: LayerConfig, group_time_ranges: list[tuple[datetime, datetime] | None] | None = None) -> None

Materialize data for the window.

Parameters:

Name Type Description Default
window Window

the window to materialize

required
item_groups list[list[ItemType]]

the items from get_items

required
layer_name str

the name of this layer

required
layer_cfg LayerConfig

the config of this layer

required
group_time_ranges list[tuple[datetime, datetime] | None] | None

optional request time range for each item group

None
Source code in rslearn/data_sources/data_source.py
def materialize(
    self,
    window: Window,
    item_groups: list[list[ItemType]],
    layer_name: str,
    layer_cfg: LayerConfig,
    group_time_ranges: list[tuple[datetime, datetime] | None] | None = None,
) -> None:
    """Materialize data for the window.

    Args:
        window: the window to materialize
        item_groups: the items from get_items
        layer_name: the name of this layer
        layer_cfg: the config of this layer
        group_time_ranges: optional request time range for each item group
    """
    raise NotImplementedError

DataSourceContext

This context is passed to every data source.

When initializing data sources within rslearn, we always set the ds_path and layer_config. However, for convenience (for users directly initializing the data sources externally), each data source should allow for initialization when one or both are missing.

Source code in rslearn/data_sources/data_source.py
class DataSourceContext:
    """This context is passed to every data source.

    When initializing data sources within rslearn, we always set the ds_path and
    layer_config. However, for convenience (for users directly initializing the data
    sources externally), each data source should allow for initialization when one or
    both are missing.
    """

    def __init__(
        self, ds_path: UPath | None = None, layer_config: LayerConfig | None = None
    ):
        """Create a new DataSourceContext.

        Args:
            ds_path: the path of the underlying dataset.
            layer_config: the LayerConfig for the layer that the data source is for.
        """
        # We don't use dataclass here because otherwise jsonargparse will ignore our
        # custom serializer/deserializer defined in rslearn.utils.jsonargparse.
        self.ds_path = ds_path
        self.layer_config = layer_config

Item

An item in a data source.

Items correspond to distinct objects in the data source, such as a raster file (e.g., Sentinel-2 scene) or a vector file (e.g., a single shapefile).

Source code in rslearn/data_sources/data_source.py
class Item:
    """An item in a data source.

    Items correspond to distinct objects in the data source, such as a raster file
    (e.g., Sentinel-2 scene) or a vector file (e.g., a single shapefile).
    """

    def __init__(self, name: str, geometry: STGeometry):
        """Creates a new item.

        Args:
            name: unique name of the item
            geometry: the spatial and temporal extent of the item
        """
        self.name = name
        self.geometry = geometry

    def serialize(self) -> dict:
        """Serializes the item to a JSON-encodable dictionary."""
        return {"name": self.name, "geometry": self.geometry.serialize()}

    @staticmethod
    def deserialize(d: dict) -> "Item":
        """Deserializes an item from a JSON-decoded dictionary."""
        return Item(name=d["name"], geometry=STGeometry.deserialize(d["geometry"]))

    def __eq__(self, other: Any) -> bool:
        """Check equality.

        Args:
            other: the other Item

        Returns:
            whether this Item is the same as the other Item.
        """
        return isinstance(other, Item) and self.name == other.name

    def __hash__(self) -> int:
        """Returns a hash of this item."""
        return hash(self.name)

serialize

serialize() -> dict

Serializes the item to a JSON-encodable dictionary.

Source code in rslearn/data_sources/data_source.py
def serialize(self) -> dict:
    """Serializes the item to a JSON-encodable dictionary."""
    return {"name": self.name, "geometry": self.geometry.serialize()}

deserialize staticmethod

deserialize(d: dict) -> Item

Deserializes an item from a JSON-decoded dictionary.

Source code in rslearn/data_sources/data_source.py
@staticmethod
def deserialize(d: dict) -> "Item":
    """Deserializes an item from a JSON-decoded dictionary."""
    return Item(name=d["name"], geometry=STGeometry.deserialize(d["geometry"]))

ItemLookupDataSource

Bases: DataSource[ItemType]

A data source that can look up items by name.

Source code in rslearn/data_sources/data_source.py
class ItemLookupDataSource(DataSource[ItemType]):
    """A data source that can look up items by name."""

    @abstractmethod
    def get_item_by_name(self, name: str) -> ItemType:
        """Gets an item by name."""
        raise NotImplementedError

get_item_by_name abstractmethod

get_item_by_name(name: str) -> ItemType

Gets an item by name.

Source code in rslearn/data_sources/data_source.py
@abstractmethod
def get_item_by_name(self, name: str) -> ItemType:
    """Gets an item by name."""
    raise NotImplementedError

RetrieveItemDataSource

Bases: DataSource[ItemType]

A data source that can retrieve items in their raw format.

Source code in rslearn/data_sources/data_source.py
class RetrieveItemDataSource(DataSource[ItemType]):
    """A data source that can retrieve items in their raw format."""

    @abstractmethod
    def retrieve_item(
        self, item: ItemType
    ) -> Generator[tuple[str, BinaryIO], None, None]:
        """Retrieves the rasters corresponding to an item as file streams."""
        raise NotImplementedError

retrieve_item abstractmethod

retrieve_item(item: ItemType) -> Generator[tuple[str, BinaryIO], None, None]

Retrieves the rasters corresponding to an item as file streams.

Source code in rslearn/data_sources/data_source.py
@abstractmethod
def retrieve_item(
    self, item: ItemType
) -> Generator[tuple[str, BinaryIO], None, None]:
    """Retrieves the rasters corresponding to an item as file streams."""
    raise NotImplementedError

DirectMaterializeDataSource

Bases: DataSource[ItemType], TileStore, Generic[ItemType]

Base class for data sources that support direct materialization via TileStore.

This class provides common TileStore functionality for data sources that can read raster data on-demand from remote sources (like cloud buckets or APIs) without first ingesting into a local tile store.

We assume here that the same asset across items has the same nodata value. The nodata value is cached so we don't need to probe the raster repeatedly.

Subclasses must implement
  • get_asset_url(): Get the URL for an asset given item and bands
Subclasses may optionally override
  • get_raster_bands(): By default, we assume that items have all assets. If items may have a subset of assets, override get_raster_bands to return the sets of bands available for that item.
  • get_read_callback(): Returns a callback to transform the raster array, for post-processing like Sentinel-2 harmonization.
Source code in rslearn/data_sources/direct_materialize_data_source.py
class DirectMaterializeDataSource(DataSource[ItemType], TileStore, Generic[ItemType]):
    """Base class for data sources that support direct materialization via TileStore.

    This class provides common TileStore functionality for data sources that can read
    raster data on-demand from remote sources (like cloud buckets or APIs) without
    first ingesting into a local tile store.

    We assume here that the same asset across items has the same nodata value. The
    nodata value is cached so we don't need to probe the raster repeatedly.

    Subclasses must implement:
        - get_asset_url(): Get the URL for an asset given item and bands

    Subclasses may optionally override:
        - get_raster_bands(): By default, we assume that items have all assets. If
            items may have a subset of assets, override get_raster_bands to return
            the sets of bands available for that item.
        - get_read_callback(): Returns a callback to transform the raster array,
            for post-processing like Sentinel-2 harmonization.
    """

    def __init__(self, asset_bands: dict[str, list[str]]):
        """Initialize the DirectMaterializeDataSource.

        Args:
            asset_bands: mapping from asset key to the list of band names in that asset.
        """
        self.asset_bands = asset_bands
        self._nodata_cache: dict[str, float | None] = {}

    def _get_asset_key_by_bands(self, bands: list[str]) -> str:
        """Get the asset key based on the band names.

        Args:
            bands: list of band names to look up.

        Returns:
            the asset key that provides those bands.

        Raises:
            ValueError: if no asset provides those bands.
        """
        for asset_key, asset_bands in self.asset_bands.items():
            if bands == asset_bands:
                return asset_key
        raise ValueError(f"no known asset with bands {bands}")

    # --- Methods that subclasses must implement ---

    @abstractmethod
    def get_asset_url(self, item: ItemType, asset_key: str) -> str:
        """Get the URL to read the asset for the given item and asset key.

        Args:
            item: the item.
            asset_key: the key identifying which asset to get.

        Returns:
            the URL to read the asset from (must be readable by rasterio).
        """
        raise NotImplementedError

    # --- Optional hooks for subclasses ---

    def get_read_callback(
        self, item: ItemType, asset_key: str
    ) -> Callable[[npt.NDArray[Any]], npt.NDArray[Any]] | None:
        """Return a callback to post-process raster data (e.g., harmonization).

        Subclasses can override this to apply transformations to the raw raster data
        after reading, such as harmonization for Sentinel-2 data.

        Args:
            item: the item being read.
            asset_key: the key identifying which asset is being read.

        Returns:
            A callback function that takes an array and returns a modified array,
            or None if no post-processing is needed.
        """
        return None

    # --- TileStore implementation ---

    @override
    def is_raster_ready(self, layer_name: str, item: Item, bands: list[str]) -> bool:
        """Checks if this raster has been written to the store.

        For remote-backed tile stores, this always returns True since data is
        read on-demand from the remote source.

        Args:
            layer_name: the layer name or alias.
            item: the item.
            bands: the list of bands identifying which specific raster to read.

        Returns:
            True, since data is always available from the remote source.
        """
        return True

    @override
    def get_raster_bands(self, layer_name: str, item: Item) -> list[list[str]]:
        """Get the sets of bands that have been stored for the specified item.

        By default, returns all band sets from the asset_bands configuration.
        Subclasses can override this if not all items have all assets.

        Args:
            layer_name: the layer name or alias.
            item: the item.

        Returns:
            a list of lists of bands available for this item.
        """
        return list(self.asset_bands.values())

    @override
    def get_raster_bounds(
        self, layer_name: str, item: Item, bands: list[str], projection: Projection
    ) -> PixelBounds:
        geom = item.geometry.to_projection(projection)
        return (
            int(geom.shp.bounds[0]),
            int(geom.shp.bounds[1]),
            int(geom.shp.bounds[2]),
            int(geom.shp.bounds[3]),
        )

    def _read_raster_from_url(
        self,
        url: str,
        projection: Projection,
        bounds: PixelBounds,
        resampling: Resampling,
    ) -> tuple[npt.NDArray[Any], float | None]:
        """Read raster data from a URL with reprojection.

        This is the common logic for reading raster data from a URL and reprojecting
        it to the target projection and bounds using rasterio's WarpedVRT.

        Args:
            url: the URL to read from (must be readable by rasterio).
            projection: the projection to read in.
            bounds: the bounds to read.
            resampling: the resampling method to use.

        Returns:
            A tuple of (raster array, nodata value or None).
        """
        wanted_transform = affine.Affine(
            projection.x_resolution,
            0,
            bounds[0] * projection.x_resolution,
            0,
            projection.y_resolution,
            bounds[1] * projection.y_resolution,
        )

        with rasterio.open(url) as src:
            src_nodata = src.nodata
            with rasterio.vrt.WarpedVRT(
                src,
                crs=projection.crs,
                transform=wanted_transform,
                width=bounds[2] - bounds[0],
                height=bounds[3] - bounds[1],
                resampling=resampling,
            ) as vrt:
                return vrt.read(), src_nodata

    @override
    def get_raster_metadata(
        self, layer_name: str, item: Item, bands: list[str]
    ) -> RasterMetadata:
        """Get metadata for a stored raster without reading pixel data.

        Opens the remote file to read the nodata value from the header and
        caches the result per asset key.
        """
        typed_item = cast(ItemType, item)
        asset_key = self._get_asset_key_by_bands(bands)

        if asset_key not in self._nodata_cache:
            asset_url = self.get_asset_url(typed_item, asset_key)
            with rasterio.open(asset_url) as src:
                self._nodata_cache[asset_key] = src.nodata

        nodata = self._nodata_cache[asset_key]
        if nodata is not None:
            return RasterMetadata(nodata_value=nodata)
        return RasterMetadata()

    @override
    def read_raster(
        self,
        layer_name: str,
        item: Item,
        bands: list[str],
        projection: Projection,
        bounds: PixelBounds,
        resampling: Resampling = Resampling.bilinear,
    ) -> RasterArray:
        # Cast to ItemType -- callers must always pass the correct subtype
        # (deserialized via the data source's deserialize_item).
        typed_item = cast(ItemType, item)

        # Get the asset key for the requested bands
        asset_key = self._get_asset_key_by_bands(bands)

        # Get the asset URL from the subclass
        asset_url = self.get_asset_url(typed_item, asset_key)

        # Read the raster data
        raw_data, src_nodata = self._read_raster_from_url(
            asset_url, projection, bounds, resampling
        )

        # Cache nodata for get_raster_metadata.
        if asset_key not in self._nodata_cache:
            self._nodata_cache[asset_key] = src_nodata

        # Apply any post-processing callback
        callback = self.get_read_callback(typed_item, asset_key)
        if callback is not None:
            raw_data = callback(raw_data)

        raster_metadata = None
        if src_nodata is not None:
            raster_metadata = RasterMetadata(nodata_value=src_nodata)

        return RasterArray(
            chw_array=raw_data,
            time_range=item.geometry.time_range,
            metadata=raster_metadata,
        )

    def materialize(
        self,
        window: Window,
        item_groups: list[list[ItemType]],
        layer_name: str,
        layer_cfg: LayerConfig,
        group_time_ranges: list[tuple[datetime, datetime] | None] | None = None,
    ) -> None:
        """Materialize data for the window.

        Args:
            window: the window to materialize.
            item_groups: the items from get_items.
            layer_name: the name of this layer.
            layer_cfg: the config of this layer.
            group_time_ranges: optional request time range for each item group.
        """
        RasterMaterializer().materialize(
            TileStoreWithLayer(self, layer_name),
            window,
            layer_name,
            layer_cfg,
            item_groups,
            group_time_ranges=group_time_ranges,
        )

    # --- TileStore methods that are not supported ---

    @override
    def write_raster(
        self,
        layer_name: str,
        item: Item,
        bands: list[str],
        projection: Projection,
        bounds: PixelBounds,
        raster: RasterArray,
    ) -> None:
        raise NotImplementedError(
            "DirectMaterializeDataSource does not support writing raster data"
        )

    @override
    def write_raster_file(
        self,
        layer_name: str,
        item: Item,
        bands: list[str],
        fname: UPath,
        time_range: tuple[datetime, datetime] | None = None,
    ) -> None:
        raise NotImplementedError(
            "DirectMaterializeDataSource does not support writing raster files"
        )

    @override
    def is_vector_ready(self, layer_name: str, item: Item) -> bool:
        raise NotImplementedError(
            "DirectMaterializeDataSource does not support vector operations"
        )

    @override
    def read_vector(
        self,
        layer_name: str,
        item: Item,
        projection: Projection,
        bounds: PixelBounds,
    ) -> list[Feature]:
        raise NotImplementedError(
            "DirectMaterializeDataSource does not support vector operations"
        )

    @override
    def write_vector(
        self, layer_name: str, item: Item, features: list[Feature]
    ) -> None:
        raise NotImplementedError(
            "DirectMaterializeDataSource does not support vector operations"
        )

get_asset_url abstractmethod

get_asset_url(item: ItemType, asset_key: str) -> str

Get the URL to read the asset for the given item and asset key.

Parameters:

Name Type Description Default
item ItemType

the item.

required
asset_key str

the key identifying which asset to get.

required

Returns:

Type Description
str

the URL to read the asset from (must be readable by rasterio).

Source code in rslearn/data_sources/direct_materialize_data_source.py
@abstractmethod
def get_asset_url(self, item: ItemType, asset_key: str) -> str:
    """Get the URL to read the asset for the given item and asset key.

    Args:
        item: the item.
        asset_key: the key identifying which asset to get.

    Returns:
        the URL to read the asset from (must be readable by rasterio).
    """
    raise NotImplementedError

get_read_callback

get_read_callback(item: ItemType, asset_key: str) -> Callable[[NDArray[Any]], NDArray[Any]] | None

Return a callback to post-process raster data (e.g., harmonization).

Subclasses can override this to apply transformations to the raw raster data after reading, such as harmonization for Sentinel-2 data.

Parameters:

Name Type Description Default
item ItemType

the item being read.

required
asset_key str

the key identifying which asset is being read.

required

Returns:

Type Description
Callable[[NDArray[Any]], NDArray[Any]] | None

A callback function that takes an array and returns a modified array,

Callable[[NDArray[Any]], NDArray[Any]] | None

or None if no post-processing is needed.

Source code in rslearn/data_sources/direct_materialize_data_source.py
def get_read_callback(
    self, item: ItemType, asset_key: str
) -> Callable[[npt.NDArray[Any]], npt.NDArray[Any]] | None:
    """Return a callback to post-process raster data (e.g., harmonization).

    Subclasses can override this to apply transformations to the raw raster data
    after reading, such as harmonization for Sentinel-2 data.

    Args:
        item: the item being read.
        asset_key: the key identifying which asset is being read.

    Returns:
        A callback function that takes an array and returns a modified array,
        or None if no post-processing is needed.
    """
    return None

is_raster_ready

is_raster_ready(layer_name: str, item: Item, bands: list[str]) -> bool

Checks if this raster has been written to the store.

For remote-backed tile stores, this always returns True since data is read on-demand from the remote source.

Parameters:

Name Type Description Default
layer_name str

the layer name or alias.

required
item Item

the item.

required
bands list[str]

the list of bands identifying which specific raster to read.

required

Returns:

Type Description
bool

True, since data is always available from the remote source.

Source code in rslearn/data_sources/direct_materialize_data_source.py
@override
def is_raster_ready(self, layer_name: str, item: Item, bands: list[str]) -> bool:
    """Checks if this raster has been written to the store.

    For remote-backed tile stores, this always returns True since data is
    read on-demand from the remote source.

    Args:
        layer_name: the layer name or alias.
        item: the item.
        bands: the list of bands identifying which specific raster to read.

    Returns:
        True, since data is always available from the remote source.
    """
    return True

get_raster_bands

get_raster_bands(layer_name: str, item: Item) -> list[list[str]]

Get the sets of bands that have been stored for the specified item.

By default, returns all band sets from the asset_bands configuration. Subclasses can override this if not all items have all assets.

Parameters:

Name Type Description Default
layer_name str

the layer name or alias.

required
item Item

the item.

required

Returns:

Type Description
list[list[str]]

a list of lists of bands available for this item.

Source code in rslearn/data_sources/direct_materialize_data_source.py
@override
def get_raster_bands(self, layer_name: str, item: Item) -> list[list[str]]:
    """Get the sets of bands that have been stored for the specified item.

    By default, returns all band sets from the asset_bands configuration.
    Subclasses can override this if not all items have all assets.

    Args:
        layer_name: the layer name or alias.
        item: the item.

    Returns:
        a list of lists of bands available for this item.
    """
    return list(self.asset_bands.values())

get_raster_metadata

get_raster_metadata(layer_name: str, item: Item, bands: list[str]) -> RasterMetadata

Get metadata for a stored raster without reading pixel data.

Opens the remote file to read the nodata value from the header and caches the result per asset key.

Source code in rslearn/data_sources/direct_materialize_data_source.py
@override
def get_raster_metadata(
    self, layer_name: str, item: Item, bands: list[str]
) -> RasterMetadata:
    """Get metadata for a stored raster without reading pixel data.

    Opens the remote file to read the nodata value from the header and
    caches the result per asset key.
    """
    typed_item = cast(ItemType, item)
    asset_key = self._get_asset_key_by_bands(bands)

    if asset_key not in self._nodata_cache:
        asset_url = self.get_asset_url(typed_item, asset_key)
        with rasterio.open(asset_url) as src:
            self._nodata_cache[asset_key] = src.nodata

    nodata = self._nodata_cache[asset_key]
    if nodata is not None:
        return RasterMetadata(nodata_value=nodata)
    return RasterMetadata()

materialize

materialize(window: Window, item_groups: list[list[ItemType]], layer_name: str, layer_cfg: LayerConfig, group_time_ranges: list[tuple[datetime, datetime] | None] | None = None) -> None

Materialize data for the window.

Parameters:

Name Type Description Default
window Window

the window to materialize.

required
item_groups list[list[ItemType]]

the items from get_items.

required
layer_name str

the name of this layer.

required
layer_cfg LayerConfig

the config of this layer.

required
group_time_ranges list[tuple[datetime, datetime] | None] | None

optional request time range for each item group.

None
Source code in rslearn/data_sources/direct_materialize_data_source.py
def materialize(
    self,
    window: Window,
    item_groups: list[list[ItemType]],
    layer_name: str,
    layer_cfg: LayerConfig,
    group_time_ranges: list[tuple[datetime, datetime] | None] | None = None,
) -> None:
    """Materialize data for the window.

    Args:
        window: the window to materialize.
        item_groups: the items from get_items.
        layer_name: the name of this layer.
        layer_cfg: the config of this layer.
        group_time_ranges: optional request time range for each item group.
    """
    RasterMaterializer().materialize(
        TileStoreWithLayer(self, layer_name),
        window,
        layer_name,
        layer_cfg,
        item_groups,
        group_time_ranges=group_time_ranges,
    )