@@ -6090,9 +6090,7 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
60906090 }
60916091
60926092 // Only do a vertical transformation if the target CRS is 3D.
6093- const auto dstSingle = dynamic_cast <crs::SingleCRS *>(targetCRS.get ());
6094- if (dstSingle &&
6095- dstSingle->coordinateSystem ()->axisList ().size () == 2 ) {
6093+ if (geogDst->coordinateSystem ()->axisList ().size () <= 2 ) {
60966094 auto tmp = createOperations (componentsSrc[0 ], sourceEpoch,
60976095 targetCRS, targetEpoch, context);
60986096 for (const auto &op : tmp) {
@@ -6109,6 +6107,42 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
61096107 horizTransforms = createOperations (componentsSrc[0 ], sourceEpoch,
61106108 targetCRS, targetEpoch, context);
61116109 }
6110+
6111+ const auto hasAtLeastOneOpWithNonTrivialAndWithAllGrids =
6112+ [&dbContext](
6113+ const std::vector<CoordinateOperationNNPtr> &verticalTransforms,
6114+ CoordinateOperationContext::GridAvailabilityUse
6115+ gridAvailabilityUse,
6116+ bool ignoreMissingGrids,
6117+ bool *foundRegisteredTransform = nullptr ) {
6118+ if (foundRegisteredTransform)
6119+ *foundRegisteredTransform = false ;
6120+ for (const auto &op : verticalTransforms) {
6121+ if (hasIdentifiers (op) && dbContext) {
6122+ bool missingGrid = false ;
6123+ if (!ignoreMissingGrids) {
6124+ const auto gridsNeeded = op->gridsNeeded (
6125+ dbContext,
6126+ gridAvailabilityUse ==
6127+ CoordinateOperationContext::
6128+ GridAvailabilityUse::KNOWN_AVAILABLE);
6129+ for (const auto &gridDesc : gridsNeeded) {
6130+ if (!gridDesc.available ) {
6131+ missingGrid = true ;
6132+ break ;
6133+ }
6134+ }
6135+ }
6136+ if (foundRegisteredTransform)
6137+ *foundRegisteredTransform = true ;
6138+ if (!missingGrid) {
6139+ return true ;
6140+ }
6141+ }
6142+ }
6143+ return false ;
6144+ };
6145+
61126146 std::vector<CoordinateOperationNNPtr> verticalTransforms;
61136147
61146148 if (componentsSrc.size () >= 2 &&
@@ -6127,7 +6161,6 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
61276161 context.skipHorizontalTransformation = false ;
61286162 }
61296163 };
6130- SetSkipHorizontalTransform setSkipHorizontalTransform (context);
61316164
61326165 struct SetGeogCRSOfVertCRS {
61336166 Context &context;
@@ -6148,39 +6181,23 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
61486181 };
61496182 SetGeogCRSOfVertCRS setGeogCRSOfVertCRS (context, srcGeogCRS);
61506183
6151- verticalTransforms = createOperations (
6152- componentsSrc[1 ], util::optional<common::DataEpoch>(),
6153- targetCRS->promoteTo3D (std::string (), dbContext),
6154- util::optional<common::DataEpoch>(), context);
6155- bool foundRegisteredTransformWithAllGridsAvailable = false ;
6184+ {
6185+ SetSkipHorizontalTransform setSkipHorizontalTransform (context);
6186+
6187+ verticalTransforms = createOperations (
6188+ componentsSrc[1 ], util::optional<common::DataEpoch>(),
6189+ targetCRS, util::optional<common::DataEpoch>(), context);
6190+ }
61566191 const auto gridAvailabilityUse =
61576192 context.context ->getGridAvailabilityUse ();
61586193 const bool ignoreMissingGrids =
61596194 gridAvailabilityUse ==
61606195 CoordinateOperationContext::GridAvailabilityUse::
61616196 IGNORE_GRID_AVAILABILITY;
6162- for (const auto &op : verticalTransforms) {
6163- if (hasIdentifiers (op) && dbContext) {
6164- bool missingGrid = false ;
6165- if (!ignoreMissingGrids) {
6166- const auto gridsNeeded = op->gridsNeeded (
6167- dbContext,
6168- gridAvailabilityUse ==
6169- CoordinateOperationContext::
6170- GridAvailabilityUse::KNOWN_AVAILABLE);
6171- for (const auto &gridDesc : gridsNeeded) {
6172- if (!gridDesc.available ) {
6173- missingGrid = true ;
6174- break ;
6175- }
6176- }
6177- }
6178- if (!missingGrid) {
6179- foundRegisteredTransformWithAllGridsAvailable = true ;
6180- break ;
6181- }
6182- }
6183- }
6197+ bool foundRegisteredTransformWithAllGridsAvailable =
6198+ hasAtLeastOneOpWithNonTrivialAndWithAllGrids (
6199+ verticalTransforms, gridAvailabilityUse,
6200+ ignoreMissingGrids);
61846201 if (srcGeogCRS &&
61856202 !srcGeogCRS->_isEquivalentTo (
61866203 geogDst, util::IComparable::Criterion::EQUIVALENT) &&
@@ -6190,40 +6207,22 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
61906207 ->demoteTo2D (std::string (), dbContext)
61916208 ->promoteTo3D (
61926209 std::string (), dbContext,
6193- geogDst->coordinateSystem ()->axisList ().size () == 3
6194- ? geogDst->coordinateSystem ()->axisList ()[2 ]
6195- : cs::VerticalCS::createGravityRelatedHeight (
6196- common::UnitOfMeasure::METRE)
6197- ->axisList ()[0 ]);
6198- auto verticalTransformsTmp = createOperations (
6199- componentsSrc[1 ], util::optional<common::DataEpoch>(),
6200- geogCRSTmp, util::optional<common::DataEpoch>(), context);
6201- bool foundRegisteredTransform = false ;
6202- bool foundRegisteredTransformWithAllGridsAvailable2 = false ;
6203- for (const auto &op : verticalTransformsTmp) {
6204- if (hasIdentifiers (op) && dbContext) {
6205- bool missingGrid = false ;
6206- if (!ignoreMissingGrids) {
6207- const auto gridsNeeded = op->gridsNeeded (
6208- dbContext,
6209- gridAvailabilityUse ==
6210- CoordinateOperationContext::
6211- GridAvailabilityUse::KNOWN_AVAILABLE);
6212- for (const auto &gridDesc : gridsNeeded) {
6213- if (!gridDesc.available ) {
6214- missingGrid = true ;
6215- break ;
6216- }
6217- }
6218- }
6219- foundRegisteredTransform = true ;
6220- if (!missingGrid) {
6221- foundRegisteredTransformWithAllGridsAvailable2 =
6222- true ;
6223- break ;
6224- }
6225- }
6210+ geogDst->coordinateSystem ()->axisList ()[2 ]);
6211+ std::vector<CoordinateOperationNNPtr> verticalTransformsTmp;
6212+ {
6213+ SetSkipHorizontalTransform setSkipHorizontalTransform (
6214+ context);
6215+
6216+ verticalTransformsTmp = createOperations (
6217+ componentsSrc[1 ], util::optional<common::DataEpoch>(),
6218+ geogCRSTmp, util::optional<common::DataEpoch>(),
6219+ context);
62266220 }
6221+ bool foundRegisteredTransform = false ;
6222+ const bool foundRegisteredTransformWithAllGridsAvailable2 =
6223+ hasAtLeastOneOpWithNonTrivialAndWithAllGrids (
6224+ verticalTransformsTmp, gridAvailabilityUse,
6225+ ignoreMissingGrids, &foundRegisteredTransform);
62276226 if (foundRegisteredTransformWithAllGridsAvailable2 &&
62286227 !foundRegisteredTransformWithAllGridsAvailable) {
62296228 verticalTransforms = std::move (verticalTransformsTmp);
@@ -6233,6 +6232,94 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
62336232 verticalTransformsTmp.end ());
62346233 }
62356234 }
6235+
6236+ // Case for EPSG:5550+7651 ("PNG94 / PNGMG94 zone 54 + Kumul 34
6237+ // height") to EPSG:9754 ("WGS 84 (G2139)")
6238+ // (https://github.com/OSGeo/PROJ/issues/4618)
6239+ // where there is a vertical transformation between Kumul 34 height
6240+ // and WGS84 going through EGM96 and the transformation between
6241+ // PNG94 and WGS 84 (G2139) goes through WGS84 We can then get a
6242+ // good result by doing EPSG:5550+7651 -> EPSG:4979 and EPSG:4979 ->
6243+ // EPSG:9754
6244+ const auto hasNonBallparkLambda =
6245+ [](const CoordinateOperationNNPtr &op) {
6246+ return !op->hasBallparkTransformation ();
6247+ };
6248+ const bool onlyVerticalBallparkTransformation =
6249+ std::find_if (verticalTransforms.begin (),
6250+ verticalTransforms.end (),
6251+ hasNonBallparkLambda) == verticalTransforms.end ();
6252+ if (onlyVerticalBallparkTransformation) {
6253+ std::map<std::string, crs::CRSNNPtr> candidateIntermGeogCRS;
6254+ for (const auto &op : horizTransforms) {
6255+ if (!op->hasBallparkTransformation ()) {
6256+ auto concatenatedOp =
6257+ dynamic_cast <const ConcatenatedOperation *>(
6258+ op.get ());
6259+ if (concatenatedOp) {
6260+ for (const auto &subOp :
6261+ concatenatedOp->operations ()) {
6262+ auto subOpTargetCRS = subOp->targetCRS ();
6263+ if (subOpTargetCRS) {
6264+ auto tmpCRS = subOpTargetCRS->promoteTo3D (
6265+ std::string (), dbContext);
6266+ if (!tmpCRS->identifiers ().empty () &&
6267+ !tmpCRS->_isEquivalentTo (
6268+ targetCRS.get (),
6269+ util::IComparable::Criterion::
6270+ EQUIVALENT) &&
6271+ candidateIntermGeogCRS.find (
6272+ tmpCRS->nameStr ()) ==
6273+ candidateIntermGeogCRS.end ()) {
6274+ candidateIntermGeogCRS.insert (
6275+ std::make_pair (tmpCRS->nameStr (),
6276+ std::move (tmpCRS)));
6277+ }
6278+ }
6279+ }
6280+ }
6281+ }
6282+ }
6283+
6284+ for (const auto &[name, geogCRSTmp] : candidateIntermGeogCRS) {
6285+ (void )name;
6286+ const auto verticalTransformsTmp = createOperations (
6287+ componentsSrc[1 ], util::optional<common::DataEpoch>(),
6288+ geogCRSTmp, util::optional<common::DataEpoch>(),
6289+ context);
6290+ const bool onlyVerticalBallparkTransformationTmp =
6291+ std::find_if (verticalTransformsTmp.begin (),
6292+ verticalTransformsTmp.end (),
6293+ hasNonBallparkLambda) ==
6294+ verticalTransformsTmp.end ();
6295+ if (!onlyVerticalBallparkTransformationTmp) {
6296+ const auto ops1 = createOperations (
6297+ sourceCRS, util::optional<common::DataEpoch>(),
6298+ geogCRSTmp, util::optional<common::DataEpoch>(),
6299+ context);
6300+ const auto ops2 = createOperations (
6301+ geogCRSTmp, util::optional<common::DataEpoch>(),
6302+ targetCRS, util::optional<common::DataEpoch>(),
6303+ context);
6304+ for (const auto &op1 : ops1) {
6305+ if (!op1->hasBallparkTransformation ()) {
6306+ for (const auto &op2 : ops2) {
6307+ if (!op2->hasBallparkTransformation ()) {
6308+ try {
6309+ res.emplace_back (
6310+ ConcatenatedOperation::
6311+ createComputeMetadata (
6312+ {op1, op2},
6313+ disallowEmptyIntersection));
6314+ } catch (const std::exception &) {
6315+ }
6316+ }
6317+ }
6318+ }
6319+ }
6320+ }
6321+ }
6322+ }
62366323 }
62376324
62386325 if (horizTransforms.empty () || verticalTransforms.empty ()) {
@@ -6850,9 +6937,9 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToCompound(
68506937 }
68516938
68526939 const auto createOpsInTwoSteps =
6853- [&res, bestAccuracy,
6854- bestStepCount]( const std::vector<CoordinateOperationNNPtr> &ops1,
6855- const std::vector<CoordinateOperationNNPtr> &ops2) {
6940+ [&res, & bestAccuracy, &bestStepCount](
6941+ const std::vector<CoordinateOperationNNPtr> &ops1,
6942+ const std::vector<CoordinateOperationNNPtr> &ops2) {
68566943 std::vector<CoordinateOperationNNPtr> res2;
68576944 double bestAccuracy2 = -1 ;
68586945 size_t bestStepCount2 = 0 ;
@@ -6912,35 +6999,41 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToCompound(
69126999 (bestAccuracy < 0 || (bestAccuracy2 < bestAccuracy ||
69137000 (bestAccuracy2 == bestAccuracy &&
69147001 bestStepCount2 < bestStepCount)))) {
6915- res = std::move (res2);
7002+ bestAccuracy = bestAccuracy2;
7003+ bestStepCount = bestStepCount2;
7004+ res.insert (res.end (), res2.begin (), res2.end ());
69167005 }
69177006 };
69187007
6919- // If the promoted-to-3D source geographic CRS is not a known object,
6920- // transformations from it to another 3D one may not be relevant,
6921- // so try doing source -> geogDst 3D -> dest, if geogDst 3D is a known
6922- // object
6923- if (!srcGeog->identifiers ().empty () &&
6924- intermGeogSrc->identifiers ().empty () &&
6925- !intermGeogDst->identifiers ().empty () &&
6926- hasNonTrivialTargetTransf) {
6927- const auto opsSrcToIntermGeog = createOperations (
6928- sourceCRS, util::optional<common::DataEpoch>(), intermGeogDst,
6929- util::optional<common::DataEpoch>(), context);
6930- if (hasNonTrivialTransf (opsSrcToIntermGeog)) {
6931- createOpsInTwoSteps (opsSrcToIntermGeog, opsGeogToTarget);
7008+ if (res.empty () || intermGeogSrc->identifiers ().empty () ||
7009+ intermGeogDst->identifiers ().empty ()) {
7010+ // If the promoted-to-3D source geographic CRS is not a known
7011+ // object, transformations from it to another 3D one may not be
7012+ // relevant, so try doing source -> geogDst 3D -> dest, if geogDst
7013+ // 3D is a known object
7014+ if (!srcGeog->identifiers ().empty () &&
7015+ !intermGeogDst->identifiers ().empty () &&
7016+ hasNonTrivialTargetTransf) {
7017+ const auto opsSrcToIntermGeog = createOperations (
7018+ sourceCRS, util::optional<common::DataEpoch>(),
7019+ intermGeogDst, util::optional<common::DataEpoch>(),
7020+ context);
7021+ if (hasNonTrivialTransf (opsSrcToIntermGeog)) {
7022+ createOpsInTwoSteps (opsSrcToIntermGeog, opsGeogToTarget);
7023+ }
69327024 }
6933- }
6934- // Symmetrical situation with the promoted-to-3D target geographic CRS
6935- else if (!dstGeog->identifiers ().empty () &&
6936- intermGeogDst->identifiers ().empty () &&
6937- !intermGeogSrc->identifiers ().empty () &&
6938- hasNonTrivialSrcTransf) {
6939- const auto opsIntermGeogToDst = createOperations (
6940- intermGeogSrc, util::optional<common::DataEpoch>(), targetCRS,
6941- util::optional<common::DataEpoch>(), context);
6942- if (hasNonTrivialTransf (opsIntermGeogToDst)) {
6943- createOpsInTwoSteps (opsSrcToGeog, opsIntermGeogToDst);
7025+
7026+ // Symmetrical situation with the promoted-to-3D target geographic
7027+ // CRS
7028+ if (!dstGeog->identifiers ().empty () &&
7029+ !intermGeogSrc->identifiers ().empty () &&
7030+ hasNonTrivialSrcTransf) {
7031+ const auto opsIntermGeogToDst = createOperations (
7032+ intermGeogSrc, util::optional<common::DataEpoch>(),
7033+ targetCRS, util::optional<common::DataEpoch>(), context);
7034+ if (hasNonTrivialTransf (opsIntermGeogToDst)) {
7035+ createOpsInTwoSteps (opsSrcToGeog, opsIntermGeogToDst);
7036+ }
69447037 }
69457038 }
69467039
0 commit comments