Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 27 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,18 +147,19 @@ Where:

- `a`: pixel width (w-e pixel resolution)
- `b`: row rotation (typically 0)
- `c`: x-coordinate of the upper-left corner of the upper-left pixel
- `c`: x-coordinate of pixel (0, 0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

pixel (0, 0) is ambiguous (array element vs. continuous point). Anchor it to the formula's variables:

Suggested change
- `c`: x-coordinate of pixel (0, 0)
- `c`: x-coordinate of the point `(col_index, row_index) = (0, 0)` in coordinate space

- `d`: column rotation (typically 0)
- `e`: pixel height (n-s pixel resolution, negative value for north-up images)
- `f`: y-coordinate of the upper-left corner of the upper-left pixel
- `f`: y-coordinate of pixel (0, 0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
- `f`: y-coordinate of pixel (0, 0)
- `f`: y-coordinate of the point `(col_index, row_index) = (0, 0)` in coordinate space


**Coordinate convention:**

The transform operates on array indices where `(0, 0)` is at the **top-left corner** of the top-left pixel, and `(width, height)` is at the bottom-right corner of the bottom-right pixel. The center of the top-left pixel is at `(0.5, 0.5)`.
The meaning of pixel (0, 0) depends on `spatial:registration`:

This follows the GDAL geotransform and Python's Affine library convention.
- **`"pixel"` (default):** Pixel (0, 0) refers to the **top-left corner** of the top-left pixel. The pixel value fills the area from (0, 0) to (1, 1). An N×M image covers the bounds (0, 0) to (N, M). Coefficients `c` and `f` give the corner coordinates.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
- **`"pixel"` (default):** Pixel (0, 0) refers to the **top-left corner** of the top-left pixel. The pixel value fills the area from (0, 0) to (1, 1). An N×M image covers the bounds (0, 0) to (N, M). Coefficients `c` and `f` give the corner coordinates.
- **`"pixel"` (default):** The point `(col_index, row_index) = (0, 0)` is the **top-left corner** of the top-left pixel. The pixel value covers the index-space area from (0, 0) to (1, 1). An N×M image covers index-space bounds (0, 0) to (N, M). Coefficients `c` and `f` give the coordinates of that corner.

- **`"node"`:** Pixel (0, 0) refers to the **point location** of the top-left pixel. The pixel value is a point measurement at that location. An N×M image covers the bounds (0, 0) to (N-1, M-1). Coefficients `c` and `f` give the point coordinates. If displayed with pixel cells, the top-left corner of the display would be at (-0.5, -0.5) in index space.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Clearer to call it a node/center and state both the node extent and the footprint together:

Suggested change
- **`"node"`:** Pixel (0, 0) refers to the **point location** of the top-left pixel. The pixel value is a point measurement at that location. An N×M image covers the bounds (0, 0) to (N-1, M-1). Coefficients `c` and `f` give the point coordinates. If displayed with pixel cells, the top-left corner of the display would be at (-0.5, -0.5) in index space.
- **`"node"`:** The point `(col_index, row_index) = (0, 0)` is the **node/center** of the top-left pixel. The pixel value is a point measurement at that location. The N×M grid of nodes spans index-space (0, 0) to (N-1, M-1); the displayed cell footprints extend to (-0.5, -0.5)–(N-0.5, M-0.5). Coefficients `c` and `f` give the coordinates of that node.


Note: Rasterio's `xy()` and `rowcol()` methods automatically add/subtract 0.5 to convert between pixel coordinates and corner coordinates. For example, `transformer.xy(0, 0)` is equivalent to applying this transform to `(0.5, 0.5)`, giving the coordinate at the center of the first pixel.
This matches the [GeoTIFF raster space model](http://docs.opengeospatial.org/is/19-008r4/19-008r4.html#_raster_space), where the ModelTiePoint at (0, 0) gives the corner coordinate for PixelIsArea and the point coordinate for PixelIsPoint.

**Coefficient ordering:**

Expand Down Expand Up @@ -213,34 +214,27 @@ Grid cell registration type
- **Default**: `"pixel"`
- **Valid values**: `"node"` or `"pixel"`

Specifies whether the grid uses node registration (grid-registered) or pixel registration (cell-registered). This property is particularly important for grids where the interpretation of coordinate ranges differs between registration types.
Specifies whether pixel values represent area measurements or point measurements. This affects how `spatial:transform` and `spatial:bbox` coordinates are interpreted (see [spatial:transform coordinate convention](#spatialtransform)).

**Node Registration (grid/node):**
**Pixel Registration (`"pixel"`, default):**

Node-registered grids have cells centered on the grid-lines. The coordinate ranges (`spatial:bbox` and `spatial:transform`) refer to the centers of the cells on the outside border of the grid, and the footprints of the cells extend 1/2 cell width outside these ranges.
Pixel values represent measurements over an area. Coordinates in `spatial:transform` and `spatial:bbox` refer to cell boundaries (edges).

- Cells are centered on coordinate points
- Commonly used for discrete point data representation
- A global grid will have cells centered directly on the North and South Poles
- Has one more row and one more column than a pixel-registered grid with identical range
- An N×M image covers bounds (0, 0) to (N, M) in index space
- Commonly used for imagery and raster data
- Equivalent to GeoTIFF PixelIsArea

**Pixel Registration (cell/pixel):**
**Node Registration (`"node"`):**

Pixel-registered grids have cells lying between the grid-lines. The coordinate ranges refer to the outside edges of the boundaries of the grid.
Pixel values represent point measurements at specific locations. Coordinates in `spatial:transform` and `spatial:bbox` refer to cell centers (nodes).

- Cell boundaries align with coordinate points
- Commonly used in images to prevent edge pixels from being cut in half
- A global grid will touch the edges of the poles without covering their centers
- Each cell in one registration overlaps quadrants of four cells in the corresponding node-registration
- An N×M image covers bounds (0, 0) to (N-1, M-1) in index space

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pin down what spatial:bbox reports under node registration (this is the common reader trap):

Suggested change
- An N×M image covers bounds (0, 0) to (N-1, M-1) in index space
- An N×M image covers bounds (0, 0) to (N-1, M-1) in index space; `spatial:bbox` reports the extent of node coordinates (outermost cell *centers*), not the half-cell-padded footprint

- Cell footprints extend 1/2 cell width beyond the stated coordinate range
- A node-registered grid covering the same extent as a pixel-registered grid has one more row and column
- Commonly used for DEMs, weather station grids, and older NetCDF files
- Equivalent to GeoTIFF PixelIsPoint

**Important considerations:**

- Converting between registration types results in relief flattening, as each cell in one registration overlies corners of four cells in the other
- The conversion process averages values, reducing local highs and raising local deeps
- Most grid applications recognize both types, but misidentifying the registration can shift cell locations and data
- This property helps tools correctly interpret the relationship between array indices and spatial coordinates

When `spatial:registration` is omitted, implementations MUST assume `"pixel"` registration for backwards compatibility.
When `spatial:registration` is omitted, implementations MUST assume `"pixel"` registration.

**Relationship to other formats:**

Expand Down Expand Up @@ -514,11 +508,14 @@ Implementations should handle unknown transform types gracefully (warn or skip)

### How does spatial:registration relate to GeoTIFF PixelIsArea/PixelIsPoint?

The `spatial:registration` property directly maps to GeoTIFF's raster space concepts:

- **`spatial:registration: "pixel"`** (default) = **GeoTIFF PixelIsArea**: Pixels represent areas/cells with boundaries at coordinate points. An N×M image covers bounds (0,0) to (N,M).
The `spatial:registration` property directly maps to GeoTIFF's [raster space model](http://docs.opengeospatial.org/is/19-008r4/19-008r4.html#_raster_space):

- **`spatial:registration: "node"`** = **GeoTIFF PixelIsPoint**: Pixel values are point measurements at coordinate locations. An N×M image covers bounds (0,0) to (N-1,M-1).
| | `"pixel"` (default) | `"node"` |
|---|---|---|
| **GeoTIFF equivalent** | PixelIsArea | PixelIsPoint |
| **Pixel values represent** | Area/cell measurements | Point measurements |
| **`spatial:transform` origin (`c`, `f`)** | Corner of top-left pixel | Location of top-left pixel |
| **N×M image bounds** | (0, 0) to (N, M) | (0, 0) to (N-1, M-1) |

When converting GeoTIFF data to Zarr, use `spatial:registration: "node"` for PixelIsPoint GeoTIFFs and `spatial:registration: "pixel"` (or omit it) for PixelIsArea GeoTIFFs.

Expand Down