From 621ce5e46396b7c99fb88f2ede4cbcc1da0eceaa Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 2 Jan 2023 19:26:33 +0100 Subject: [PATCH] proj_create_crs_to_crs_from_pj(): add a ONLY_BEST=YES option (fixes #3533) Also add a --only-best switch to cs2cs ONLY_BEST=YES/NO: (PROJ >= 9.2) Can be set to YES to cause PROJ to error out if the best transformation, known of PROJ, and usable by PROJ if all grids known and usable by PROJ were accessible, cannot be used. Best transformation should be understood as the transformation returned by :cpp:func:`proj_get_suggested_operation` if all known grids were accessible (either locally or through network). --- docs/source/apps/cs2cs.rst | 13 +++- .../development/reference/functions.rst | 8 +++ src/4D_api.cpp | 69 ++++++++++++++++++- src/apps/cs2cs.cpp | 10 ++- src/proj_internal.h | 1 + test/cli/testvarious | 28 ++++++++ test/cli/tv_out.dist | 15 ++++ 7 files changed, 141 insertions(+), 3 deletions(-) diff --git a/docs/source/apps/cs2cs.rst b/docs/source/apps/cs2cs.rst index fb6bb89768..b1499257c7 100644 --- a/docs/source/apps/cs2cs.rst +++ b/docs/source/apps/cs2cs.rst @@ -13,7 +13,8 @@ Synopsis | **cs2cs** [**-eEfIlrstvwW** [args]] | [[--area ] | [--bbox ]] - | [--authority ] [--no-ballpark] [--accuracy ] [--3d] + | [--authority ] [--3d] + | [--accuracy ] [--only-best] [--no-ballpark] | ([*+opt[=arg]* ...] [+to *+opt[=arg]* ...] | {source_crs} {target_crs}) | file ... @@ -166,6 +167,16 @@ The following control parameters can appear in any order: `south_lat` and `north_lat` in the [-90,90]. `west_long` is generally lower than `east_long`, except in the case where the area of interest crosses the antimeridian. +.. option:: --only-best + + .. versionadded:: 9.2.0 + + Error out if the best transformation, known of PROJ, and usable by PROJ if + all grids known and usable by PROJ were accessible, cannot be used. + Best transformation should be understood as the transformation returned by + :cpp:func:`proj_get_suggested_operation` if all known grids were + accessible (either locally or through network). + .. option:: --no-ballpark .. versionadded:: 8.0.0 diff --git a/docs/source/development/reference/functions.rst b/docs/source/development/reference/functions.rst index 38c81d865b..daf167a17d 100644 --- a/docs/source/development/reference/functions.rst +++ b/docs/source/development/reference/functions.rst @@ -201,6 +201,14 @@ paragraph for more details. - ALLOW_BALLPARK=YES/NO: can be set to NO to disallow the use of :term:`Ballpark transformation` in the candidate coordinate operations. + - ONLY_BEST=YES/NO: (PROJ >= 9.2) + Can be set to YES to cause PROJ to error out if the best + transformation, known of PROJ, and usable by PROJ if all grids known and + usable by PROJ were accessible, cannot be used. Best transformation should + be understood as the transformation returned by + :cpp:func:`proj_get_suggested_operation` if all known grids were + accessible (either locally or through network). + - FORCE_OVER=YES/NO: can be set to YES to force the ``+over`` flag on the transformation returned by this function. See :ref:`longitude_wrapping` diff --git a/src/4D_api.cpp b/src/4D_api.cpp index e02cbf73ca..1af36887cb 100644 --- a/src/4D_api.cpp +++ b/src/4D_api.cpp @@ -348,6 +348,28 @@ similarly, but prefers the 2D resp. 3D interfaces if available. if( res.xyzt.x != HUGE_VAL ) { return res; } + else if( P->errorIfBestTransformationNotAvailable ) { + std::string msg("Attempt to use coordinate operation "); + msg += alt.name; + msg += " failed"; + int gridUsed = proj_coordoperation_get_grid_used_count(P->ctx, alt.pj); + for( int i = 0; i < gridUsed; ++i ) + { + const char* gridName = ""; + int available = FALSE; + if( proj_coordoperation_get_grid_used( + P->ctx, alt.pj, i, &gridName, nullptr, nullptr, + nullptr, nullptr, nullptr, &available) && + !available ) + { + msg += ". Grid "; + msg += gridName; + msg += " is not available."; + } + } + pj_log(P->ctx, PJ_LOG_ERROR, msg.c_str()); + return res; + } if( iRetry == N_MAX_RETRY ) { break; } @@ -1899,6 +1921,7 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons double accuracy = -1; bool allowBallparkTransformations = true; bool forceOver = false; + bool errorIfBestTransformationNotAvailable = false; for (auto iter = options; iter && iter[0]; ++iter) { const char *value; if ((value = getOptionValue(*iter, "AUTHORITY="))) { @@ -1915,6 +1938,16 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons "Invalid value for ALLOW_BALLPARK option."); return nullptr; } + } else if ((value = getOptionValue(*iter, "ONLY_BEST="))) { + if( ci_equal(value, "yes") ) + errorIfBestTransformationNotAvailable = true; + else if( ci_equal(value, "no") ) + errorIfBestTransformationNotAvailable = false; + else { + ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR, + "Invalid value for ONLY_BEST option."); + return nullptr; + } } else if ((value = getOptionValue(*iter, "FORCE_OVER="))) { if (ci_equal(value, "yes")) { @@ -1963,7 +1996,7 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); proj_operation_factory_context_set_grid_availability_use( ctx, operation_ctx, - proj_context_is_network_enabled(ctx) ? + (errorIfBestTransformationNotAvailable || proj_context_is_network_enabled(ctx)) ? PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE: PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); @@ -1984,7 +2017,11 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons ctx->forceOver = forceOver; + const int old_debug_level = ctx->debug_level; + if( errorIfBestTransformationNotAvailable ) + ctx->debug_level = PJ_LOG_NONE; PJ* P = proj_list_get(ctx, op_list, 0); + ctx->debug_level = old_debug_level; assert(P); if( P == nullptr || op_count == 1 || @@ -1992,11 +2029,40 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons proj_get_type(target_crs) == PJ_TYPE_GEOCENTRIC_CRS ) { proj_list_destroy(op_list); ctx->forceOver = false; + + if( P != nullptr && + errorIfBestTransformationNotAvailable && + !proj_coordoperation_is_instantiable(ctx, P) ) + { + std::string msg("Attempt to use coordinate operation "); + msg += proj_get_name(P); + msg += " failed"; + int gridUsed = proj_coordoperation_get_grid_used_count(ctx, P); + for( int i = 0; i < gridUsed; ++i ) + { + const char* gridName = ""; + int available = FALSE; + if( proj_coordoperation_get_grid_used( + ctx, P, i, &gridName, nullptr, nullptr, + nullptr, nullptr, nullptr, &available) && + !available ) + { + msg += ". Grid "; + msg += gridName; + msg += " is not available."; + } + } + pj_log(ctx, PJ_LOG_ERROR, msg.c_str()); + } + return P; } + if( errorIfBestTransformationNotAvailable ) + ctx->debug_level = PJ_LOG_NONE; auto preparedOpList = pj_create_prepared_operations(ctx, source_crs, target_crs, op_list); + ctx->debug_level = old_debug_level; ctx->forceOver = false; proj_list_destroy(op_list); @@ -2016,6 +2082,7 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons return retP; } + P->errorIfBestTransformationNotAvailable = errorIfBestTransformationNotAvailable; P->alternativeCoordinateOperations = std::move(preparedOpList); // The returned P is rather dummy P->descr = "Set of coordinate operations"; diff --git a/src/apps/cs2cs.cpp b/src/apps/cs2cs.cpp index 7c565bb385..81d412db8c 100644 --- a/src/apps/cs2cs.cpp +++ b/src/apps/cs2cs.cpp @@ -78,7 +78,8 @@ static const char *oterr = "*\t*"; /* output line for unprojectable input */ static const char *usage = "%s\nusage: %s [-dDeEfIlrstvwW [args]]\n" " [[--area name_or_code] | [--bbox west_long,south_lat,east_long,north_lat]]\n" - " [--authority {name}] [--accuracy {accuracy}] [--no-ballpark] [--3d]\n" + " [--authority {name}] [--3d]\n" + " [--accuracy {accuracy}] [--only-best] [--no-ballpark]\n" " [+opt[=arg] ...] [+to +opt[=arg] ...] [file ...]\n"; static double (*informat)(const char *, @@ -418,6 +419,7 @@ int main(int argc, char **argv) { const char* authority = nullptr; double accuracy = -1; bool allowBallpark = true; + bool errorIfBestTransformationNotAvailable = false; bool promoteTo3D = false; /* process run line arguments */ @@ -484,6 +486,9 @@ int main(int argc, char **argv) { else if (strcmp(*argv, "--no-ballpark") == 0 ) { allowBallpark = false; } + else if (strcmp(*argv, "--only-best") == 0 ) { + errorIfBestTransformationNotAvailable = true; + } else if (strcmp(*argv, "--3d") == 0 ) { promoteTo3D = true; } @@ -893,6 +898,9 @@ int main(int argc, char **argv) { if( !allowBallpark ) { options.push_back("ALLOW_BALLPARK=NO"); } + if( errorIfBestTransformationNotAvailable ) { + options.push_back("ONLY_BEST=YES"); + } options.push_back(nullptr); transformation = proj_create_crs_to_crs_from_pj(nullptr, src, dst, pj_area, options.data()); diff --git a/src/proj_internal.h b/src/proj_internal.h index 119da01ae1..dc4944c429 100644 --- a/src/proj_internal.h +++ b/src/proj_internal.h @@ -594,6 +594,7 @@ struct PJconsts { **************************************************************************************/ std::vector alternativeCoordinateOperations{}; int iCurCoordOp = -1; + bool errorIfBestTransformationNotAvailable = false; /************************************************************************************* diff --git a/test/cli/testvarious b/test/cli/testvarious index 3e960bf1b8..454fd69df1 100755 --- a/test/cli/testvarious +++ b/test/cli/testvarious @@ -1082,6 +1082,34 @@ PROJ_DISPLAY_PROGRAM_NAME=NO $EXE -W10 +proj=latlong +datum=WGS84 +to +proj=latl 0 0 EOF +echo "##############################################################" >> ${OUT} +echo "Test cs2cs --only-best (working)" >> ${OUT} +# +$EXE --only-best NTF RGF93 -E >>${OUT} 2>&1 <> ${OUT} +echo "Test cs2cs --only-best (grid missing)" >> ${OUT} +# +$EXE --only-best EPSG:4326+3855 EPSG:4979 -E >>${OUT} 2>&1 <> ${OUT} +echo "Test cs2cs --only-best (grid missing)" >> ${OUT} +# +$EXE --only-best NAD27 NAD83 -E >>${OUT} 2>&1 <> ${OUT} +echo "Test cs2cs --only-best --no-ballpark (grid missing)" >> ${OUT} +# +$EXE --only-best --no-ballpark EPSG:4326+3855 EPSG:4979 -E >>${OUT} 2>&1 <