Chain vertical CRS transformations through intermediate same-datum vertical CRS#4708
Conversation
…te vertical CRSs sharing a datum with the source or target
rouault
left a comment
There was a problem hiding this comment.
is this generated with LLM ? Asking because the use of non ASCII characters like ↔ is quite typical of them. We have a checkbox in the pull request template about AI/LLM usage. It must be ticked when appropriate
test/unit/test_operationfactory.cpp
Outdated
| // The pipeline should contain axisswap (depth→height) and geogoffset | ||
| auto projStr = | ||
| list[0]->exportToPROJString(PROJStringFormatter::create().get()); | ||
| EXPECT_TRUE(projStr.find("geogoffset") != std::string::npos) << projStr; |
There was a problem hiding this comment.
would be more convincing to test the whole pipeline string
There was a problem hiding this comment.
@rouault
Good point, thanks. Full pipeline is now added.
Yes, this code was developed with assistance from an LLM, and I forgot to check the box. I will remove the non-ASCII characters and update the PR description. Thanks for pointing that out |
359e734 to
b7071f0
Compare
…e same-datum vertical CRS (#4711) Chain vertical CRS transformations through intermediate same-datum vertical CRS (#4708) ## Summary When transforming between two vertical CRSs and no direct registered operation exists for the exact pair, PROJ now searches for registered operations involving an intermediate vertical CRS that shares the same datum as the source or target. The intermediate CRS may differ in axis direction (height vs depth), units (metres vs feet), or both. Previously PROJ fell back to a ballpark transformation, discarding available registered operations. - ✅ Closes #4707 - ✅ Tests added - [x] Developed with AI-assistance (Claude Opus 4.6) ## Example **EPSG:5705** (Baltic 1977 height) → **EPSG:5706** (Caspian depth) EPSG:5438 transforms 5705 → 5611 (Caspian *height*). CRS 5611 and 5706 share the Caspian datum but differ in axis direction. PROJ now composes: `5705 →(EPSG:5438)→ 5611 →(height-to-depth axisswap)→ 5706` ### Before (ballpark fallback) > ``` > +proj=affine +s33=-1 > (50.0, 40.0, 100) → (50.0, 40.0, -100.0) ← wrong > ``` ### After (registered operation + axis conversion) > ``` > +proj=pipeline +step +proj=geogoffset +dh=28 +step +proj=axisswap +order=1,2,-3 > (50.0, 40.0, 100) → (50.0, 40.0, -128.0) ← correct > ```
Summary
When transforming between two vertical CRSs and no direct registered operation exists for the exact pair, PROJ now searches for registered operations involving an intermediate vertical CRS that shares the same datum as the source or target. The intermediate CRS may differ in axis direction (height vs depth), units (metres vs feet), or both. Previously PROJ fell back to a ballpark transformation, discarding available registered operations.
Example
EPSG:5705 (Baltic 1977 height) → EPSG:5706 (Caspian depth)
EPSG:5438 transforms 5705 → 5611 (Caspian height). CRS 5611 and 5706 share the Caspian datum but differ in axis direction. PROJ now composes:
5705 →(EPSG:5438)→ 5611 →(height-to-depth axisswap)→ 5706Before (ballpark fallback)
After (registered operation + axis conversion)
Implementation
New method
createOperationsVertToVertWithIntermediateVertincoordinateoperationfactory.cpp, using two strategies:This mirrors the existing
createOperationsGeogToVertWithIntermediateVert(geographic→vertical paths) but extends it to vertical→vertical paths. Uses the existingfindCandidateVertCRSForDatuminfrastructure.Changes
src/iso19111/operation/coordinateoperationfactory.cpp: AddedcreateOperationsVertToVertWithIntermediateVertmethod with anti-recursion guard; invoked fromcreateOperationsFromDatabaseWithVertCRSwhen no direct result is found.test/unit/test_operationfactory.cpp: Two new tests — Strategy 1 (5705→5706, height→depth) and Strategy 2 (5706→5705, depth→height).NEWS.md: Added entry under 9.8.0 Updates.docs/source/operations/operations_computation.rst: Updated "Vertical CRS to a Vertical CRS" section documenting the new intermediate CRS pivot logic.