EUMETSAT Data Tailor: tailor= (config + eligibility, no network)¶
EUMETSAT Data Tailor customises products server-side — subset (ROI crop), reproject, and reformat — before delivery. earthlens reaches it through a dedicated tailor=TailorConfig(...) argument to EUMETSAT.download.
This notebook teaches the request shape and eligibility model entirely offline: how to build a TailorConfig, how its bbox becomes a Data Tailor region-of-interest, and which catalog collections are Data-Tailor-eligible. The live submit → poll → stream → delete round-trip needs Data Store download entitlement and is only described here (last section).
API: earthlens.eumetsat.TailorConfig, earthlens.eumetsat.Catalog.
Setup¶
Import TailorConfig and Catalog from the EUMETSAT backend, plus pandas / matplotlib for the tables and chart. No credentials or network are needed — both ship as package data / pure value objects.
from collections import Counter
import matplotlib.pyplot as plt
import pandas as pd
from earthlens.eumetsat import Catalog, TailorConfig
Quickstart — a TailorConfig¶
A TailorConfig is a small frozen value object describing the customisation: the output format, the target crs / projection, an optional bbox crop, an optional band filter, and a quicklook flag. The Data Tailor product-type is not set here — it comes from the catalog row (next sections).
cfg = TailorConfig(format="geotiff", crs="geographic", bbox=(4.0, 48.0, 8.0, 52.0))
cfg
TailorConfig(format='geotiff', crs='geographic', bbox=(4.0, 48.0, 8.0, 52.0), filter=None, quicklook=False)
The fields map onto the eumdac Chain the backend builds:
| Field | Meaning | Maps to |
|---|---|---|
format |
output format ("geotiff", "netcdf4", …) |
Chain.format |
crs |
target projection | Chain.projection |
bbox |
(west, south, east, north) crop |
Chain.roi (NSWE) |
filter |
band / layer names to keep | Chain.filter |
quicklook |
request a quicklook rendering | Chain.quicklook |
ROI mapping — bbox → NSWE¶
Data Tailor's region-of-interest is an [north, south, west, east] list. TailorConfig stores bbox in the GeoJSON/OGC order (west, south, east, north) and exposes the reordered ROI via .nswe. When no bbox is given, .nswe is None and the backend falls back to the request's own lat_lim / lon_lim.
examples = [
("Central Europe", (4.0, 48.0, 8.0, 52.0)),
("British Isles", (-11.0, 49.0, 2.0, 61.0)),
("no bbox (falls back to lat/lon_lim)", None),
]
pd.DataFrame(
[(name, bbox, TailorConfig(bbox=bbox).nswe) for name, bbox in examples],
columns=["region", "bbox (w, s, e, n)", ".nswe [n, s, w, e]"],
)
| region | bbox (w, s, e, n) | .nswe [n, s, w, e] | |
|---|---|---|---|
| 0 | Central Europe | (4.0, 48.0, 8.0, 52.0) | [52.0, 48.0, 4.0, 8.0] |
| 1 | British Isles | (-11.0, 49.0, 2.0, 61.0) | [61.0, 49.0, -11.0, 2.0] |
| 2 | no bbox (falls back to lat/lon_lim) | None | None |
Validation — bad boxes are rejected early¶
TailorConfig validates bbox at construction: an inverted box (west > east or south > north) or an out-of-range coordinate raises a ValidationError at the call site, rather than failing far away at the live service.
TailorConfig validates bbox at construction: an inverted box (west > east or south > north) or an out-of-range coordinate raises a pydantic.ValidationError at the call site — e.g. TailorConfig(bbox=(8, 48, 4, 52)) fails with "bbox west (8) must be <= east (4)" — rather than failing far away at the live service.
Eligibility — not every collection is tailorable¶
Data Tailor supports a fixed registry of product types. A curated catalog row is Data-Tailor-eligible only when it carries a tailor_product_type. Load the catalog and split the collections into eligible vs not.
cat = Catalog()
eligible = {k: c for k, c in cat.datasets.items() if c.tailor_product_type}
print(f"{len(eligible)} of {len(cat.datasets)} curated collections are Data-Tailor-eligible")
83 of 180 curated collections are Data-Tailor-eligible
Eligible collections by group¶
Tally the eligible collections by mission-family group and plot it, so the spread across MSG / MTG / Metop / Sentinel-3 / … is visible at a glance.
by_group = Counter(c.group.value for c in eligible.values())
series = pd.Series(dict(sorted(by_group.items()))).sort_values()
ax = series.plot.barh(color="steelblue")
ax.set_xlabel("eligible collections")
ax.set_title("Data-Tailor-eligible EUMETSAT collections by group")
plt.tight_layout()
plt.show()
A sample of eligible rows and their product type¶
Each eligible row maps its friendly key to a Data Tailor tailor_product_type (the id passed as Chain.product). Show a representative sample.
sample = sorted(eligible.items())[:12]
pd.DataFrame(
[(k, c.group.value, c.output_kind, c.tailor_product_type) for k, c in sample],
columns=["key", "group", "output_kind", "tailor_product_type"],
)
| key | group | output_kind | tailor_product_type | |
|---|---|---|---|---|
| 0 | metop-amsua-l1 | Metop | raster | AMSAL1 |
| 1 | metop-ascat-1-sigma0-resampled-25-km | Metop | raster | ASCATL1SZO |
| 2 | metop-ascat-1-szf-climate-record-release | Metop | raster | ASCATL1SZF |
| 3 | metop-ascat-1-szo-climate-record-release | Metop | raster | ASCATL1SZO |
| 4 | metop-ascat-1-szr-climate-record-release | Metop | raster | ASCATL1SZR |
| 5 | metop-ascat-coastal-winds-12-5-km | Metop | vector | OASWC12 |
| 6 | metop-ascat-l1-szf | Metop | raster | ASCATL1SZF |
| 7 | metop-ascat-l1-szr | Metop | raster | ASCATL1SZR |
| 8 | metop-ascat-l2-12-5-km-winds | Metop | vector | OR1ASWC12 |
| 9 | metop-ascat-l2-25-km-winds-record | Metop | vector | OR1ASW025 |
| 10 | metop-ascat-soil-moisture-12km | Metop | raster | ASCATL2SMR |
| 11 | metop-ascat-soil-moisture-25km | Metop | raster | ASCATL2SMO |
A tailor= request against a non-eligible collection (e.g. Sentinel-5P TROPOMI, which this service does not tailor) raises a clear ValueError before any job is submitted — confirm one is None:
print("s5p-l2-no2 tailor_product_type:", cat.get_dataset("s5p-l2-no2").tailor_product_type)
s5p-l2-no2 tailor_product_type: None
The live round-trip (requires download entitlement)¶
With valid credentials and Data Store download entitlement for the collection, a tailor= download submits one customisation per matching product, polls it to DONE, streams the customised output(s) to path, and deletes the customisation (quota hygiene). It returns the customised GeoTIFF/NetCDF paths, which pyramids opens like any raster:
from earthlens.earthlens import EarthLens
from earthlens.eumetsat import TailorConfig
el = EarthLens(
data_source="eumetsat",
start="2024-06-01", end="2024-06-01",
variables={"s3-olci-l1-efr": ["OLL1EFR"]},
lat_lim=[50.0, 52.0], lon_lim=[-1.0, 1.0],
path="eumetsat_tailor_out",
)
paths = el.download(tailor=TailorConfig(format="geotiff", crs="geographic", bbox=(-1.0, 50.0, 1.0, 52.0)))
This cell is intentionally not executed here: it needs an account authorised to download from the Data Store (accept the collection licence in the EUMETSAT portal). Everything above runs offline.
Takeaway¶
TailorConfig(format=, crs=, bbox=, filter=, quicklook=)is the spatial customisation request;bboxis(west, south, east, north)and becomes the[n, s, w, e]ROI via.nswe.- Only catalog rows with a
tailor_product_typeare Data-Tailor-eligible; a non-eligible request fails fast with a clear error. tailor=is spatial and distinct from the temporalaggregate=reducer. See the Data Tailor reference.