Bottom-Up DEM (Positive Y Resolution)¶
A south-up GeoTIFF whose affine transform has a positive e term: the origin is the bottom-left corner and row 0 is the southernmost row. Every other raster in the catalog is north-up, so code that assumes a negative e passes the whole corpus and then flips this one.
| Property | Value |
|---|---|
| Case ID | bottom_up_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("bottom_up_dem_small")
def test_bottom_up_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:
What this case checks¶
Verify that a consumer reads row order from the transform rather than assuming north-up, and that it orders bounds explicitly instead of trusting bounds.bottom to be the smaller northing.
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 |
positive_e |
expected_pixel_anchor |
area |
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, 4200360.0, 500360.0, 4200000.0] |
Known consumer divergences¶
Disagreements already investigated on this case. If your reader reproduces one of these, it is catalogued — not a new finding.
rio-tiler — rio-tiler 9.4.3, GDAL 3.12.2
The positive-e affine is valid and rasterio reads it, but rio-tiler propagates the inverted BoundingBox (bottom > top) onto Reader.bounds, so bounds[3] - bounds[1] is negative. feature() then raises WindowError while part() over the identical area succeeds. reader.py:487 calls windows.from_bounds with no ordering normalisation, and reader.py:58 computes y_res = (bounds[3] - bounds[1]) / height with no abs().
titiler — titiler 0.24 / rio-tiler 8.x, GDAL 3.12.2
/cog/info returns bounds with miny > maxy for the bottom-up (positive e) affine, and the normalised /cog/bbox request built from those bounds then returns HTTP 500 rather than the window.
rio-stac — rio-stac 0.11, GDAL 3.12.2
create_stac_item writes proj:bbox unnormalised for the bottom-up affine, so the Item is invalid per the projection extension even though the WGS84 bbox it writes alongside is correct.
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 > 0distinguishes 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_smallcarries both conventions at once: non-zerob/dskew terms and a positivee. rio-tiler's two defects have adjacent root causes and a singleWarpedVRTguard 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 reasonbottom_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_smallrotates by roughly 40°, againstrotated_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.
Related¶
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¶
loadbounds-checktransform-inspection
Files¶
- Primary:
bottom_up_dem_small.tif - Notes:
notes.md
Source and license¶
- Source: geocase-curated
- License: MIT
Tags¶
georeferencing geotiff raster south-up transform
Related cases¶
- Pixel-Is-Area DEM --
pixel_is_area_dem_small - Pixel-Is-Point DEM --
pixel_is_point_dem_small - Bottom-Up Square (Positive E and Nothing Else) --
bottom_up_only_square - Rotated Bottom-Up DEM (Skew and Positive Y Resolution) --
rotated_bottom_up_small - DEM NaN NoData Small --
dem_nan_nodata_small