Raster and coordinate conventions¶
Two different standards apply to two different things, and confusing them is how rasters end up transposed or mislocated. This page settles which applies where, and what the framework guarantees so that plugin authors do not have to.
The two orders, and why they don't conflict¶
| Concern | Order | Standard |
|---|---|---|
| Raster array / data cube dimensions | (…, y, x) — x last |
NumPy row-major, CF T,Z,Y,X, GeoZarr, GDAL |
| GeoJSON geometry coordinate pairs | [x, y] = [lon, lat] |
RFC 7946 |
| Bounding boxes | [xmin, ymin, xmax, ymax] |
RFC 7946 ([west, south, east, north]) |
These are not in tension — they describe different objects. (…, y, x) is the shape of an
N-dimensional array, where the row index comes before the column index because that is how
row-major memory is laid out. [x, y] is a coordinate tuple, where longitude comes first
because RFC 7946 says so. A single codebase uses both, in their own places.
So: array dimensions are (…, y, x); coordinate variables are named x / y; GeoJSON
pairs and bboxes are [x, y]-ordered.
openEO, which the processing layer is built on, is deliberately neutral here — it addresses
dimensions by name (reduce_dimension(dimension="t")) and spatial extents by field
(west/east/south/north), so the question never arises at the API level. But the
reference implementation runs on xarray, which is NumPy/CF-ordered, so every cube flowing
through the engine is physically (…, y, x) regardless.
What the framework guarantees¶
These are properties of a published store, not of any one source, so they are enforced once
at the write boundary — see shared/raster_contract.py.
A plugin may return data in whatever orientation and naming its source delivers.
- The temporal dimension is named
t. Sources spelling ittime,valid_time,dateortime_counterare renamed. The map viewer looks fort; a store that shipstimerenders with no time control. - Spatial dimensions are named
y/xand ordered(…, y, x). Variables arriving in another order are transposed. ydescends — array row 0 is the northernmost row. South-up sources are reversed, coordinate and data together, so the value at a given latitude does not move; only its row index does. See below for why this is guaranteed rather than left to the reader.- A geographic x axis runs −180…180. Sources on the 0…360 frame (ERA5 among them) are rolled and re-sorted. Projected eastings are left alone — one legitimately exceeds 180.
- The declared CRS matches the coordinates it describes. Data is stored in its native
CRS, whatever the source delivered; nothing is reprojected on ingest. The invariant is
consistency, not a fixed CRS. The instance-wide
crs:setting is never used as a fallback for an untagged cube — doing so stampedEPSG:32633onto degree coordinates and put the store at the projection's origin instead of on the map.
What this means if you write a plugin¶
Nothing. Return data in whatever orientation, dimension naming and longitude frame your source
delivers, and declare a crs class attribute only if the source carries no CRS of its own. The
framework normalises the rest at the write boundary; a plugin that hand-rolls a y-flip or a
longitude roll is doing work that will simply be re-done.