diff options
| -rw-r--r-- | include/proj/coordinateoperation.hpp | 4 | ||||
| -rw-r--r-- | src/apps/projinfo.cpp | 10 | ||||
| -rw-r--r-- | src/iso19111/c_api.cpp | 21 | ||||
| -rw-r--r-- | src/iso19111/coordinateoperation.cpp | 55 | ||||
| -rw-r--r-- | src/iso19111/factory.cpp | 3 | ||||
| -rw-r--r-- | src/proj.h | 5 | ||||
| -rw-r--r-- | test/unit/test_c_api.cpp | 57 | ||||
| -rw-r--r-- | test/unit/test_operation.cpp | 4 |
8 files changed, 133 insertions, 26 deletions
diff --git a/include/proj/coordinateoperation.hpp b/include/proj/coordinateoperation.hpp index c7459416..686a0c73 100644 --- a/include/proj/coordinateoperation.hpp +++ b/include/proj/coordinateoperation.hpp @@ -1759,6 +1759,10 @@ class PROJ_GCC_DLL CoordinateOperationContext { PROJ_DLL void setDesiredAccuracy(double accuracy); + PROJ_DLL void setAllowBallparkTransformations(bool allow); + + PROJ_DLL bool getAllowBallparkTransformations() const; + /** Specify how source and target CRS extent should be used to restrict * candidate operations (only taken into account if no explicit area of * interest is specified. */ diff --git a/src/apps/projinfo.cpp b/src/apps/projinfo.cpp index 10ce346f..85db14e2 100644 --- a/src/apps/projinfo.cpp +++ b/src/apps/projinfo.cpp @@ -705,6 +705,7 @@ static void outputOperations( ctxt->setIntermediateCRS(pivots); ctxt->setUsePROJAlternativeGridNames(usePROJGridAlternatives); ctxt->setDiscardSuperseded(!showSuperseded); + ctxt->setAllowBallparkTransformations(outputOpt.ballparkAllowed); list = CoordinateOperationFactory::create()->createOperations( nnSourceCRS, nnTargetCRS, ctxt); if (!spatialCriterionExplicitlySpecified && @@ -726,15 +727,6 @@ static void outputOperations( << std::endl; std::exit(1); } - if (!outputOpt.ballparkAllowed) { - std::vector<CoordinateOperationNNPtr> listNew; - for (const auto &op : list) { - if (!op->hasBallparkTransformation()) { - listNew.emplace_back(op); - } - } - list = std::move(listNew); - } if (outputOpt.quiet && !list.empty()) { outputObject(dbContext, list[0], allowUseIntermediateCRS, outputOpt); return; diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp index 5a3f0374..6b726f34 100644 --- a/src/iso19111/c_api.cpp +++ b/src/iso19111/c_api.cpp @@ -7518,6 +7518,27 @@ void PROJ_DLL proj_operation_factory_context_set_discard_superseded( // --------------------------------------------------------------------------- +/** \brief Set whether ballpark transformations are allowed. + * + * @param ctx PROJ context, or NULL for default context + * @param factory_ctx Operation factory context. must not be NULL + * @param allow set to TRUE to allow ballpark transformations. + * @since 7.1 + */ +void PROJ_DLL proj_operation_factory_context_set_allow_ballpark_transformations( + PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, int allow) { + SANITIZE_CTX(ctx); + assert(factory_ctx); + try { + factory_ctx->operationContext->setAllowBallparkTransformations(allow != + 0); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } +} + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress /** \brief Opaque object representing a set of operation results. */ struct PJ_OPERATION_LIST : PJ_OBJ_LIST { diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp index 822d09ac..8c47050d 100644 --- a/src/iso19111/coordinateoperation.cpp +++ b/src/iso19111/coordinateoperation.cpp @@ -10464,6 +10464,7 @@ struct CoordinateOperationContext::Private { std::vector<std::pair<std::string, std::string>> intermediateCRSAuthCodes_{}; bool discardSuperseded_ = true; + bool allowBallpark_ = true; }; //! @endcond @@ -10518,6 +10519,20 @@ void CoordinateOperationContext::setDesiredAccuracy(double accuracy) { // --------------------------------------------------------------------------- +/** \brief Return whether ballpark transformations are allowed */ +bool CoordinateOperationContext::getAllowBallparkTransformations() const { + return d->allowBallpark_; +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether ballpark transformations are allowed */ +void CoordinateOperationContext::setAllowBallparkTransformations(bool allow) { + d->allowBallpark_ = allow; +} + +// --------------------------------------------------------------------------- + /** \brief Set how source and target CRS extent should be used * when considering if a transformation can be used (only takes effect if * no area of interest is explicitly defined). @@ -11187,7 +11202,7 @@ struct FilterResults { const CoordinateOperationContext::SourceTargetCRSExtentUse sourceAndTargetCRSExtentUse; - bool hasOpThatContainsAreaOfInterest = false; + bool hasOpThatContainsAreaOfInterestAndNoGrid = false; std::vector<CoordinateOperationNNPtr> res{}; // ---------------------------------------------------------------------- @@ -11233,6 +11248,7 @@ struct FilterResults { STRICT_CONTAINMENT : context->getSpatialCriterion(); bool hasFoundOpWithExtent = false; + const bool allowBallpark = context->getAllowBallparkTransformations(); for (const auto &op : sourceList) { if (desiredAccuracy != 0) { const double accuracy = getAccuracy(op); @@ -11240,6 +11256,9 @@ struct FilterResults { continue; } } + if (!allowBallpark && op->hasBallparkTransformation()) { + continue; + } if (areaOfInterest) { bool emptyIntersection = false; auto extent = getExtent(op, true, emptyIntersection); @@ -11248,9 +11267,11 @@ struct FilterResults { hasFoundOpWithExtent = true; bool extentContains = extent->contains(NN_NO_CHECK(areaOfInterest)); - if (extentContains) { - if (!op->hasBallparkTransformation()) { - hasOpThatContainsAreaOfInterest = true; + if (!hasOpThatContainsAreaOfInterestAndNoGrid && + extentContains) { + if (!op->hasBallparkTransformation() && + op->gridsNeeded(nullptr, true).empty()) { + hasOpThatContainsAreaOfInterestAndNoGrid = true; } } if (spatialCriterion == @@ -11277,9 +11298,11 @@ struct FilterResults { !extent1 || extent->contains(NN_NO_CHECK(extent1)); bool extentContainsExtent2 = !extent2 || extent->contains(NN_NO_CHECK(extent2)); - if (extentContainsExtent1 && extentContainsExtent2) { - if (!op->hasBallparkTransformation()) { - hasOpThatContainsAreaOfInterest = true; + if (!hasOpThatContainsAreaOfInterestAndNoGrid && + extentContainsExtent1 && extentContainsExtent2) { + if (!op->hasBallparkTransformation() && + op->gridsNeeded(nullptr, true).empty()) { + hasOpThatContainsAreaOfInterestAndNoGrid = true; } } if (spatialCriterion == @@ -11313,6 +11336,9 @@ struct FilterResults { continue; } } + if (!allowBallpark && op->hasBallparkTransformation()) { + continue; + } res.emplace_back(op); } } @@ -11446,12 +11472,13 @@ struct FilterResults { // If we have more than one result, and than the last result is the // default "Ballpark geographic offset" or "Ballpark geocentric - // translation" - // operations we have synthetized, and that at least one operation - // has the desired area of interest, remove it as - // all previous results are necessarily better - if (hasOpThatContainsAreaOfInterest && res.size() > 1) { - const std::string &name = res.back()->nameStr(); + // translation" operations we have synthetized, and that at least one + // operation has the desired area of interest and does not require the + // use of grids, remove it as all previous results are necessarily + // better + if (hasOpThatContainsAreaOfInterestAndNoGrid && res.size() > 1) { + const auto &opLast = res.back(); + const std::string &name = opLast->nameStr(); if (name.find(BALLPARK_GEOGRAPHIC_OFFSET) != std::string::npos || name.find(NULL_GEOGRAPHIC_OFFSET) != std::string::npos || name.find(NULL_GEOCENTRIC_TRANSLATION) != std::string::npos || @@ -14772,7 +14799,7 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToCompound( } } - // If we didn't find a non-ballbark transformation between + // If we didn't find a non-ballpark transformation between // the 2 vertical CRS, then try through intermediate geographic CRS // For example // WGS 84 + EGM96 --> ETRS89 + Belfast height where diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp index 32ed6abb..aa3cd6cf 100644 --- a/src/iso19111/factory.cpp +++ b/src/iso19111/factory.cpp @@ -926,7 +926,8 @@ bool DatabaseContext::lookForGridAlternative(const std::string &officialName, bool &inverse) const { auto res = d->run( "SELECT proj_grid_name, proj_grid_format, inverse_direction FROM " - "grid_alternatives WHERE original_grid_name = ?", + "grid_alternatives WHERE original_grid_name = ? AND " + "proj_grid_name <> ''", {officialName}); if (res.empty()) { return false; @@ -1202,6 +1202,11 @@ void PROJ_DLL proj_operation_factory_context_set_discard_superseded( PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, int discard); +void PROJ_DLL proj_operation_factory_context_set_allow_ballpark_transformations( + PJ_CONTEXT *ctx, + PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, + int allow); + /* ------------------------------------------------------------------------- */ diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp index da95e523..9e477ef3 100644 --- a/test/unit/test_c_api.cpp +++ b/test/unit/test_c_api.cpp @@ -1600,6 +1600,63 @@ TEST_F(CApi, proj_create_operations_with_pivot) { // --------------------------------------------------------------------------- +TEST_F(CApi, proj_create_operations_allow_ballpark_transformations) { + auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); + ASSERT_NE(ctxt, nullptr); + ContextKeeper keeper_ctxt(ctxt); + + auto source_crs = proj_create_from_database( + m_ctxt, "EPSG", "4267", PJ_CATEGORY_CRS, false, nullptr); // NAD27 + ASSERT_NE(source_crs, nullptr); + ObjectKeeper keeper_source_crs(source_crs); + + auto target_crs = proj_create_from_database( + m_ctxt, "EPSG", "4258", PJ_CATEGORY_CRS, false, nullptr); // ETRS89 + ASSERT_NE(target_crs, nullptr); + ObjectKeeper keeper_target_crs(target_crs); + + proj_operation_factory_context_set_spatial_criterion( + m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); + + proj_operation_factory_context_set_grid_availability_use( + m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED); + + // Default: allowed implictly + { + auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); + ASSERT_NE(res, nullptr); + ObjListKeeper keeper_res(res); + + EXPECT_EQ(proj_list_get_count(res), 1); + } + + // Allow explictly + { + proj_operation_factory_context_set_allow_ballpark_transformations( + m_ctxt, ctxt, true); + + auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); + ASSERT_NE(res, nullptr); + ObjListKeeper keeper_res(res); + + EXPECT_EQ(proj_list_get_count(res), 1); + } + + // Disallow + { + proj_operation_factory_context_set_allow_ballpark_transformations( + m_ctxt, ctxt, false); + + auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); + ASSERT_NE(res, nullptr); + ObjListKeeper keeper_res(res); + + EXPECT_EQ(proj_list_get_count(res), 0); + } +} + +// --------------------------------------------------------------------------- + TEST_F(CApi, proj_context_set_database_path_null) { EXPECT_TRUE( diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp index 99e05dd1..854f813c 100644 --- a/test/unit/test_operation.cpp +++ b/test/unit/test_operation.cpp @@ -5109,7 +5109,7 @@ TEST(operation, geogCRS_to_geogCRS_init_IGNF_to_init_IGNF_context) { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( NN_CHECK_ASSERT(sourceCRS), NN_CHECK_ASSERT(targetCRS), ctxt); - ASSERT_EQ(list.size(), 1U); + ASSERT_EQ(list.size(), 2U); EXPECT_EQ(list[0]->nameStr(), "NOUVELLE TRIANGULATION DE LA FRANCE (NTF) vers RGF93 (ETRS89)"); @@ -6552,7 +6552,7 @@ TEST(operation, ETRS89_3D_to_proj_string_with_geoidgrids_nadgrids) { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( src, NN_NO_CHECK(dst), ctxt); - ASSERT_EQ(list.size(), 1U); + ASSERT_EQ(list.size(), 2U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " |
