diff --git a/src/iso19111/operation/coordinateoperationfactory.cpp b/src/iso19111/operation/coordinateoperationfactory.cpp index 7eaac8dcc3..6fd31293ec 100644 --- a/src/iso19111/operation/coordinateoperationfactory.cpp +++ b/src/iso19111/operation/coordinateoperationfactory.cpp @@ -1814,6 +1814,56 @@ CoordinateOperationFactory::Private::findOpsInRegistryDirectTo( // --------------------------------------------------------------------------- +static std::vector +findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory, + const crs::GeodeticCRS *crs, + const datum::GeodeticReferenceFrame *datum) { + std::vector candidates; + assert(datum); + const auto &ids = datum->identifiers(); + const auto &datumName = datum->nameStr(); + if (!ids.empty()) { + for (const auto &id : ids) { + const auto &authName = *(id->codeSpace()); + const auto &code = id->code(); + if (!authName.empty()) { + const auto crsIds = crs->identifiers(); + const auto tmpFactory = + (crsIds.size() == 1 && + *(crsIds.front()->codeSpace()) == authName) + ? io::AuthorityFactory::create( + authFactory->databaseContext(), authName) + .as_nullable() + : authFactory; + auto l_candidates = tmpFactory->createGeodeticCRSFromDatum( + authName, code, std::string()); + for (const auto &candidate : l_candidates) { + candidates.emplace_back(candidate); + } + } + } + } else if (datumName != "unknown" && datumName != "unnamed") { + auto matches = authFactory->createObjectsFromName( + datumName, + {io::AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, false, + 2); + if (matches.size() == 1) { + const auto &match = matches.front(); + if (datum->_isEquivalentTo( + match.get(), util::IComparable::Criterion::EQUIVALENT) && + !match->identifiers().empty()) { + return findCandidateGeodCRSForDatum( + authFactory, crs, + dynamic_cast( + match.get())); + } + } + } + return candidates; +} + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress // Look in the authority registry for operations from sourceCRS to targetCRS @@ -1834,9 +1884,59 @@ CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate( assert(authFactory); std::list> sourceIds; - std::list> targetIds; buildCRSIds(sourceCRS, context, sourceIds); + if (sourceIds.empty()) { + auto geodSrc = dynamic_cast(sourceCRS.get()); + if (geodSrc) { + const auto candidatesSrcGeod(findCandidateGeodCRSForDatum( + authFactory, geodSrc, + geodSrc + ->datumNonNull(authFactory->databaseContext().as_nullable()) + .get())); + std::vector res; + for (const auto &candidateSrcGeod : candidatesSrcGeod) { + if (candidateSrcGeod->coordinateSystem()->axisList().size() == + geodSrc->coordinateSystem()->axisList().size() && + ((dynamic_cast(sourceCRS.get()) != + nullptr) == + (dynamic_cast( + candidateSrcGeod.get()) != nullptr))) { + const auto opsWithIntermediate = + findsOpsInRegistryWithIntermediate( + candidateSrcGeod, targetCRS, context, + useCreateBetweenGeodeticCRSWithDatumBasedIntermediates); + if (!opsWithIntermediate.empty()) { + const auto opsFirst = createOperations( + sourceCRS, candidateSrcGeod, context); + for (const auto &opFirst : opsFirst) { + for (const auto &opSecond : opsWithIntermediate) { + try { + res.emplace_back( + ConcatenatedOperation:: + createComputeMetadata( + {opFirst, opSecond}, + disallowEmptyIntersection)); + } catch ( + const InvalidOperationEmptyIntersection &) { + } + } + } + if (!res.empty()) + return res; + } + } + } + } + return std::vector(); + } + + std::list> targetIds; buildCRSIds(targetCRS, context, targetIds); + if (targetIds.empty()) { + return applyInverse(findsOpsInRegistryWithIntermediate( + targetCRS, sourceCRS, context, + useCreateBetweenGeodeticCRSWithDatumBasedIntermediates)); + } const auto gridAvailabilityUse = context.context->getGridAvailabilityUse(); for (const auto &idSrc : sourceIds) { @@ -2685,56 +2785,6 @@ static bool hasIdentifiers(const CoordinateOperationNNPtr &op) { // --------------------------------------------------------------------------- -static std::vector -findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory, - const crs::GeodeticCRS *crs, - const datum::GeodeticReferenceFrame *datum) { - std::vector candidates; - assert(datum); - const auto &ids = datum->identifiers(); - const auto &datumName = datum->nameStr(); - if (!ids.empty()) { - for (const auto &id : ids) { - const auto &authName = *(id->codeSpace()); - const auto &code = id->code(); - if (!authName.empty()) { - const auto crsIds = crs->identifiers(); - const auto tmpFactory = - (crsIds.size() == 1 && - *(crsIds.front()->codeSpace()) == authName) - ? io::AuthorityFactory::create( - authFactory->databaseContext(), authName) - .as_nullable() - : authFactory; - auto l_candidates = tmpFactory->createGeodeticCRSFromDatum( - authName, code, std::string()); - for (const auto &candidate : l_candidates) { - candidates.emplace_back(candidate); - } - } - } - } else if (datumName != "unknown" && datumName != "unnamed") { - auto matches = authFactory->createObjectsFromName( - datumName, - {io::AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, false, - 2); - if (matches.size() == 1) { - const auto &match = matches.front(); - if (datum->_isEquivalentTo( - match.get(), util::IComparable::Criterion::EQUIVALENT) && - !match->identifiers().empty()) { - return findCandidateGeodCRSForDatum( - authFactory, crs, - dynamic_cast( - match.get())); - } - } - } - return candidates; -} - -// --------------------------------------------------------------------------- - void CoordinateOperationFactory::Private::setCRSs( CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) { diff --git a/test/unit/test_operationfactory.cpp b/test/unit/test_operationfactory.cpp index cdfe851663..d07f913a5c 100644 --- a/test/unit/test_operationfactory.cpp +++ b/test/unit/test_operationfactory.cpp @@ -1766,6 +1766,87 @@ TEST(operation, geogCRS_to_geogCRS_WGS84_to_GDA2020) { // --------------------------------------------------------------------------- +TEST(operation, geogCRS_to_geogCRS_with_intermediate_no_ids) { + + auto dbContext = DatabaseContext::create(); + auto authFactory = AuthorityFactory::create(dbContext, std::string()); + auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); + ctxt->setSpatialCriterion( + CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); + ctxt->setGridAvailabilityUse( + CoordinateOperationContext::GridAvailabilityUse:: + IGNORE_GRID_AVAILABILITY); + + auto objSrc = WKTParser().createFromWKT( + "GEOGCRS[\"input\",\n" + " DATUM[\"International Terrestrial Reference Frame 2014\",\n" + " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" + " LENGTHUNIT[\"metre\",1]],\n" + " ID[\"EPSG\",1165]],\n" + " PRIMEM[\"Greenwich\",0,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + " ID[\"EPSG\",8901]],\n" + " CS[ellipsoidal,3],\n" + " AXIS[\"longitude\",east,\n" + " ORDER[1],\n" + " ANGLEUNIT[\"degree\",0.0174532925199433,\n" + " ID[\"EPSG\",9122]]],\n" + " AXIS[\"latitude\",north,\n" + " ORDER[2],\n" + " ANGLEUNIT[\"degree\",0.0174532925199433,\n" + " ID[\"EPSG\",9122]]],\n" + " AXIS[\"ellipsoidal height (h)\",up,\n" + " ORDER[3],\n" + " LENGTHUNIT[\"metre\",1]]]"); + auto src = nn_dynamic_pointer_cast(objSrc); + ASSERT_TRUE(src != nullptr); + + auto objDest = WKTParser().createFromWKT( + "GEOGCRS[\"output\",\n" + " DATUM[\"Estonia 1997\",\n" + " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" + " LENGTHUNIT[\"metre\",1]],\n" + " ID[\"EPSG\",6180]],\n" + " PRIMEM[\"Greenwich\",0,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + " ID[\"EPSG\",8901]],\n" + " CS[ellipsoidal,3],\n" + " AXIS[\"longitude\",east,\n" + " ORDER[1],\n" + " ANGLEUNIT[\"degree\",0.0174532925199433,\n" + " ID[\"EPSG\",9122]]],\n" + " AXIS[\"latitude\",north,\n" + " ORDER[2],\n" + " ANGLEUNIT[\"degree\",0.0174532925199433,\n" + " ID[\"EPSG\",9122]]],\n" + " AXIS[\"ellipsoidal height (h)\",up,\n" + " ORDER[3],\n" + " LENGTHUNIT[\"metre\",1]]]"); + auto dest = nn_dynamic_pointer_cast(objDest); + ASSERT_TRUE(dest != nullptr); + + { + auto list = CoordinateOperationFactory::create()->createOperations( + NN_NO_CHECK(src), NN_NO_CHECK(dest), ctxt); + ASSERT_GE(list.size(), 1U); + // Test that a non-noop operation is returned + EXPECT_EQ( + list[0]->nameStr(), + "axis order change (geographic3D horizontal) + " + "Conversion from ITRF2014 (geog3D) to ITRF2014 (geocentric) + " + "ITRF2014 to ETRF2014 (1) + " + "Inverse of NKG_ETRF14 to ETRF2014 + " + "NKG_ETRF14 to ETRF96@2000.0 + " + "ETRF96@2000.0 to ETRF96@1997.56 + " + "Conversion from ETRS89 (geocentric) to ETRS89 (geog2D) + " + "Inverse of EST97 to ETRS89 (1) + " + "Null geographic offset from EST97 (geog2D) to EST97 (geog3D) + " + "axis order change (geographic3D horizontal)"); + } +} + +// --------------------------------------------------------------------------- + static ProjectedCRSNNPtr createUTM31_WGS84() { return ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326,