Skip to content

Ship datum support: ETRS89, NAD27, OSGB 1936 and British National Grid (#243) - #249

Closed
trasch wants to merge 2 commits into
add-struct-projectionfrom
add-datum-support
Closed

trasch wants to merge 2 commits into
add-struct-projectionfrom
add-datum-support

Conversation

@trasch

@trasch trasch commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements #243 for the WGS84-pivot architecture from #247. Ships datum-capable built-in CRSs and the public transformation helper for user-defined datum CRSs. Grid shift support (NADCON/OSTN15/NTv2, sub-meter accuracy) is deliberately filed as a separate issue (#248) per the review decision — load-don't-bundle, per-file license review.

Phase 1 — Datum metadata (additive)

  • public struct Ellipsoid (wgs84, grs80, clarke1866, airy1830, EPSG-registr υalues + derived axes/eccentricities)
  • public struct Datum (wgs84, etrs89, nad27, osgb1936)
  • Projection.datum, ProjectionDefinition.datum (default WGS84), CustomProjection.datum field

Phase 2 — Verified math infrastructure

  • public struct HelmertTransformation — 7-parameter helmert (position vector convention, exact rotation matrices, geocentric legs on arbitrary source ellipsoids) with both directions and explicit result labeling: well-known sets for NAD27 ("NAD27 to WGS 84 (4)", ~10 m) and OSGB36 ("OSGB 1936 to WGS 84 (6)", EPSG:1314, ~2 m) pulled from the EPSG registry
  • TransverseMercatorMath(ellipsoid:) — internal generalization of the Snyder TM formulas with a latitude-of-origin; UtmDefinition now delegates to it (day-1 UTM suite proves the WGS84 math byte-equivalent)

Phase 3 — Built-in datum CRSs

EPSG Type Notes
4258 ETRS89 geodetic identity transformation — OS itself labels ETRS89 data as WGS84 at meter accuracy
4267 NAD27 geodetic Helmert "(4)", ~10 m; NADCON is the sub-meter path (#248)
4277 OSGB 1936 geodetic Helmert EPSG:1314, ~2 m
27700 British National Grid Helmert + TM on Airy 1830 (lat₀ 49°, lon₀ −2°, k 0.9996012717, FE 400000, FN −100000)
  • All seeded into the registry with WKT matcher fragments; BNG probes before the generic Mercator fragments (a BNG .prj mentions Transverse_Mercator, which would otherwise match 3395 — same ordering rule as Identify UTM zones in WKT strings via regex #244's Pseudo-Mercator)
  • 27700 is unbounded (validExtent: nil) — the SW sector carries negative northings (FN = −100 km), which a nominal sector box would reject/clamp away; pinned by tests

Verification discipline (pyproj ground truth first)

Reference values were computed independently with pyproj/PROJ before porting, at 9-decimal precision (kept 2>/dev/null-suppressed warnings of the used epsg codes are documented in the test doc comments):

Path Fixture round trips measured as
NAD27 (Helmert 4) round trip 3.3e-09°
OSGB36/BNG (Helmert 1314) round trip 1.4e-08°

Fixture points include in-domain N. America and UK points, a south-west BNG case (northing 14_401.7), and the geographic/+height round trips.

Changes

New files: Datum.swift, HelmertTransformation.swift, TransverseMercatorMath.swift, the four CRS definitions; datum drinks threaded through Projection/definitions/CustomProjection; UTM delegates to the TM math. Projection exposes the new static constants.

Testing (+12)

DatumTests, HelmertTransformationTests, DatumCrsTests (reference fixtures, round trips via the WGS84 pivot incl. 4267→27700, algorithm smoke tests, WKB round trips with embedded SRIDs, WKT matching). GEOGCS["ETRS89"] now matches EPSG:4258 (previously nil).

Full suite: 2500 tests in 183 suites, zero warnings.

Implements #243 for the WGS84-pivot architecture established in
#247, shipping datum-capable built-in projections and the public
transformation helper for user-defined datum CRSs.

Phase 1 - Datum metadata model:
- public Ellipsoid (wgs84/grs80/clarke1866/airy1830 with derived
  eccentricities and axes) and public Datum (wgs84/etrs89/nad27/
  osgb1936)
- Projection.datum accessor resolved from the definition;
  ProjectionDefinition gains a datum requirement (default WGS84);
  CustomProjection gains a datum field.

Phase 2 - verified math infrastructure:
- public HelmertTransformation: 7-parameter Helmert with position
  vector convention, exact rotation matrices, geocentric legs on
  arbitrary source ellipsoids, both directions with explicit
  result projection labeling. Well-known sets for NAD27
  ("NAD27 to WGS 84 (4)", ~10 m) and OSGB36 ("OSGB 1936 to WGS 84
  (6)", EPSG:1314, ~2 m) sourced from the EPSG registry and
  verified against pyproj/PROJ reference values.
- internal TransverseMercatorMath(ellipsoid:) generalizing the
  Snyder TM formulas with a latitude-of-origin; UtmDefinition
  delegates to it (guarded by the existing UTM known-value tests,
  behavior provably unchanged).

Phase 3 - built-in datum CRSs:
- EPSG:4267 NAD27 geodetic, EPSG:4277 OSGB36 geodetic,
  EPSG:4258 ETRS89 geodetic (identity shift; OS labels ETRS89
  data as WGS84 at meter accuracy) and EPSG:27700 British
  National Grid (helmert + TM on Airy 1830: lat0 49, lon0 -2,
  k 0.9996012717, FE 400000, FN -100000)
- all registered in the registry seed with WKT matcher fragments
  (BNG probes before the generic Mercator fragments), datum
  metadata, test-appropriate unbounded extents (BNG keeps its
  southwest negative-northing region valid).

Fixtures: The reference values were computed independently with
pyproj/PROJ before porting, at 9-decimal precision; round-trip
errors measured at ~3.3e-9 deg (NAD27) and ~1.4e-8 deg (OSGB36).

Tests (+12): datum metadata, Helmert both directions + round
trips, per-CRS reference fixtures, route-pivot round trips,
British National Grid values with the southwest sector, algorithm
smoke tests, WKB round trips and WKT matching for the new SRIDs.
ETRS89-bearing WKT strings now match EPSG:4258 instead of
returning nil.

Also extends the README Projections section with the datum CRSs,
the accuracy tier note and attribution, and links the grid shift
support issue (#248).
@trasch
trasch marked this pull request as draft September 15, 2026 10:29
@trasch trasch self-assigned this Sep 15, 2026
@trasch trasch added the enhancement New feature or request label Sep 15, 2026
@trasch

trasch commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #250, which consolidates the full projection-genericity chain into a single reviewable PR. All commits are unchanged in #250.

@trasch trasch closed this Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant