Building collocated datasets#

Once you have a Matchup, download the underlying products and read them into a collocated dataset with one call. Products are downloaded if they are not already on disk; the download location and API credentials come from the EOMatchContext.

from eomatch import EOMatchContext
from eomatch.finder.sat2sat import Sat2SatMUFinder
In [1]: In [1]: events = Sat2SatMUFinder(context=EOMatchContext("my_config.yaml")).finder()

In [2]: In [2]: mu = events[0].matchup_set[0]

In [3]: In [3]: dt = mu.return_matchup_dataset()

In [4]: In [4]: dt

In [5]: Out[4]:
   ...: <xarray.DataTree>
   ...: Group: /
   ...: │   Attributes: (date_created, history, ...)
   ...: ├── Group: /LANDSAT_C2L1
   ...: │       Dimensions: (y: 412, x: 398)
   ...: └── Group: /S2_MSI_L1C
   ...:         Dimensions: (y: 1203, x: 1189)
   ...: 

The result is an xarray.DataTree with one node per product collection, named after its collection ID. If the same collection occurs more than once, a numeric suffix is added (S2_MSI_L1C, S2_MSI_L1C_2, …). Each node holds the data eoio read for that product, subset to the collocation region. Several products from the same platform and collection are read together into a single node, concatenated along product_datetime.

Controlling what is read#

return_matchup_dataset() accepts an optional collection_read_args argument that overrides what eoio reads on a per-collection basis. Because each collection uses different variable names (e.g. B02 in Sentinel-2, B2 in Landsat), overrides are always keyed by STAC collection ID.

Per-collection defaults can be set in the config under the read key (see Configuring read defaults), so you rarely need to pass collection_read_args explicitly. When you do, each collection entry may contain any of:

Key

Behaviour

Notes

vars_sel

Full replacement

Replaces the config-resolved value entirely for that collection. Use this to control exactly which variables are loaded into memory.

read_params

Sub-key merge

Merged on top of the config-resolved value. Only the keys you supply are overridden; others keep their config (or default) values.

processors

Full replacement

Replaces the config-resolved value entirely for that collection. Only the processors you name here will run.

Select specific bands per collection:

dt = mu.return_matchup_dataset(
    collection_read_args={
        "S2_MSI_L1C":   {"vars_sel": {"meas": ["B02", "B03", "B04", "B08"]}},
        "LANDSAT_C2L1": {"vars_sel": {"meas": ["B2",  "B3",  "B4",  "B5" ]}},
    }
)

Apply processors to one collection only:

dt = mu.return_matchup_dataset(
    collection_read_args={
        "S2_MSI_L1C": {"processors": {"toa_reflectance": {}}},
    }
)

Nudge a read parameter for one collection — read_params within a collection entry merges at the sub-key level:

dt = mu.return_matchup_dataset(
    collection_read_args={
        "LANDSAT_C2L1": {"read_params": {"use_chunks": True}},
    }
)

Configuring read defaults#

The read section of your config file sets per-collection defaults so you do not have to repeat them at every call site. Global defaults apply to all collections; per-collection entries are merged on top.

read:
  defaults:
    vars_sel:
      meas: []   # empty list = read all available measurement variables
      aux: []
    read_params:
      use_chunks: false
      metadata_level: true
      save_extracted: false
    processors: {}

  collections:
    LANDSAT_C2L1:
      vars_sel:
        meas: [B2, B3, B4, B5]
    S2_MSI_L1C:
      vars_sel:
        meas: [B02, B03, B04, B8A]

The merge order (lowest → highest priority) is:

  1. Hardcoded fallbacks (meas: [], aux: [], etc.)

  2. read.defaults from the config

  3. read.collections.<collection_id> from the config

  4. Call-time arguments passed to return_matchup_dataset()

Cropping to the shared valid-data extent#

Products’ native grids commonly only partially overlap and/or carry NaN padding well beyond their actual footprint (e.g. a diagonal swath inside a larger rectangular tile) — the roi subsetting above already limits what gets read to the collocation region, but that region can still be mostly padding. Every gridded product is therefore additionally cropped to the lat/lon bounding box where all of them have valid measurement data, once read — on by default. Pass crop_to_shared_valid_extent=False to disable it:

dt = mu.return_matchup_dataset(crop_to_shared_valid_extent=False)

Or set read.crop_to_shared_valid_extent: false in the config (see Configuring read defaults) to disable it for every call unless overridden here. Deriving lat/lon for a product that only has a projected grid + CRS (not lat/lon directly) requires the optional spatial extra (pip install eomatch[spatial]); without it, such a product is left uncropped rather than this setting having no effect at all.

Building datasets in bulk#

To build datasets for all matchups found in a run, iterate over the events:

for event in events:
    for mu in event.matchup_set:
        ds = mu.return_matchup_dataset()
        # process or save ds ...