Rotated Bottom-Up DEM (Skew and Positive Y Resolution)¶
A DEM carrying both transform conventions at once: a rotated affine with non-zero b/d skew terms AND a positive e, so the origin is the bottom-left corner and row 0 is the southernmost row. The catalog previously held one rotated raster and one bottom-up raster and no case combining them, so a consumer could handle either convention alone and appear correct.
| Property | Value |
|---|---|
| Case ID | rotated_bottom_up_small |
| Category | raster |
| Format | GeoTIFF |
| CRS | EPSG:32633 |
| Location | Central Mediterranean (synthetic, UTM 33N) — 15.00°E, 37.95°N → 15.01°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("rotated_bottom_up_small")
def test_rotated_bottom_up_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 rotation and row order from the same affine, rather than handling one convention and silently normalising the other. rio-tiler's rotated-affine and bottom-up defects have adjacent root causes and a single WarpedVRT guard addresses both; this case is what distinguishes a complete fix from a partial one.
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, rotated |
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, 500456.0, 4200456.0] |
| Pixel to world (row, col, x, y) | [[0.0, 0.0, 500019.0, 4200019.0], [11.0, 11.0, 500437.0, 4200437.0], [6.0, 6.0, 500247.0, 4200247.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 > 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¶
loadaffine-transform-checkbounds-checktransform-inspection
Files¶
- Primary:
rotated_bottom_up_small.tif - Notes:
notes.md
Source and license¶
- Source: geocase-curated
- License: MIT
Tags¶
georeferencing geotiff raster rotated south-up transform
Related cases¶
- Bottom-Up DEM (Positive Y Resolution) --
bottom_up_dem_small - Bottom-Up Square (Positive E and Nothing Else) --
bottom_up_only_square - Rotated Raster with Non-Square Pixels --
rotated_nonsquare_small - Rotated Square (Rotation and Nothing Else) --
rotated_only_square - Steeply Rotated DEM (40-Degree Skew) --
rotated_steep_small