aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2020-04-04 12:23:07 +0200
committerKristian Evers <kristianevers@gmail.com>2020-04-04 19:23:22 +0200
commit8aab6fd948a5e9c16a352fa34a4e8cde54d5d924 (patch)
tree6b258381e0e197f99bb5a9360408f508dd234eb5
parent670eab68f2345b2a77bfac8a109588770bceb740 (diff)
downloadPROJ-8aab6fd948a5e9c16a352fa34a4e8cde54d5d924.tar.gz
PROJ-8aab6fd948a5e9c16a352fa34a4e8cde54d5d924.zip
createOperations(): improve results of compoundCRS to compoundCRS case (fixes #2115)
-rw-r--r--docs/source/operations/operations_computation.rst93
-rw-r--r--src/iso19111/coordinateoperation.cpp196
-rw-r--r--test/unit/test_operation.cpp79
3 files changed, 265 insertions, 103 deletions
diff --git a/docs/source/operations/operations_computation.rst b/docs/source/operations/operations_computation.rst
index ef84cfb2..1813a010 100644
--- a/docs/source/operations/operations_computation.rst
+++ b/docs/source/operations/operations_computation.rst
@@ -660,46 +660,73 @@ CompoundCRS to CompoundCRS
---------------------------------------------------------------------------------
There is some similarity with the previous paragraph. We first research the
-vertical transformations between the vertical CRS. If such tranformation has
-a registered interpolation geographic CRS, then it is used. Otherwise we fallback
-to the geographic CRS of the source CRS.
+vertical transformations between the two vertical CRS.
-Finally, a 3-level loop to create the final set of operations chaining together:
+1. If there is such a tranformation, be it direct, or if both vertical CRS
+ relate to a common intermediate CRS.
+ If it has a registered interpolation geographic CRS, then it is used.
+ Otherwise we fallback to the geographic CRS of the source CRS.
-- the horizontal transformation from the source CRS to the interpolation CRS
-- the vertical transformation
-- the horizontal transformation from the interpolation CRS to the target CRS.
+ Finally, a 3-level loop to create the final set of operations chaining together:
-This is implemented by the ``createOperationsCompoundToGeog`` method
+ - the horizontal transformation from the source CRS to the interpolation CRS
+ - the vertical transformation
+ - the horizontal transformation from the interpolation CRS to the target CRS.
-Example:
+ Example:
-.. code-block:: shell
+ .. code-block:: shell
+
+ $ projinfo -s "NAD27 + NGVD29 height (ftUS)" -t "NAD83 + NAVD88 height" --spatial-test intersects --summary
+
+ Candidate operations found: 20
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (1), 0.17 m, USA - CONUS east of 89°W - onshore
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + NAD27 to NAD83 (1), 0.17 m, USA - CONUS 89°W-107°W - onshore
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + NAD27 to NAD83 (1), 0.17 m, USA - CONUS west of 107°W - onshore
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (3), 1.02 m, unknown domain of validity
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + NAD27 to NAD83 (3), 1.02 m, unknown domain of validity
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + NAD27 to NAD83 (3), 1.02 m, unknown domain of validity
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (5), 1.02 m, unknown domain of validity, at least one grid missing
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (6), 1.52 m, unknown domain of validity, at least one grid missing
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + NAD27 to NAD83 (9), 1.52 m, unknown domain of validity, at least one grid missing
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + NAD27 to NAD83 (9), 1.52 m, unknown domain of validity, at least one grid missing
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, USA - CONUS east of 89°W - onshore, has ballpark transformation
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, USA - CONUS 89°W-107°W - onshore, has ballpark transformation
+ unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, USA - CONUS west of 107°W - onshore, has ballpark transformation
+ unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (1), unknown accuracy, USA - CONUS including EEZ, has ballpark transformation
+ unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (3), unknown accuracy, Canada, has ballpark transformation
+ unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (4), unknown accuracy, Canada - NAD27, has ballpark transformation
+ unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (5), unknown accuracy, Canada - Quebec, has ballpark transformation, at least one grid missing
+ unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (6), unknown accuracy, Canada - Quebec, has ballpark transformation, at least one grid missing
+ unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (9), unknown accuracy, Canada - Saskatchewan, has ballpark transformation, at least one grid missing
+ unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, World, has ballpark transformation
+
+
+2. Otherwise, when there is no such transformation, we decompose into 3 steps:
+
+ - transform from the source CRS to the geographic 3D CRS corresponding to it
+ - transform from the geographic 3D CRS corresponding to the source CRS to the
+ geographic 3D CRS corresponding to the target CRS
+ - transform from the geographic 3D CRS corresponding to the target CRS to the
+ target CRS.
+
+ Example:
+
+ .. code-block:: shell
+
+ $ projinfo -s "WGS 84 + EGM96 height" -t "ETRS89 + Belfast height" --spatial-test intersects --summary
+
+ Candidate operations found: 7
+ unknown id, Inverse of WGS 84 to EGM96 height (1) + Inverse of ETRS89 to WGS 84 (1) + ETRS89 to Belfast height (2), 2.014 m, UK - Northern Ireland - onshore
+ unknown id, Inverse of WGS 84 to EGM96 height (1) + Inverse of ETRS89 to WGS 84 (1) + ETRS89 to Belfast height (1), 2.03 m, UK - Northern Ireland - onshore, at least one grid missing
+ unknown id, Inverse of WGS 84 to EGM96 height (1) + Null geographic offset from WGS 84 (geog3D) to WGS 84 (geog2D) + Inverse of OSGB 1936 to WGS 84 (4) + OSGB 1936 to ETRS89 (2) + Null geographic offset from ETRS89 (geog2D) to ETRS89 (geog3D) + ETRS89 to Belfast height (2), 19.044 m, unknown domain of validity
+ unknown id, Inverse of WGS 84 to EGM96 height (1) + Null geographic offset from WGS 84 (geog3D) to WGS 84 (geog2D) + Inverse of OSGB 1936 to WGS 84 (2) + OSGB 1936 to ETRS89 (2) + Null geographic offset from ETRS89 (geog2D) to ETRS89 (geog3D) + ETRS89 to Belfast height (2), 11.044 m, unknown domain of validity
+ unknown id, Inverse of WGS 84 to EGM96 height (1) + Null geographic offset from WGS 84 (geog3D) to WGS 84 (geog2D) + Inverse of TM75 to WGS 84 (2) + TM75 to ETRS89 (3) + Null geographic offset from ETRS89 (geog2D) to ETRS89 (geog3D) + ETRS89 to Belfast height (2), 2.424 m, UK - Northern Ireland - onshore, at least one grid missing
+ unknown id, Inverse of WGS 84 to EGM96 height (1) + Null geographic offset from WGS 84 (geog3D) to WGS 84 (geog2D) + Inverse of TM75 to WGS 84 (2) + TM75 to ETRS89 (3) + Null geographic offset from ETRS89 (geog2D) to ETRS89 (geog3D) + ETRS89 to Belfast height (1), 2.44 m, UK - Northern Ireland - onshore, at least one grid missing
+ unknown id, Inverse of WGS 84 to EGM96 height (1) + Null geographic offset from WGS 84 (geog3D) to WGS 84 (geog2D) + Inverse of OSGB 1936 to WGS 84 (4) + OSGB 1936 to ETRS89 (2) + Null geographic offset from ETRS89 (geog2D) to ETRS89 (geog3D) + ETRS89 to Belfast height (1), 19.06 m, unknown domain of validity, at least one grid missing
- $ projinfo -s "NAD27 + NGVD29 height (ftUS)" -t "NAD83 + NAVD88 height" --spatial-test intersects --summary
-
- Candidate operations found: 20
- unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (1), 0.17 m, USA - CONUS east of 89°W - onshore
- unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + NAD27 to NAD83 (1), 0.17 m, USA - CONUS 89°W-107°W - onshore
- unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + NAD27 to NAD83 (1), 0.17 m, USA - CONUS west of 107°W - onshore
- unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (3), 1.02 m, unknown domain of validity
- unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + NAD27 to NAD83 (3), 1.02 m, unknown domain of validity
- unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + NAD27 to NAD83 (3), 1.02 m, unknown domain of validity
- unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (5), 1.02 m, unknown domain of validity, at least one grid missing
- unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + NAD27 to NAD83 (6), 1.52 m, unknown domain of validity, at least one grid missing
- unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + NAD27 to NAD83 (9), 1.52 m, unknown domain of validity, at least one grid missing
- unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + NAD27 to NAD83 (9), 1.52 m, unknown domain of validity, at least one grid missing
- unknown id, NGVD29 height (ftUS) to NAVD88 height (3) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, USA - CONUS east of 89°W - onshore, has ballpark transformation
- unknown id, NGVD29 height (ftUS) to NAVD88 height (2) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, USA - CONUS 89°W-107°W - onshore, has ballpark transformation
- unknown id, NGVD29 height (ftUS) to NAVD88 height (1) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, USA - CONUS west of 107°W - onshore, has ballpark transformation
- unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (1), unknown accuracy, USA - CONUS including EEZ, has ballpark transformation
- unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (3), unknown accuracy, Canada, has ballpark transformation
- unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (4), unknown accuracy, Canada - NAD27, has ballpark transformation
- unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (5), unknown accuracy, Canada - Quebec, has ballpark transformation, at least one grid missing
- unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (6), unknown accuracy, Canada - Quebec, has ballpark transformation, at least one grid missing
- unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + NAD27 to NAD83 (9), unknown accuracy, Canada - Saskatchewan, has ballpark transformation, at least one grid missing
- unknown id, Transformation from NGVD29 height (ftUS) to NAVD88 height (ballpark vertical transformation) + Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, World, has ballpark transformation
+This is implemented by the ``createOperationsCompoundToCompound`` method
When the source or target CRS is a BoundCRS
---------------------------------------------------------------------------------
diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
index 267e9450..822d09ac 100644
--- a/src/iso19111/coordinateoperation.cpp
+++ b/src/iso19111/coordinateoperation.cpp
@@ -14754,84 +14754,144 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToCompound(
const auto &componentsSrc = compoundSrc->componentReferenceSystems();
const auto &componentsDst = compoundDst->componentReferenceSystems();
- if (!componentsSrc.empty() &&
- componentsSrc.size() == componentsDst.size()) {
- if (componentsSrc[0]->extractGeographicCRS() &&
- componentsDst[0]->extractGeographicCRS()) {
-
- std::vector<CoordinateOperationNNPtr> verticalTransforms;
- if (componentsSrc.size() >= 2 &&
- componentsSrc[1]->extractVerticalCRS() &&
- componentsDst[1]->extractVerticalCRS()) {
- if (!componentsSrc[1]->_isEquivalentTo(
- componentsDst[1].get())) {
- verticalTransforms = createOperations(
- componentsSrc[1], componentsDst[1], context);
- }
- }
+ if (componentsSrc.empty() || componentsSrc.size() != componentsDst.size()) {
+ return;
+ }
+ const auto srcGeog = componentsSrc[0]->extractGeographicCRS();
+ const auto dstGeog = componentsDst[0]->extractGeographicCRS();
+ if (srcGeog == nullptr || dstGeog == nullptr) {
+ return;
+ }
- for (const auto &verticalTransform : verticalTransforms) {
- auto interpolationGeogCRS =
- NN_NO_CHECK(componentsSrc[0]->extractGeographicCRS());
- auto transformationVerticalTransform =
- dynamic_cast<const Transformation *>(
- verticalTransform.get());
- if (transformationVerticalTransform) {
- auto interpTransformCRS =
- transformationVerticalTransform->interpolationCRS();
- if (interpTransformCRS) {
- auto nn_interpTransformCRS =
- NN_NO_CHECK(interpTransformCRS);
- if (dynamic_cast<const crs::GeographicCRS *>(
- nn_interpTransformCRS.get())) {
- interpolationGeogCRS = NN_NO_CHECK(
- util::nn_dynamic_pointer_cast<
- crs::GeographicCRS>(nn_interpTransformCRS));
- }
- }
- } else {
- auto compSrc0BoundCrs =
- dynamic_cast<crs::BoundCRS *>(componentsSrc[0].get());
- auto compDst0BoundCrs =
- dynamic_cast<crs::BoundCRS *>(componentsDst[0].get());
- if (compSrc0BoundCrs && compDst0BoundCrs &&
- dynamic_cast<crs::GeographicCRS *>(
- compSrc0BoundCrs->hubCRS().get()) &&
- compSrc0BoundCrs->hubCRS()->_isEquivalentTo(
- compDst0BoundCrs->hubCRS().get())) {
- interpolationGeogCRS = NN_NO_CHECK(
- util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
- compSrc0BoundCrs->hubCRS()));
- }
+ std::vector<CoordinateOperationNNPtr> verticalTransforms;
+ if (componentsSrc.size() >= 2 && componentsSrc[1]->extractVerticalCRS() &&
+ componentsDst[1]->extractVerticalCRS()) {
+ if (!componentsSrc[1]->_isEquivalentTo(componentsDst[1].get())) {
+ verticalTransforms =
+ createOperations(componentsSrc[1], componentsDst[1], context);
+ }
+ }
+
+ // If we didn't find a non-ballbark transformation between
+ // the 2 vertical CRS, then try through intermediate geographic CRS
+ // For example
+ // WGS 84 + EGM96 --> ETRS89 + Belfast height where
+ // there is a geoid model for EGM96 referenced to WGS 84
+ // and a geoid model for Belfast height referenced to ETRS89
+ if (verticalTransforms.size() == 1 &&
+ verticalTransforms.front()->hasBallparkTransformation()) {
+ auto dbContext =
+ context.context->getAuthorityFactory()->databaseContext();
+ const auto intermGeogSrc =
+ srcGeog->promoteTo3D(std::string(), dbContext);
+ const bool intermGeogSrcIsSameAsIntermGeogDst =
+ srcGeog->_isEquivalentTo(dstGeog.get());
+ const auto intermGeogDst =
+ intermGeogSrcIsSameAsIntermGeogDst
+ ? intermGeogSrc
+ : dstGeog->promoteTo3D(std::string(), dbContext);
+ const auto opsSrcToGeog =
+ createOperations(sourceCRS, intermGeogSrc, context);
+ const auto opsGeogToTarget =
+ createOperations(intermGeogDst, targetCRS, context);
+ const bool hasNonTrivalSrcTransf =
+ !opsSrcToGeog.empty() &&
+ !opsSrcToGeog.front()->hasBallparkTransformation();
+ const bool hasNonTrivialTargetTransf =
+ !opsGeogToTarget.empty() &&
+ !opsGeogToTarget.front()->hasBallparkTransformation();
+ if (hasNonTrivalSrcTransf && hasNonTrivialTargetTransf) {
+ const auto opsGeogSrcToGeogDst =
+ createOperations(intermGeogSrc, intermGeogDst, context);
+ for (const auto &op1 : opsSrcToGeog) {
+ if (op1->hasBallparkTransformation()) {
+ continue;
}
- auto opSrcCRSToGeogCRS = createOperations(
- componentsSrc[0], interpolationGeogCRS, context);
- auto opGeogCRStoDstCRS = createOperations(
- interpolationGeogCRS, componentsDst[0], context);
- for (const auto &opSrc : opSrcCRSToGeogCRS) {
- for (const auto &opDst : opGeogCRStoDstCRS) {
-
+ for (const auto &op2 : opsGeogSrcToGeogDst) {
+ for (const auto &op3 : opsGeogToTarget) {
+ if (op3->hasBallparkTransformation()) {
+ continue;
+ }
try {
- auto op = createHorizVerticalHorizPROJBased(
- sourceCRS, targetCRS, opSrc, verticalTransform,
- opDst, interpolationGeogCRS, true);
- res.emplace_back(op);
- } catch (const InvalidOperationEmptyIntersection &) {
- } catch (const io::FormattingException &) {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ intermGeogSrcIsSameAsIntermGeogDst
+ ? std::vector<
+ CoordinateOperationNNPtr>{op1,
+ op3}
+ : std::vector<
+ CoordinateOperationNNPtr>{op1,
+ op2,
+ op3},
+ !allowEmptyIntersection));
+ } catch (const std::exception &) {
}
}
}
}
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
- if (verticalTransforms.empty()) {
- auto resTmp = createOperations(componentsSrc[0],
- componentsDst[0], context);
- for (const auto &op : resTmp) {
- auto opClone = op->shallowClone();
- setCRSs(opClone.get(), sourceCRS, targetCRS);
- res.emplace_back(opClone);
+ for (const auto &verticalTransform : verticalTransforms) {
+ auto interpolationGeogCRS = NN_NO_CHECK(srcGeog);
+ auto transformationVerticalTransform =
+ dynamic_cast<const Transformation *>(verticalTransform.get());
+ if (transformationVerticalTransform) {
+ auto interpTransformCRS =
+ transformationVerticalTransform->interpolationCRS();
+ if (interpTransformCRS) {
+ auto nn_interpTransformCRS = NN_NO_CHECK(interpTransformCRS);
+ if (dynamic_cast<const crs::GeographicCRS *>(
+ nn_interpTransformCRS.get())) {
+ interpolationGeogCRS = NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ nn_interpTransformCRS));
}
}
+ } else {
+ auto compSrc0BoundCrs =
+ dynamic_cast<crs::BoundCRS *>(componentsSrc[0].get());
+ auto compDst0BoundCrs =
+ dynamic_cast<crs::BoundCRS *>(componentsDst[0].get());
+ if (compSrc0BoundCrs && compDst0BoundCrs &&
+ dynamic_cast<crs::GeographicCRS *>(
+ compSrc0BoundCrs->hubCRS().get()) &&
+ compSrc0BoundCrs->hubCRS()->_isEquivalentTo(
+ compDst0BoundCrs->hubCRS().get())) {
+ interpolationGeogCRS = NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ compSrc0BoundCrs->hubCRS()));
+ }
+ }
+ auto opSrcCRSToGeogCRS =
+ createOperations(componentsSrc[0], interpolationGeogCRS, context);
+ auto opGeogCRStoDstCRS =
+ createOperations(interpolationGeogCRS, componentsDst[0], context);
+ for (const auto &opSrc : opSrcCRSToGeogCRS) {
+ for (const auto &opDst : opGeogCRStoDstCRS) {
+
+ try {
+ auto op = createHorizVerticalHorizPROJBased(
+ sourceCRS, targetCRS, opSrc, verticalTransform, opDst,
+ interpolationGeogCRS, true);
+ res.emplace_back(op);
+ } catch (const InvalidOperationEmptyIntersection &) {
+ } catch (const io::FormattingException &) {
+ }
+ }
+ }
+ }
+
+ if (verticalTransforms.empty()) {
+ auto resTmp =
+ createOperations(componentsSrc[0], componentsDst[0], context);
+ for (const auto &op : resTmp) {
+ auto opClone = op->shallowClone();
+ setCRSs(opClone.get(), sourceCRS, targetCRS);
+ res.emplace_back(opClone);
}
}
}
diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp
index ad218899..99e05dd1 100644
--- a/test/unit/test_operation.cpp
+++ b/test/unit/test_operation.cpp
@@ -7357,11 +7357,11 @@ TEST(operation, compoundCRS_to_compoundCRS_context_helmert_noop) {
ctxt->setSpatialCriterion(
CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
// WGS84 + EGM96
- auto objSrc = createFromUserInput("EPSG:4326+3855", dbContext);
+ auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext);
auto srcCrs = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
ASSERT_TRUE(srcCrs != nullptr);
// ETRS89 + EGM96
- auto objDest = createFromUserInput("EPSG:4258+3855", dbContext);
+ auto objDest = createFromUserInput("EPSG:4258+5773", dbContext);
auto destCrs = nn_dynamic_pointer_cast<CompoundCRS>(objDest);
ASSERT_TRUE(destCrs != nullptr);
auto list = CoordinateOperationFactory::create()->createOperations(
@@ -7373,6 +7373,81 @@ TEST(operation, compoundCRS_to_compoundCRS_context_helmert_noop) {
// ---------------------------------------------------------------------------
+// EGM96 has a geoid model referenced to WGS84, and Belfast height has a
+// geoid model referenced to ETRS89
+TEST(operation, compoundCRS_to_compoundCRS_WGS84_EGM96_to_ETRS89_Belfast) {
+ auto dbContext = DatabaseContext::create();
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ // WGS84 + EGM96
+ auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext);
+ auto srcCrs = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
+ ASSERT_TRUE(srcCrs != nullptr);
+ // ETRS89 + Belfast height
+ auto objDest = createFromUserInput("EPSG:4258+5732", dbContext);
+ auto destCrs = nn_dynamic_pointer_cast<CompoundCRS>(objDest);
+ ASSERT_TRUE(destCrs != nullptr);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCrs), NN_NO_CHECK(destCrs), ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + "
+ "Inverse of ETRS89 to WGS 84 (1) + "
+ "ETRS89 to Belfast height (2)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 "
+ "+step +inv +proj=vgridshift +grids=uk_os_OSGM15_Belfast.tif "
+ "+multiplier=1 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+// Variant of above where source intermediate geog3D CRS == target intermediate
+// geog3D CRS
+TEST(operation, compoundCRS_to_compoundCRS_WGS84_EGM96_to_WGS84_Belfast) {
+ auto dbContext = DatabaseContext::create();
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ // WGS84 + EGM96
+ auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext);
+ auto srcCrs = nn_dynamic_pointer_cast<CompoundCRS>(objSrc);
+ ASSERT_TRUE(srcCrs != nullptr);
+ // WGS84 + Belfast height
+ auto objDest = createFromUserInput("EPSG:4326+5732", dbContext);
+ auto destCrs = nn_dynamic_pointer_cast<CompoundCRS>(objDest);
+ ASSERT_TRUE(destCrs != nullptr);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(srcCrs), NN_NO_CHECK(destCrs), ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + "
+ "Inverse of ETRS89 to WGS 84 (1) + "
+ "ETRS89 to Belfast height (2) + "
+ "ETRS89 to WGS 84 (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 "
+ "+step +inv +proj=vgridshift +grids=uk_os_OSGM15_Belfast.tif "
+ "+multiplier=1 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
TEST(operation, vertCRS_to_vertCRS) {
auto vertcrs_m_obj = PROJStringParser().createFromPROJString("+vunits=m");