Skip to content

feat: Table-driven GeoKey to CRS builder - #182

Merged
kylebarron merged 5 commits into
mainfrom
kyle/crs-table-builder
Oct 26, 2026
Merged

kylebarron merged 5 commits into
mainfrom
kyle/crs-table-builder

Conversation

@kylebarron

Copy link
Copy Markdown
Member

Note

This PR was written by Claude (Claude Code), not by @kylebarron.

Stage 4 of the GeoKey-to-CRS work: the user-defined CRS path of _crs.py is now driven by the GeoTIFF CT method table shared through geotiff-test-data, built with pyproj's typed classes, and tested against GDAL's reading of every fixture in the conformance corpus. Design: dev-docs/specs/2026-10-26-geokey-crs-conformance-design.md.

What changed

  • _crs_table.py loads the vendored data/geotiff-ct-methods.json into typed records (method, variants, parameters with their GeoKey fallback lists).
  • _crs_geographic.py builds the geographic side the way libgeotiff's GTIFGetDefn and GDAL's GTIFGetOGISDefn do: angular and linear units from EPSG codes (via proj.db) or explicit sizes, the ellipsoid from its code or axes (sphere when the axes are equal), the datum and prime meridian from their codes or from GDAL's citation names (Datum = ..., Primem = ..., unknown fallback, never undefined), and coordinate systems in the file's units and axis directions.
  • _crs_conversion.py interprets the table: first-present GeoKey wins (a stored 0.0 counts), angular and linear values scaled to degrees and metres, the Mercator and Polar Stereographic variant rules, the polar axis rule, pyproj's typed conversion classes where they exist and a PROJ string otherwise, then a check that every table parameter reached the conversion with its value. A table key async-tiff cannot read (ProjRectifiedGridAngle today) raises instead of defaulting.
  • _crs.py is now dispatch only; crs_from_geo_keys and projjson_from_geo_keys keep their signatures.
  • All builders take the GeoKeys as a plain mapping, so the 70-odd unit tests use dicts.

Conformance

KNOWN_FAILURES in tests/test_crs_conformance.py drops from 34 rows to 7:

  • tmerc_towgs84, omerc_variant_a, omerc_variant_b, somerc: need GeoKeys 2062/3096, merged in feat: Expose GeogTOWGS84GeoKey and ProjRectifiedGridAngleGeoKey async-tiff#368 but not yet released.
  • lcc_1sp_paris_grad: GDAL's own write and read of a prime meridian in grads disagree (spec Q3); we follow libgeotiff's single conversion.
  • krovak, moll: ESRI PE String rows, where GDAL layers the Geog* keys and ESRI name morphing on top of the WKT and pyproj parses the WKT alone. The PE String path is unchanged in this PR.

The fresh-context review found three inputs the GDAL-written corpus never produces and that misbehaved: a coded datum without ellipsoid keys raised, an ellipsoid code with only a semi-major axis silently became a sphere, and the degree code 9102 scaled angles by a factor a hair off 1.0 (and some sexagesimal codes pyproj reports with a zero factor would have zeroed every angle). All three are fixed with tests.

uv run pytest: 511 passed, 4 skipped, 9 xfailed (the 7 above plus 2 non-strict proj.db-drift rows). Ruff and mypy clean.

Follow-ups

  • geotiff-test-data: the table's Laborde (CT 4) parameters inherit the Hotine keys lonc/alpha, but PROJ's labrd takes lon_0/azi; the builder overrides them (PROJ_KEY_OVERRIDES) and the validation step is what caught it. The table should be corrected so the override can go.
  • When async-tiff ships keys 2062 and 3096, add their accessors to KEY_ACCESSORS (and a BoundCRS for TOWGS84) and remove the four rows.
  • Also includes the .gitignore entry for dev-docs/plans/ that did not land with test: Add the CRS conformance suite from geotiff-test-data #181.

🤖 Written by Claude Code

🤖 Generated with Claude Code

kylebarron and others added 5 commits October 26, 2026 14:03
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The user-defined path of _crs.py no longer hand-writes PROJJSON. The
geographic CRS comes from _crs_geographic (units, ellipsoid, datum and
prime meridian by code or key, GDAL's citation names) and the projection
from _crs_conversion, which interprets the vendored CT table: libgeotiff's
key fallback order, unit scaling of angular and linear parameters, the
Mercator and Polar Stereographic variant rules, pyproj's typed conversion
classes where they exist and a PROJ string otherwise, and a check that
every table parameter reached the conversion.

Known conformance failures drop from 34 to 7: four rows need GeoKeys
async-tiff does not expose yet, one is GDAL's own prime-meridian-in-grads
inconsistency, and two are ESRI PE String rows where GDAL's ESRI name
morphing differs from PROJ's parser.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…eys in the spec

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…, and exact degree units

Review findings on GeoKey arrangements GDAL never writes but the spec
allows: a coded datum no longer requires ellipsoid keys, an ellipsoid
code with only a semi-major axis keeps the coded flattening (and a
user-defined ellipsoid with no shape raises instead of becoming a
sphere), the degree code 9102 is exactly 1.0 as in libgeotiff, and a
unit with a zero conversion factor raises instead of zeroing angles.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ds-release-bot ds-release-bot Bot added the feat label Oct 26, 2026
@kylebarron
kylebarron merged commit 0ecafa6 into main Oct 26, 2026
6 checks passed
@kylebarron
kylebarron deleted the kyle/crs-table-builder branch October 26, 2026 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant