Skip to content

Chain vertical CRS transformations through intermediate same-datum vertical CRS#4708

Merged
rouault merged 10 commits intoOSGeo:masterfrom
phaarnes:fix/vert-to-vert-operations-chaining
Mar 13, 2026
Merged

Chain vertical CRS transformations through intermediate same-datum vertical CRS#4708
rouault merged 10 commits intoOSGeo:masterfrom
phaarnes:fix/vert-to-vert-operations-chaining

Conversation

@phaarnes
Copy link
Contributor

@phaarnes phaarnes commented Mar 10, 2026

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)→ 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

Implementation

New method createOperationsVertToVertWithIntermediateVert in coordinateoperationfactory.cpp, using two strategies:

  1. Pivot on target datum: Find candidate vertical CRSs sharing the target's datum. If a registered operation exists from the source to any candidate, compose it with the candidate→target axis/unit conversion.
  2. Pivot on source datum: Find candidate vertical CRSs sharing the source's datum. If a registered operation exists from any candidate to the target, compose the source→candidate conversion with that operation.

This mirrors the existing createOperationsGeogToVertWithIntermediateVert (geographic→vertical paths) but extends it to vertical→vertical paths. Uses the existing findCandidateVertCRSForDatum infrastructure.

Changes

  • src/iso19111/operation/coordinateoperationfactory.cpp: Added createOperationsVertToVertWithIntermediateVert method with anti-recursion guard; invoked from createOperationsFromDatabaseWithVertCRS when 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.

Copy link
Member

@rouault rouault left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

// 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be more convincing to test the whole pipeline string

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rouault
Good point, thanks. Full pipeline is now added.

@phaarnes
Copy link
Contributor Author

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

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

@rouault rouault force-pushed the fix/vert-to-vert-operations-chaining branch from 359e734 to b7071f0 Compare March 13, 2026 17:03
@rouault rouault added the backport 9.8 Backport to 9.8 branch label Mar 13, 2026
@rouault rouault merged commit 3409895 into OSGeo:master Mar 13, 2026
31 checks passed
rouault added a commit that referenced this pull request Mar 13, 2026
…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
> ```
@phaarnes phaarnes deleted the fix/vert-to-vert-operations-chaining branch March 16, 2026 06:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport 9.8 Backport to 9.8 branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Chain vertical CRS transformations through intermediate vertical CRS sharing the same datum

2 participants