Skip to content

feat: support instance-configurable CRS for GeoZarr storage#92

Merged
turban merged 7 commits intomainfrom
feat/instance-crs
May 9, 2026
Merged

feat: support instance-configurable CRS for GeoZarr storage#92
turban merged 7 commits intomainfrom
feat/instance-crs

Conversation

@turban
Copy link
Copy Markdown
Contributor

@turban turban commented May 9, 2026

Closes #90.

Summary

  • Adds crs key to climate-api.yaml (defaults to EPSG:4326, fully backwards-compatible)
  • build_dataset_zarr reads the instance CRS and writes it into GeoZarr metadata (proj:code, spatial:bbox) instead of hardcoding WGS84
  • Spatial dimensions are always stored as x/y regardless of CRS — no special-casing for WGS84
  • GET /zarr/{dataset_id} response includes three new fields: crs, proj4, and bounds, read from the store's own GeoZarr attributes (falls back to instance config) — so clients like zarr-layer can configure on-the-fly reprojection without hard-coding CRS details

How to use

For a national instance storing data in Norwegian UTM33:

# climate-api.yaml
crs: EPSG:25833

Download functions for native-CRS datasets (e.g. seNorge in UTM33) can write x/y coordinates directly — no reprojection needed before saving.

Breaking change

None. Instances without a crs key continue to use EPSG:4326 and produce identical output.

Note: all Zarr stores now use x/y as the canonical spatial dimension names (previously longitude/latitude for WGS84). If downstream clients select by dim name, they will need updating.

zarr-layer client

The new proj4 and bounds fields in GET /zarr/{dataset_id} let the client build layer props dynamically:

const info = await fetch("/zarr/senorge_temperature_daily_nor").then(r => r.json());
const layer = new ZarrLayer({
  source: `/zarr/senorge_temperature_daily_nor`,
  proj4: info.proj4,
  bounds: info.bounds,
  variable: "tg",});

Test plan

  • tests/test_config.pyget_crs(): default, explicit value, non-string, unknown EPSG code (e.g. EPSG:999999) all fail fast with clear errors
  • Full test suite passes
  • Norway instance ingests seNorge data in EPSG:25833 without reprojection step
  • GET /zarr/{dataset_id} returns crs, proj4, and bounds for a WGS84 dataset

turban added 4 commits May 9, 2026 19:06
Add `crs` key to climate-api.yaml (defaults to EPSG:4326).

- config.get_crs() reads the instance CRS; DEFAULT_CRS constant guards
  backwards compatibility for instances without an explicit crs key
- build_dataset_zarr uses the instance CRS for GeoZarr metadata and
  the xproj CRS assignment in the multiscales path
- For WGS84 instances spatial dims are normalised to longitude/latitude
  as before; for other CRS (e.g. UTM33) they are normalised to x/y to
  reflect that coordinate values are in metres, not degrees
- GET /zarr/{dataset_id} now includes crs, proj4, and bounds fields so
  zarr-layer clients can configure reprojection without hard-coding them
- climate-api.yaml.example documents the new optional key
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds instance-configurable CRS support for GeoZarr storage (via climate-api.yaml) and extends the /zarr/{dataset_id} listing response with CRS metadata so clients can configure reprojection without hard-coded CRS details.

Changes:

  • Introduces config.get_crs() with DEFAULT_CRS (EPSG:4326) and example config docs.
  • Updates Zarr building to write GeoZarr CRS metadata from instance config (instead of hardcoding WGS84) and adjusts coordinate normalisation expectations in tests/docs.
  • Extends /zarr/{dataset_id} store listing with crs, proj4, and bounds.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
climate_api/config.py Adds DEFAULT_CRS and get_crs() config accessor.
climate_api/data_manager/services/downloader.py Uses instance CRS for GeoZarr metadata and renames spatial dims during Zarr build.
climate_api/ingestions/services.py Extends /zarr listing response with CRS/proj4/bounds helpers.
climate-api.yaml.example Documents optional crs: key.
tests/test_config.py Adds tests for get_crs() default/config/invalid type.
tests/test_downloader.py Updates coordinate-normalisation expectations and pyramid coarsening dims.
docs/user_guide.md Updates examples to use x/y dimensions.
docs/adding_custom_datasets.md Updates coordinate-normalisation guidance for custom datasets.

Comment thread climate_api/data_manager/services/downloader.py
Comment thread tests/test_downloader.py
Comment thread docs/user_guide.md
Comment thread docs/adding_custom_datasets.md
Comment thread climate_api/ingestions/services.py
Comment thread climate_api/ingestions/services.py Outdated
Comment thread climate_api/config.py Outdated
Comment thread tests/test_config.py
turban added 3 commits May 9, 2026 19:48
…ust proj4 conversion

- get_crs() validates the CRS string is parseable via pyproj at config load time,
  so misconfigured instances fail fast with a clear error (not during Zarr build)
- get_dataset_zarr_store_info_or_404() reads proj:code from the store's own GeoZarr
  attributes first, falling back to the instance config CRS; avoids mismatch when
  the config CRS changes after a store was built
- _crs_to_proj4() returns str|None and catches CRS errors instead of propagating
  a 500; _read_zarr_attrs() is extracted as a shared helper for both bounds and CRS
- Add test for unknown EPSG code (EPSG:999999) to lock in the early-failure behaviour
@turban turban merged commit 82270b7 into main May 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: support national CRS (e.g. EPSG:25833) for GeoZarr storage

2 participants