Extractor¶
Extractor extracts environmental values at point locations (CSV / DataFrame) or
spatial geometries (SHP / GeoDataFrame). By default it reads from the h2ds Zarr stores
via ZarrCatalog, but it can also extract against an arbitrary in-memory xarray dataset
that is not yet in the store (see extract_from_dataset()).
from h2mare.processing.extractor import Extractor
extractor = Extractor("data/points.csv", time_col="ls_date", index_col="idlance")
df = extractor.run("sst") # returns a DataFrame
extractor.run("sst", output_path="out.csv") # or writes a CSV and returns None
The input (points or geometries) is supplied to the constructor, not to run().
There are no start_date / end_date arguments: the extraction window is the set of
dates present in the input, intersected with each variable's store coverage.
Constructor¶
Extractor(
file_path, # path to .csv/.shp, or an in-memory DataFrame/GeoDataFrame
*,
index_col, # REQUIRED — name of a unique key column already in the data
time_col=None, # default "time"
lon_col=None, # default "lon" (CSV only)
lat_col=None, # default "lat" (CSV only)
app_config=None, # default: settings.app_config
store_root=None, # default: STORE_ROOT
crs=4326, # EPSG code for geometry extraction (SHP only)
log_file=None, # default: LOGS_DIR/extractor.log
)
| Parameter | Default | Description |
|---|---|---|
file_path |
— | A .csv / .shp path, or an in-memory pd.DataFrame (points) / gpd.GeoDataFrame (geometries). The suffix or object type selects point vs geometry mode. |
index_col |
required | Name of the unique key column used to merge results back onto your input. It must already exist in the data and be unique — the Extractor consumes the key, it never creates one. A missing or duplicated key raises ValueError. Use ensure_row_id to establish it. |
time_col |
"time" |
Name of the time column in the input. |
lon_col |
"lon" |
Longitude column name (CSV/point input only). |
lat_col |
"lat" |
Latitude column name (CSV/point input only). |
app_config |
settings.app_config |
Override the application configuration (variable registry, depth slices, etc.). |
store_root |
STORE_ROOT |
Root directory of the Zarr stores. |
crs |
4326 |
EPSG code that geometries are reprojected to (SHP/geometry input only). |
log_file |
LOGS_DIR/extractor.log |
Extraction log file. The first Extractor in the process fixes this; later values are ignored. |
Establishing the merge key (ensure_row_id)¶
The Extractor keeps only the columns it needs (time/lon/lat for points,
time/geometry for geometries) and returns one column per extracted variable. To stitch
those results back onto your original dataframe, you need a key that exists on both
sides. Because the Extractor never sees your other columns, that key must be established
before extraction, on the frame you keep — not invented inside the Extractor.
ensure_row_id is the helper for that step:
from h2mare.processing.extractor import ensure_row_id
df = ensure_row_id(df) # adds a unique "row_id" column (or validates an existing one)
col state in data |
Behaviour |
|---|---|
| present, unique | returned unchanged |
| present, duplicated | raises ValueError — a duplicated key collapses rows on merge-back |
| absent | adds a positional 0..n-1 key (on a copy) — safe to merge back only against this exact frame, in this order |
Merging results back¶
df = ensure_row_id(df) # key now lives on your frame
out = Extractor(df, index_col="row_id").run("sst") # result indexed by row_id
enriched = df.merge(out, left_on="row_id", right_index=True)
Passing a .csv/.shp path instead of a frame is still supported, but with
index_col required the file must already contain that key column — there is no
auto-provisioning fallback. For files, read them in, call ensure_row_id, and pass the
frame.
run()¶
extractor.run(
var_dict=None, # which var_keys / variables to extract; None = all
output_path=None, # None → return DataFrame; path → write CSV, return None
n_workers=8, # parallel workers for geometry (SHP) extraction only
)
| Parameter | Description |
|---|---|
var_dict |
Selects what to extract. A str (single var_key), a list[str] (several var_keys), or a dict[var_key, vars] to pick specific variables inside a var_key (e.g. {"radiation": ["tisr", "ssrd"]}). None extracts every var_key in config (excluding compiled h2mare-source outputs). |
output_path |
If None, run() returns the result DataFrame. If a path is given, the result is written to CSV and run() returns None. |
n_workers |
Number of ThreadPoolExecutor workers. Only used for geometry (SHP) extraction; point (CSV) extraction is vectorized and ignores it. |
var_dict = {"seapodym": [], "radiation": ["tisr", "ssrd", "slhf"]}
extractor = Extractor("input.csv", time_col="ls_date", index_col="idlance")
results = extractor.run(var_dict, output_path="out.csv", n_workers=12)
Extraction is checkpointed per var_key (INTERIM_DIR/extraction_checkpoint.feather), so
an interrupted run resumes where it stopped.
extract_from_dataset()¶
Extract against an arbitrary in-memory xarray object instead of the store — useful for
new data that has not been ingested yet. It runs the same engine as run() but bypasses
ZarrCatalog, reusing the points/geometries already prepared in the constructor.
extractor.extract_from_dataset(
ds, # xr.Dataset | xr.DataArray with coords lon, lat, [time]
*,
vars=None, # subset of data_vars (xr.Dataset only)
n_workers=8, # geometry (SHP) extraction only
clip_to_coverage=False, # drop input rows outside the ds extent → NaN
) -> pd.DataFrame
| Parameter | Description |
|---|---|
ds |
Gridded data with coords named lon, lat, and optionally time. For geometry input the dataset is assumed to be in crs — its CRS is overwritten (not reprojected) to match the geometries. |
vars |
Subset of variables to extract. Only valid when ds is an xr.Dataset; passing it with a DataArray raises TypeError. |
n_workers |
Parallel workers for geometry (SHP) extraction only. |
clip_to_coverage |
When True, input rows whose location (and time, if ds has a time coord) fall outside the ds extent are dropped and surface as NaN in the result. Default False, since nearest-neighbour (CSV) and clip-or-NaN (SHP) already handle out-of-extent inputs. |
Only config-free preparation is applied. Config-driven steps that the store path performs
— depth-slice expansion, rename_lonlat, and store date/bbox coverage resolution — are
the caller's responsibility: prepare ds beforehand.
import numpy as np, pandas as pd, xarray as xr
ds = xr.Dataset(
{"sst": (("time", "lat", "lon"), np.arange(2 * 3 * 3.0).reshape(2, 3, 3))},
coords={"time": pd.to_datetime(["2021-01-01", "2021-01-02"]),
"lat": [40, 41, 42], "lon": [-10, -9, -8]},
)
pts = pd.DataFrame({"time": ["2021-01-01", "2021-01-02"], "lon": [-10, -8], "lat": [40, 42]})
pts = ensure_row_id(pts)
out = Extractor(pts, index_col="row_id").extract_from_dataset(ds) # sst at nearest cells
Output format¶
run() produces a single tabular result, indexed by index_col:
- Returned as a
DataFramewhenoutput_pathisNone. - Written as a CSV when
output_pathis given (andrun()returnsNone). Theindex_colkey is written as a column so you can merge the result back. If the file already exists, overlapping columns are dropped from the existing file and the new columns are joined in.
Columns are the input columns carried through, plus one column per extracted variable:
| Input | Carried-through columns | Extracted columns |
|---|---|---|
| CSV / points | time, lon, lat |
one column per variable (e.g. sst, tisr); depth-sliced variables expand to var_<depth> |
| SHP / geometries | time, geometry |
one column per variable; bathy additionally yields a bathy_std column (mean / std over each geometry) |
The in-memory run() return keeps the SHP geometry column as a GeoDataFrame with live
shapely geometries (restored from the input on a checkpoint resume, since feather cannot
round-trip them). It is dropped only when writing to CSV, where shapely geometries would
serialize to WKT strings that cannot be read back as geometries.
Parallelism¶
Geometry (SHP) extraction runs each geometry through a ThreadPoolExecutor, sized by the
n_workers argument (default 8). Point (CSV) extraction is fully vectorized — it uses a
cached KDTree for the nearest grid cell and searchsorted for the nearest time — and does
not use n_workers.