Skip to content

Pixel-Is-Point DEM

rasterGeoTIFFEPSG:32633tinybundled

Declares AREA_OR_POINT=Point, so the transform's coordinates name pixel centres rather than corners. Byte-identical to pixel_is_area_dem_small apart from that one tag, which moves every pixel half a pixel -- a georeferencing error invisible to shape, CRS and checksum checks.

Pixels of pixel_is_point_dem_small, a 12x12 raster, with NoData in magenta
1 band, 12x12 px, float32, sentinel NoData. Rendered from the case's actual pixels, contrast-stretched for display; NoData is shown in magenta.
Property Value
Case ID pixel_is_point_dem_small
Category raster
Format GeoTIFF
CRS EPSG:32633
Location Central Mediterranean (synthetic, UTM 33N) — 15.00°E, 37.95°N → 15.00°E, 37.95°N
Test tier unit
Size class tiny
Storage class bundled
Redistributable yes
Loader rasterio
Status validated

Use this case

import pytest


@pytest.mark.geocase_case("pixel_is_point_dem_small")
def test_pixel_is_point_dem_small(geocase_case) -> None:
    data = geocase_case.load()
    assert data is not None

Use GeoCase in your tests

Install the complete set of vector, raster, and NetCDF dependencies:

pip install "geocase[all]"

View GeoCase on PyPI.

What this case checks

Verify that a consumer reads AREA_OR_POINT rather than assuming one convention. Against pixel_is_area_dem_small -- identical array, identical transform -- the same pixel sits half a pixel away.

Risk types covered

Expected behavior

Assertion Expected
expect_loadable yes
expect_crs yes
expected_epsg 32633
expect_nodata yes
expected_band_count 1
expected_dtype float32
expected_shape [12, 12]
expected_nodata_value -9999.0
nodata_convention sentinel
expected_transform_signs negative_e
expected_pixel_anchor point

Known answer

Computed from the actual bytes and gated against them. Grade your own output against these.

Quantity Value
Mean over valid pixels 72.0
Mean including NoData 2.0625
NoData pixels 1
Bounds (case CRS) [500000.0, 4200000.0, 500360.0, 4200360.0]

Notes

Three fixtures covering two georeferencing conventions that nearly every consumer assumes without checking, and that nothing in this catalog exercised before plan 34. Like the footprint edge cases, they share a directory because their value is in the comparison between them.

Why these exist

Both failures here are silent. Neither produces an exception, a warning, or a visibly broken file — the output is a plausible raster describing the wrong piece of ground.

bottom_up_dem_small — positive e

Every other raster in the catalog is built with rasterio.transform.from_origin, which always emits a negative e term: north-up, origin at the top-left, row 0 the northernmost. Code that hardcodes that assumption passes all 32 of them.

This one has e = +30.0, origin at the bottom-left, and row 0 is the southernmost row. The array is the north-up ramp flipped vertically, so the file describes the same ground the other two do — get the flip wrong and you have a file that is internally consistent and geographically upside down, which is the exact bug the case exists to catch.

A concrete trap this fixture exposes: rasterio does not normalise bounds. BoundingBox is computed straight from the affine, so this file reports bottom = 4200360 and top = 4200000 — the larger northing in bottom. Anything computing a height as top - bottom, or handing these bounds to a windowing helper, gets a negative number or an empty read. Every other fixture in the catalog hides this, because for them the two happen to be ordered the way people expect.

pixel_is_area_dem_small / pixel_is_point_dem_small — AREA_OR_POINT

The pair is the test. Both files carry the same array and the same transform, and differ only in one metadata tag:

  • Area — the transform's coordinates name pixel corners.
  • Point — they name pixel centres.

That is a half-pixel difference in where every value sits: 15 m on a 30 m grid. It survives every gate this repository had — shape, dtype, CRS, nodata, checksum, even the rendered preview are all identical.

Area is written explicitly, though GDAL's default would produce the same behaviour by omitting the tag. A file that says nothing cannot be one half of a differential: a consumer reading tags()["AREA_OR_POINT"] on a silent file gets a KeyError, and one using .get() without a default gets None and guesses. assert_pixel_anchor defaults to "Area" for exactly this reason.

Typical checks

  • src.transform.e > 0 distinguishes the bottom-up case.
  • src.tags().get("AREA_OR_POINT", "Area") — with the default.
  • Order bounds explicitly rather than assuming bottom < top.

Widening the axis (plan 37 phase 3)

The external validation runs of 2026-08-31 found real defects in rio-tiler, titiler and rio-stac, and every one of them came from a transform convention rather than a format baseline. At that point the corpus held exactly one rotated raster and exactly one bottom-up raster — a sample size of one per axis, each of which paid on its first run. Two cases here widen it:

  • rotated_bottom_up_small carries both conventions at once: non-zero b/d skew terms and a positive e. rio-tiler's two defects have adjacent root causes and a single WarpedVRT guard addresses both, so a case carrying only one convention cannot distinguish a complete fix from a partial one. Its rows are flipped for the same reason bottom_up_dem_small's are: the file has to describe the same ground, not the same array. Flipping the transform sign alone yields a file that is internally consistent and geographically upside down.
  • rotated_steep_small rotates by roughly 40°, against rotated_two_islands' ~14°. At that angle a north-up assumption misplaces the grid's far corner by about ten cells rather than one, so a differential report shows the magnitude and direction of a consumer's error instead of something that reads as a one-pixel edge effect.

Both reuse the affine_transform_bug risk type rather than minting new terms.

rotated_two_islands and nonsquare_diagonal_sparse also carry non-default transforms — a rotated affine and anisotropic 60 m × 30 m pixels respectively. Both were backfilled with expected_transform_signs in plan 34, turning a property the corpus always had into one it declares.

Required capabilities

  • load
  • bounds-check
  • transform-inspection

Files

Browse this case on GitHub

Source and license

  • Source: geocase-curated
  • License: MIT

Tags

georeferencing geotiff pixel-anchor raster transform