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 <