diff options
| author | Kristian Evers <kristianevers@gmail.com> | 2020-12-21 17:28:48 +0100 |
|---|---|---|
| committer | Kristian Evers <kristianevers@gmail.com> | 2020-12-21 17:28:48 +0100 |
| commit | 5aad0d25f8423b8b88a716d0333c7bd19f6184c7 (patch) | |
| tree | f0a248ef08fb51ef0ec29178ef41fd4168d4c85d /src | |
| parent | c3efbd23a5bf26f1dfd5bc55ae3488d5665ace98 (diff) | |
| parent | 1cafe3e602d3f697c8d2daaa9b634f3ad23b0d53 (diff) | |
| download | PROJ-5aad0d25f8423b8b88a716d0333c7bd19f6184c7.tar.gz PROJ-5aad0d25f8423b8b88a716d0333c7bd19f6184c7.zip | |
Merge remote-tracking branch 'osgeo/master'
Diffstat (limited to 'src')
152 files changed, 21634 insertions, 17541 deletions
diff --git a/src/4D_api.cpp b/src/4D_api.cpp index d6eb901d..909c3c32 100644 --- a/src/4D_api.cpp +++ b/src/4D_api.cpp @@ -176,7 +176,8 @@ double proj_roundtrip (PJ *P, PJ_DIRECTION direction, int n, PJ_COORD *coord) { return HUGE_VAL; if (n < 1) { - proj_errno_set (P, EINVAL); + proj_log_error(P, _("n should be >= 1")); + proj_errno_set (P, PROJ_ERR_OTHER_API_MISUSE); return HUGE_VAL; } @@ -294,7 +295,7 @@ similarly, but prefers the 2D resp. 3D interfaces if available. if( iRetry > 0 ) { const int oldErrno = proj_errno_reset(P); if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { - pj_log(P->ctx, PJ_LOG_DEBUG, proj_errno_string(oldErrno)); + pj_log(P->ctx, PJ_LOG_DEBUG, proj_context_errno_string(P->ctx, oldErrno)); } pj_log(P->ctx, PJ_LOG_DEBUG, "Did not result in valid result. " @@ -312,7 +313,7 @@ similarly, but prefers the 2D resp. 3D interfaces if available. } PJ_COORD res = direction == PJ_FWD ? pj_fwd4d( coord, alt.pj ) : pj_inv4d( coord, alt.pj ); - if( proj_errno(alt.pj) == PJD_ERR_NETWORK_ERROR ) { + if( proj_errno(alt.pj) == PROJ_ERR_OTHER_NETWORK_ERROR ) { return proj_coord_error (); } if( res.xyzt.x != HUGE_VAL ) { @@ -359,21 +360,14 @@ similarly, but prefers the 2D resp. 3D interfaces if available. } } - proj_errno_set (P, EINVAL); + proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_NO_OPERATION); return proj_coord_error (); } - switch (direction) { - case PJ_FWD: - return pj_fwd4d (coord, P); - case PJ_INV: - return pj_inv4d (coord, P); - default: - break; - } - - proj_errno_set (P, EINVAL); - return proj_coord_error (); + if (direction == PJ_FWD) + return pj_fwd4d (coord, P); + else + return pj_inv4d (coord, P); } @@ -383,18 +377,43 @@ int proj_trans_array (PJ *P, PJ_DIRECTION direction, size_t n, PJ_COORD *coord) /****************************************************************************** Batch transform an array of PJ_COORD. + Performs transformation on all points, even if errors occur on some points. + + Individual points that fail to transform will have their components set to + HUGE_VAL + Returns 0 if all coordinates are transformed without error, otherwise - returns error number. + returns a precise error number if all coordinates that fail to transform + for the same reason, or a generic error code if they fail for different + reasons. ******************************************************************************/ size_t i; + int retErrno = 0; + bool hasSetRetErrno = false; + bool sameRetErrno = true; for (i = 0; i < n; i++) { + proj_context_errno_set(P->ctx, 0); coord[i] = proj_trans (P, direction, coord[i]); - if (proj_errno(P)) - return proj_errno (P); - } + int thisErrno = proj_errno(P); + if( thisErrno != 0 ) + { + if( !hasSetRetErrno ) + { + retErrno = thisErrno; + hasSetRetErrno = true; + } + else if( sameRetErrno && retErrno != thisErrno ) + { + sameRetErrno = false; + retErrno = PROJ_ERR_COORD_TRANSFM; + } + } + } - return 0; + proj_context_errno_set(P->ctx, retErrno); + + return retErrno; } @@ -500,9 +519,6 @@ size_t proj_trans_generic ( break; case PJ_IDENT: return nmin; - default: - proj_errno_set (P, EINVAL); - return 0; } /* Arrays of length==0 are broadcast as the constant 0 */ @@ -771,7 +787,7 @@ PJ *pj_create_internal (PJ_CONTEXT *ctx, const char *definition) { n = strlen (definition); args = (char *) malloc (n + 1); if (nullptr==args) { - proj_context_errno_set(ctx, ENOMEM); + proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/); return nullptr; } strcpy (args, definition); @@ -779,14 +795,14 @@ PJ *pj_create_internal (PJ_CONTEXT *ctx, const char *definition) { argc = pj_trim_argc (args); if (argc==0) { free (args); - proj_context_errno_set(ctx, PJD_ERR_NO_ARGS); + proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG); return nullptr; } argv = pj_trim_argv (argc, args); if (!argv) { free(args); - proj_context_errno_set(ctx, ENOMEM); + proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/); return nullptr; } @@ -821,14 +837,14 @@ indicator, as in {"+proj=utm", "+zone=32"}, or leave it out, as in {"proj=utm", if (nullptr==ctx) ctx = pj_get_default_ctx (); if (nullptr==argv) { - proj_context_errno_set(ctx, PJD_ERR_NO_ARGS); + proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG); return nullptr; } /* We assume that free format is used, and build a full proj_create compatible string */ c = pj_make_args (argc, argv); if (nullptr==c) { - proj_context_errno_set(ctx, ENOMEM); + proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP /* ENOMEM */); return nullptr; } @@ -849,14 +865,14 @@ Same as proj_create_argv() but calls pj_create_internal() instead of proj_create if (nullptr==ctx) ctx = pj_get_default_ctx (); if (nullptr==argv) { - proj_context_errno_set(ctx, PJD_ERR_NO_ARGS); + proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG); return nullptr; } /* We assume that free format is used, and build a full proj_create compatible string */ c = pj_make_args (argc, argv); if (nullptr==c) { - proj_context_errno_set(ctx, ENOMEM); + proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/); return nullptr; } @@ -1286,10 +1302,24 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons } const char* authority = nullptr; + double accuracy = -1; + bool allowBallparkTransformations = true; for (auto iter = options; iter && iter[0]; ++iter) { const char *value; if ((value = getOptionValue(*iter, "AUTHORITY="))) { authority = value; + } else if ((value = getOptionValue(*iter, "ACCURACY="))) { + accuracy = pj_atof(value); + } else if ((value = getOptionValue(*iter, "ALLOW_BALLPARK="))) { + if( ci_equal(value, "yes") ) + allowBallparkTransformations = true; + else if( ci_equal(value, "no") ) + allowBallparkTransformations = false; + else { + ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR, + "Invalid value for ALLOW_BALLPARK option."); + return nullptr; + } } else { std::string msg("Unknown option :"); msg += *iter; @@ -1303,6 +1333,14 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons return nullptr; } + proj_operation_factory_context_set_allow_ballpark_transformations( + ctx, operation_ctx, allowBallparkTransformations); + + if( accuracy >= 0 ) { + proj_operation_factory_context_set_desired_accuracy(ctx, operation_ctx, + accuracy); + } + if( area && area->bbox_set ) { proj_operation_factory_context_set_area_of_interest( ctx, diff --git a/src/Makefile.am b/src/Makefile.am index 83ee4adc..16eca5de 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -63,7 +63,23 @@ libproj_la_SOURCES = \ iso19111/crs.cpp \ iso19111/datum.cpp \ iso19111/coordinatesystem.cpp \ - iso19111/coordinateoperation.cpp \ + iso19111/operation/concatenatedoperation.cpp \ + iso19111/operation/coordinateoperation_internal.hpp \ + iso19111/operation/coordinateoperation_private.hpp \ + iso19111/operation/coordinateoperationfactory.cpp \ + iso19111/operation/conversion.cpp \ + iso19111/operation/esriparammappings.hpp \ + iso19111/operation/esriparammappings.cpp \ + iso19111/operation/operationmethod_private.hpp \ + iso19111/operation/oputils.hpp \ + iso19111/operation/oputils.cpp \ + iso19111/operation/parammappings.hpp \ + iso19111/operation/parammappings.cpp \ + iso19111/operation/projbasedoperation.cpp \ + iso19111/operation/singleoperation.cpp \ + iso19111/operation/transformation.cpp \ + iso19111/operation/vectorofvaluesparams.hpp \ + iso19111/operation/vectorofvaluesparams.cpp \ iso19111/io.cpp \ iso19111/internal.cpp \ iso19111/factory.cpp \ diff --git a/src/aasincos.cpp b/src/aasincos.cpp index c4314c67..ca33663b 100644 --- a/src/aasincos.cpp +++ b/src/aasincos.cpp @@ -14,7 +14,7 @@ aasin(PJ_CONTEXT *ctx,double v) { if ((av = fabs(v)) >= 1.) { if (av > ONE_TOL) - proj_context_errno_set( ctx, PJD_ERR_ACOS_ASIN_ARG_TOO_LARGE ); + proj_context_errno_set( ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN ); return (v < 0. ? -M_HALFPI : M_HALFPI); } return asin(v); @@ -26,7 +26,7 @@ aacos(PJ_CONTEXT *ctx, double v) { if ((av = fabs(v)) >= 1.) { if (av > ONE_TOL) - proj_context_errno_set( ctx, PJD_ERR_ACOS_ASIN_ARG_TOO_LARGE ); + proj_context_errno_set( ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN ); return (v < 0. ? M_PI : 0.); } return acos(v); diff --git a/src/apps/cs2cs.cpp b/src/apps/cs2cs.cpp index 409a5ef3..5542a282 100644 --- a/src/apps/cs2cs.cpp +++ b/src/apps/cs2cs.cpp @@ -38,6 +38,7 @@ #include <cassert> #include <iostream> #include <string> +#include <vector> #include <proj/io.hpp> #include <proj/metadata.hpp> @@ -77,7 +78,7 @@ 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}]\n" + " [--authority {name}] [--accuracy {accuracy}] [--no-ballpark]\n" " [+opt[=arg] ...] [+to +opt[=arg] ...] [file ...]\n"; static double (*informat)(const char *, @@ -374,6 +375,8 @@ int main(int argc, char **argv) { ExtentPtr bboxFilter; std::string area; const char* authority = nullptr; + double accuracy = -1; + bool allowBallpark = true; /* process run line arguments */ while (--argc > 0) { /* collect run line arguments */ @@ -412,6 +415,15 @@ int main(int argc, char **argv) { std::exit(1); } } + else if (strcmp(*argv, "--accuracy") == 0 ) { + ++argv; + --argc; + if( argc == 0 ) { + emess(1, "missing argument for --accuracy"); + std::exit(1); + } + accuracy = c_locale_stod(*argv); + } else if (strcmp(*argv, "--authority") == 0 ) { ++argv; --argc; @@ -421,6 +433,9 @@ int main(int argc, char **argv) { } authority = *argv; } + else if (strcmp(*argv, "--no-ballpark") == 0 ) { + allowBallpark = false; + } else if (**argv == '-') { for (arg = *argv;;) { switch (*++arg) { @@ -773,14 +788,24 @@ int main(int argc, char **argv) { } std::string authorityOption; /* keep this variable in this outer scope ! */ - const char* options[2] = { nullptr, nullptr }; + std::string accuracyOption; /* keep this variable in this outer scope ! */ + std::vector<const char*> options; if( authority ) { authorityOption = "AUTHORITY="; authorityOption += authority; - options[0] = authorityOption.data(); + options.push_back(authorityOption.data()); + } + if( accuracy >= 0 ) { + accuracyOption = "ACCURACY="; + accuracyOption += toString(accuracy); + options.push_back(accuracyOption.data()); + } + if( !allowBallpark ) { + options.push_back("ALLOW_BALLPARK=NO"); } + options.push_back(nullptr); transformation = proj_create_crs_to_crs_from_pj(nullptr, src, dst, - pj_area, options); + pj_area, options.data()); proj_destroy(src); proj_destroy(dst); diff --git a/src/apps/gie.cpp b/src/apps/gie.cpp index b504b922..95c7da34 100644 --- a/src/apps/gie.cpp +++ b/src/apps/gie.cpp @@ -951,7 +951,15 @@ Tell GIE what to expect, when transforming the ACCEPTed input if (expect_failure_with_errno) { if (proj_errno (T.P)==expect_failure_with_errno) return another_succeeding_failure (); - fprintf (T.fout, "errno=%d, expected=%d\n", proj_errno (T.P), expect_failure_with_errno); + //fprintf (T.fout, "errno=%d, expected=%d\n", proj_errno (T.P), expect_failure_with_errno); + banner (T.operation); + errmsg (3, "%serrno=%s (%d), expected=%d at line %d\n", + delim, + err_const_from_errno(proj_errno(T.P)), + proj_errno (T.P), + expect_failure_with_errno, + F->lineno + ); return another_failing_failure (); } @@ -1107,85 +1115,32 @@ static int dispatch (const char *cmnd, const char *args) { namespace { // anonymous namespace struct errno_vs_err_const {const char *the_err_const; int the_errno;}; static const struct errno_vs_err_const lookup[] = { - {"pjd_err_no_args" , -1}, - {"pjd_err_no_option_in_init_file" , -2}, - {"pjd_err_no_colon_in_init_string" , -3}, - {"pjd_err_proj_not_named" , -4}, - {"pjd_err_unknown_projection_id" , -5}, - {"pjd_err_invalid_eccentricity" , -6}, - {"pjd_err_unknown_unit_id" , -7}, - {"pjd_err_invalid_boolean_param" , -8}, - {"pjd_err_unknown_ellp_param" , -9}, - {"pjd_err_rev_flattening_is_zero" , -10}, - {"pjd_err_ref_rad_larger_than_90" , -11}, - {"pjd_err_es_less_than_zero" , -12}, - {"pjd_err_major_axis_not_given" , -13}, - {"pjd_err_lat_or_lon_exceed_limit" , -14}, - {"pjd_err_invalid_x_or_y" , -15}, - {"pjd_err_wrong_format_dms_value" , -16}, - {"pjd_err_non_conv_inv_meri_dist" , -17}, - {"pjd_err_non_conv_sinhpsi2tanphi" , -18}, - {"pjd_err_acos_asin_arg_too_large" , -19}, - {"pjd_err_tolerance_condition" , -20}, - {"pjd_err_conic_lat_equal" , -21}, - {"pjd_err_lat_larger_than_90" , -22}, - {"pjd_err_lat1_is_zero" , -23}, - {"pjd_err_lat_ts_larger_than_90" , -24}, - {"pjd_err_control_point_no_dist" , -25}, - {"pjd_err_no_rotation_proj" , -26}, - {"pjd_err_w_or_m_zero_or_less" , -27}, - {"pjd_err_lsat_not_in_range" , -28}, - {"pjd_err_path_not_in_range" , -29}, - {"pjd_err_invalid_h" , -30}, - {"pjd_err_k_less_than_zero" , -31}, - {"pjd_err_lat_1_or_2_zero_or_90" , -32}, - {"pjd_err_lat_0_or_alpha_eq_90" , -33}, - {"pjd_err_ellipsoid_use_required" , -34}, - {"pjd_err_invalid_utm_zone" , -35}, - {"" , -36}, /* no longer used */ - {"pjd_err_failed_to_find_proj" , -37}, - {"pjd_err_failed_to_load_grid" , -38}, - {"pjd_err_invalid_m_or_n" , -39}, - {"pjd_err_n_out_of_range" , -40}, - {"pjd_err_lat_1_2_unspecified" , -41}, - {"pjd_err_abs_lat1_eq_abs_lat2" , -42}, - {"pjd_err_lat_0_half_pi_from_mean" , -43}, - {"pjd_err_unparseable_cs_def" , -44}, - {"pjd_err_geocentric" , -45}, - {"pjd_err_unknown_prime_meridian" , -46}, - {"pjd_err_axis" , -47}, - {"pjd_err_grid_area" , -48}, - {"pjd_err_invalid_sweep_axis" , -49}, - {"pjd_err_malformed_pipeline" , -50}, - {"pjd_err_unit_factor_less_than_0" , -51}, - {"pjd_err_invalid_scale" , -52}, - {"pjd_err_non_convergent" , -53}, - {"pjd_err_missing_args" , -54}, - {"pjd_err_lat_0_is_zero" , -55}, - {"pjd_err_ellipsoidal_unsupported" , -56}, - {"pjd_err_too_many_inits" , -57}, - {"pjd_err_invalid_arg" , -58}, - {"pjd_err_inconsistent_unit" , -59}, - {"pjd_err_mutually_exclusive_args" , -60}, - {"pjd_err_generic_error" , -61}, - {"pjd_err_network_error" , -62}, - {"pjd_err_dont_skip" , 5555}, - {"pjd_err_unknown" , 9999}, - {"pjd_err_enomem" , ENOMEM}, + + { "invalid_op", PROJ_ERR_INVALID_OP }, + { "invalid_op_wrong_syntax", PROJ_ERR_INVALID_OP_WRONG_SYNTAX }, + { "invalid_op_missing_arg", PROJ_ERR_INVALID_OP_MISSING_ARG }, + { "invalid_op_illegal_arg_value", PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE }, + { "invalid_op_mutually_exclusive_args", PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS }, + { "invalid_op_file_not_found_or_invalid", PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID }, + { "coord_transfm", PROJ_ERR_COORD_TRANSFM }, + { "coord_transfm_invalid_coord", PROJ_ERR_COORD_TRANSFM_INVALID_COORD }, + { "coord_transfm_outside_projection_domain", PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN }, + { "coord_transfm_no_operation", PROJ_ERR_COORD_TRANSFM_NO_OPERATION }, + { "coord_transfm_outside_grid", PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID }, + { "coord_transfm_grid_at_nodata", PROJ_ERR_COORD_TRANSFM_GRID_AT_NODATA }, + { "other", PROJ_ERR_OTHER }, + { "api_misuse", PROJ_ERR_OTHER_API_MISUSE }, + { "no_inverse_op", PROJ_ERR_OTHER_NO_INVERSE_OP }, + { "network_error", PROJ_ERR_OTHER_NETWORK_ERROR }, }; } // anonymous namespace -static const struct errno_vs_err_const unknown = {"PJD_ERR_UNKNOWN", 9999}; - - static int list_err_codes (void) { int i; const int n = sizeof lookup / sizeof lookup[0]; for (i = 0; i < n; i++) { - if (9999==lookup[i].the_errno) - break; - fprintf (T.fout, "%25s (%2.2d): %s\n", lookup[i].the_err_const + 8, + fprintf (T.fout, "%25s (%2.2d): %s\n", lookup[i].the_err_const, lookup[i].the_errno, proj_errno_string (lookup[i].the_errno)); } return 0; @@ -1198,9 +1153,9 @@ static const char *err_const_from_errno (int err) { for (i = 0; i < n; i++) { if (err==lookup[i].the_errno) - return lookup[i].the_err_const + 8; + return lookup[i].the_err_const; } - return unknown.the_err_const; + return "unknown"; } @@ -1226,14 +1181,6 @@ static int errno_from_err_const (const char *err_const) { /* Else try to find a matching identifier */ len = strlen (tolower_err_const); - /* First try to find a match excluding the PJD_ERR_ prefix */ - for (i = 0; i < n; i++) { - if (strlen(lookup[i].the_err_const) > 8 && - 0==strncmp (lookup[i].the_err_const + 8, err_const, len)) - return lookup[i].the_errno; - } - - /* If that did not work, try with the full name */ for (i = 0; i < n; i++) { if (0==strncmp (lookup[i].the_err_const, err_const, len)) return lookup[i].the_errno; diff --git a/src/apps/projinfo.cpp b/src/apps/projinfo.cpp index da885fbb..8d389019 100644 --- a/src/apps/projinfo.cpp +++ b/src/apps/projinfo.cpp @@ -95,7 +95,9 @@ static void usage() { << std::endl << " [--pivot-crs always|if_no_direct_transformation|" << "never|{auth:code[,auth:code]*}]" << std::endl - << " [--show-superseded] [--hide-ballpark]" << std::endl + << " [--show-superseded] [--hide-ballpark] " + "[--accuracy {accuracy}]" + << std::endl << " [--allow-ellipsoidal-height-as-vertical-crs]" << std::endl << " [--boundcrs-to-wgs84]" << std::endl @@ -672,8 +674,8 @@ static void outputOperations( CoordinateOperationContext::IntermediateCRSUse allowUseIntermediateCRS, const std::vector<std::pair<std::string, std::string>> &pivots, const std::string &authority, bool usePROJGridAlternatives, - bool showSuperseded, bool promoteTo3D, const OutputOptions &outputOpt, - bool summary) { + bool showSuperseded, bool promoteTo3D, double minimumAccuracy, + const OutputOptions &outputOpt, bool summary) { auto sourceObj = buildObject(dbContext, sourceCRSStr, "crs", "source CRS", false, CoordinateOperationContext::IntermediateCRSUse::NEVER, @@ -715,6 +717,9 @@ static void outputOperations( ctxt->setUsePROJAlternativeGridNames(usePROJGridAlternatives); ctxt->setDiscardSuperseded(!showSuperseded); ctxt->setAllowBallparkTransformations(outputOpt.ballparkAllowed); + if (minimumAccuracy >= 0) { + ctxt->setDesiredAccuracy(minimumAccuracy); + } list = CoordinateOperationFactory::create()->createOperations( nnSourceCRS, nnTargetCRS, ctxt); if (!spatialCriterionExplicitlySpecified && @@ -819,6 +824,7 @@ int main(int argc, char **argv) { bool identify = false; bool showSuperseded = false; bool promoteTo3D = false; + double minimumAccuracy = -1; for (int i = 1; i < argc; i++) { std::string arg(argv[i]); @@ -934,6 +940,9 @@ int main(int argc, char **argv) { << ", " << e.what() << std::endl; usage(); } + } else if (arg == "--accuracy" && i + 1 < argc) { + i++; + minimumAccuracy = c_locale_stod(argv[i]); } else if (arg == "--area" && i + 1 < argc) { i++; area = argv[i]; @@ -1338,12 +1347,12 @@ int main(int argc, char **argv) { } try { - outputOperations(dbContext, sourceCRSStr, targetCRSStr, bboxFilter, - spatialCriterion, - spatialCriterionExplicitlySpecified, crsExtentUse, - gridAvailabilityUse, allowUseIntermediateCRS, - pivots, authority, usePROJGridAlternatives, - showSuperseded, promoteTo3D, outputOpt, summary); + outputOperations( + dbContext, sourceCRSStr, targetCRSStr, bboxFilter, + spatialCriterion, spatialCriterionExplicitlySpecified, + crsExtentUse, gridAvailabilityUse, allowUseIntermediateCRS, + pivots, authority, usePROJGridAlternatives, showSuperseded, + promoteTo3D, minimumAccuracy, outputOpt, summary); } catch (const std::exception &e) { std::cerr << "outputOperations() failed with: " << e.what() << std::endl; diff --git a/src/conversions/axisswap.cpp b/src/conversions/axisswap.cpp index 1aa339c3..682f74ef 100644 --- a/src/conversions/axisswap.cpp +++ b/src/conversions/axisswap.cpp @@ -174,13 +174,16 @@ PJ *CONVERSION(axisswap,0) { unsigned int i, j, n = 0; if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = (void *) Q; /* +order and +axis are mutually exclusive */ if ( !pj_param_exists(P->params, "order") == !pj_param_exists(P->params, "axis") ) - return pj_default_destructor(P, PJD_ERR_AXIS); + { + proj_log_error(P, _("order and axis parameters are mutually exclusive.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS); + } /* fill axis list with indices from 4-7 to simplify duplicate search further down */ for (i=0; i<4; i++) { @@ -196,8 +199,8 @@ PJ *CONVERSION(axisswap,0) { /* check that all characters are valid */ for (i=0; i<strlen(order); i++) if (strchr("1234-,", order[i]) == nullptr) { - proj_log_error(P, "axisswap: unknown axis '%c'", order[i]); - return pj_default_destructor(P, PJD_ERR_AXIS); + proj_log_error(P, _("unknown axis '%c'"), order[i]); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } /* read axes numbers and signs */ @@ -206,8 +209,8 @@ PJ *CONVERSION(axisswap,0) { while ( *s != '\0' && n < 4 ) { Q->axis[n] = abs(atoi(s))-1; if (Q->axis[n] > 3) { - proj_log_error(P, "axisswap: invalid axis '%d'", Q->axis[n]); - return pj_default_destructor(P, PJD_ERR_AXIS); + proj_log_error(P, _("invalid axis '%d'"), Q->axis[n]); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->sign[n++] = sign(atoi(s)); while ( *s != '\0' && *s != ',' ) @@ -247,8 +250,8 @@ PJ *CONVERSION(axisswap,0) { Q->axis[i] = 2; break; default: - proj_log_error(P, "axisswap: unknown axis '%c'", P->axis[i]); - return pj_default_destructor(P, PJD_ERR_AXIS); + proj_log_error(P, _("unknown axis '%c'"), P->axis[i]); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } n = 3; @@ -260,8 +263,8 @@ PJ *CONVERSION(axisswap,0) { if (i==j) continue; if (Q->axis[i] == Q->axis[j]) { - proj_log_error(P, "swapaxis: duplicate axes specified"); - return pj_default_destructor(P, PJD_ERR_AXIS); + proj_log_error(P, _("swapaxis: duplicate axes specified")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } @@ -282,8 +285,8 @@ PJ *CONVERSION(axisswap,0) { if (P->fwd4d == nullptr && P->fwd3d == nullptr && P->fwd == nullptr) { - proj_log_error(P, "swapaxis: bad axis order"); - return pj_default_destructor(P, PJD_ERR_AXIS); + proj_log_error(P, _("swapaxis: bad axis order")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (pj_param(P->ctx, P->params, "tangularunits").i) { diff --git a/src/conversions/set.cpp b/src/conversions/set.cpp index 2f30bda8..fa8c3eb7 100644 --- a/src/conversions/set.cpp +++ b/src/conversions/set.cpp @@ -42,7 +42,7 @@ PJ *OPERATION(set, 0) { auto set = static_cast<struct Set*>(calloc (1, sizeof(struct Set))); P->opaque = set; if (nullptr==P->opaque) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); if (pj_param_exists(P->params, "v_1")) { diff --git a/src/conversions/topocentric.cpp b/src/conversions/topocentric.cpp index f6f328ad..bbe52400 100644 --- a/src/conversions/topocentric.cpp +++ b/src/conversions/topocentric.cpp @@ -78,7 +78,7 @@ PJ *CONVERSION(topocentric,1) { /*********************************************************************/ struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = static_cast<void *>(Q); // The topocentric origin can be specified either in geocentric coordinates @@ -97,26 +97,30 @@ PJ *CONVERSION(topocentric,1) { const auto hash0 = pj_param_exists(P->params, "h_0"); if( !hasX0 && !hasLon0 ) { - return pj_default_destructor(P, PJD_ERR_MISSING_ARGS); + proj_log_error(P, _("missing X_0 or lon_0")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if ( (hasX0 || hasY0 || hasZ0) && (hasLon0 || hasLat0 || hash0) ) { - return pj_default_destructor(P, PJD_ERR_MUTUALLY_EXCLUSIVE_ARGS); + proj_log_error(P, _("(X_0,Y_0,Z_0) and (lon_0,lat_0,h_0) are mutually exclusive")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS); } if( hasX0 && (!hasY0 || !hasZ0) ) { - return pj_default_destructor(P, PJD_ERR_MISSING_ARGS); + proj_log_error(P, _("missing Y_0 and/or Z_0")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if( hasLon0 && !hasLat0 ) // allow missing h_0 { - return pj_default_destructor(P, PJD_ERR_MISSING_ARGS); + proj_log_error(P, _("missing lat_0")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } // Pass a dummy ellipsoid definition that will be overridden just afterwards PJ* cart = proj_create(P->ctx, "+proj=cart +a=1"); if (cart == nullptr) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); /* inherit ellipsoid definition from P to cart */ pj_inherit_ellipsoid_def (P, cart); diff --git a/src/conversions/unitconvert.cpp b/src/conversions/unitconvert.cpp index 61bccbf1..187acf17 100644 --- a/src/conversions/unitconvert.cpp +++ b/src/conversions/unitconvert.cpp @@ -443,7 +443,7 @@ PJ *CONVERSION(unitconvert,0) { int z_out_is_linear = -1; /* unknown */ if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = (void *) Q; P->fwd4d = forward_4d; @@ -473,7 +473,10 @@ PJ *CONVERSION(unitconvert,0) { } else { f = pj_param (P->ctx, P->params, "dxy_in").f; if (f == 0.0 || 1.0 / f == 0.0) - return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); + { + proj_log_error(P, _("unknown xy_in unit")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } } Q->xy_factor = f; if (normalized_name != nullptr) { @@ -492,7 +495,10 @@ PJ *CONVERSION(unitconvert,0) { } else { f = pj_param (P->ctx, P->params, "dxy_out").f; if (f == 0.0 || 1.0 / f == 0.0) - return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); + { + proj_log_error(P, _("unknown xy_out unit")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } } Q->xy_factor /= f; if (normalized_name != nullptr) { @@ -505,8 +511,8 @@ PJ *CONVERSION(unitconvert,0) { if( xy_in_is_linear >= 0 && xy_out_is_linear >= 0 && xy_in_is_linear != xy_out_is_linear ) { - proj_log_debug(P, "inconsistent unit type between xy_in and xy_out"); - return pj_default_destructor(P, PJD_ERR_INCONSISTENT_UNIT); + proj_log_error(P, _("inconsistent unit type between xy_in and xy_out")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if ((name = pj_param (P->ctx, P->params, "sz_in").s) != nullptr) { @@ -517,7 +523,10 @@ PJ *CONVERSION(unitconvert,0) { } else { f = pj_param (P->ctx, P->params, "dz_in").f; if (f == 0.0 || 1.0 / f == 0.0) - return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); + { + proj_log_error(P, _("unknown z_in unit")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } } Q->z_factor = f; } @@ -530,21 +539,28 @@ PJ *CONVERSION(unitconvert,0) { } else { f = pj_param (P->ctx, P->params, "dz_out").f; if (f == 0.0 || 1.0 / f == 0.0) - return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); + { + proj_log_error(P, _("unknown z_out unit")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } } Q->z_factor /= f; } if( z_in_is_linear >= 0 && z_out_is_linear >= 0 && z_in_is_linear != z_out_is_linear ) { - proj_log_debug(P, "inconsistent unit type between z_in and z_out"); - return pj_default_destructor(P, PJD_ERR_INCONSISTENT_UNIT); + proj_log_error(P, _("inconsistent unit type between z_in and z_out")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if ((name = pj_param (P->ctx, P->params, "st_in").s) != nullptr) { for (i = 0; (s = time_units[i].id) && strcmp(name, s) ; ++i); - if (!s) return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); /* unknown unit conversion id */ + if (!s) + { + proj_log_error(P, _("unknown t_in unit")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } Q->t_in_id = i; proj_log_trace(P, "t_in unit: %s", time_units[i].name); @@ -554,7 +570,11 @@ PJ *CONVERSION(unitconvert,0) { if ((name = pj_param (P->ctx, P->params, "st_out").s) != nullptr) { for (i = 0; (s = time_units[i].id) && strcmp(name, s) ; ++i); - if (!s) return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); /* unknown unit conversion id */ + if (!s) + { + proj_log_error(P, _("unknown t_out unit")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } Q->t_out_id = i; proj_log_trace(P, "t_out unit: %s", time_units[i].name); diff --git a/src/ctx.cpp b/src/ctx.cpp index 2093950b..b774a40a 100644 --- a/src/ctx.cpp +++ b/src/ctx.cpp @@ -93,10 +93,10 @@ pj_ctx pj_ctx::createDefault() if( getenv("PROJ_DEBUG") != nullptr ) { - if( atoi(getenv("PROJ_DEBUG")) >= -PJ_LOG_DEBUG_MINOR ) + if( atoi(getenv("PROJ_DEBUG")) >= -PJ_LOG_TRACE ) ctx.debug_level = atoi(getenv("PROJ_DEBUG")); else - ctx.debug_level = PJ_LOG_DEBUG_MINOR; + ctx.debug_level = PJ_LOG_TRACE; } return ctx; } diff --git a/src/datum_set.cpp b/src/datum_set.cpp index 3f612633..d55eb982 100644 --- a/src/datum_set.cpp +++ b/src/datum_set.cpp @@ -25,7 +25,6 @@ * DEALINGS IN THE SOFTWARE. *****************************************************************************/ -#include <errno.h> #include <string.h> #include "proj.h" @@ -71,7 +70,8 @@ int pj_datum_set(PJ_CONTEXT *ctx, paralist *pl, PJ *projdef) for (i = 0; (s = pj_datums[i].id) && strcmp(name, s) ; ++i) {} if (!s) { - proj_context_errno_set(ctx, PJD_ERR_UNKNOWN_ELLP_PARAM); + pj_log (ctx, PJ_LOG_ERROR, _("Unknown value for datum")); + proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return 1; } @@ -87,7 +87,7 @@ int pj_datum_set(PJ_CONTEXT *ctx, paralist *pl, PJ *projdef) auto param = pj_mkparam(entry); if (nullptr == param) { - proj_context_errno_set(ctx, ENOMEM); + proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/); return 1; } curr->next = param; @@ -99,7 +99,7 @@ int pj_datum_set(PJ_CONTEXT *ctx, paralist *pl, PJ *projdef) auto param = pj_mkparam(pj_datums[i].defn); if (nullptr == param) { - proj_context_errno_set(ctx, ENOMEM); + proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/); return 1; } curr->next = param; diff --git a/src/dmstor.cpp b/src/dmstor.cpp index 24887a11..4c7408cf 100644 --- a/src/dmstor.cpp +++ b/src/dmstor.cpp @@ -61,7 +61,7 @@ dmstor_ctx(PJ_CONTEXT *ctx, const char *is, char **rs) { n = 2; break; case 'r': case 'R': if (nl) { - proj_context_errno_set( ctx, PJD_ERR_WRONG_FORMAT_DMS_VALUE ); + proj_context_errno_set( ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE ); return HUGE_VAL; } ++s; @@ -73,7 +73,7 @@ dmstor_ctx(PJ_CONTEXT *ctx, const char *is, char **rs) { continue; } if (n < nl) { - proj_context_errno_set( ctx, PJD_ERR_WRONG_FORMAT_DMS_VALUE ); + proj_context_errno_set( ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE ); return HUGE_VAL; } v += tv * vm[n]; diff --git a/src/ell_set.cpp b/src/ell_set.cpp index 176fc553..438448d2 100644 --- a/src/ell_set.cpp +++ b/src/ell_set.cpp @@ -1,6 +1,5 @@ /* set ellipsoid parameters a and es */ -#include <errno.h> #include <math.h> #include <stddef.h> #include <string.h> @@ -147,23 +146,29 @@ static int ellps_ellps (PJ *P) { /* Then look up the right size and shape parameters from the builtin list */ if (strlen (par->param) < 7) - return proj_errno_set (P, PJD_ERR_INVALID_ARG); + { + proj_log_error(P, _("Invalid value for +ellps")); + return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } name = par->param + 6; ellps = pj_find_ellps (name); if (nullptr==ellps) - return proj_errno_set (P, PJD_ERR_UNKNOWN_ELLP_PARAM); + { + proj_log_error(P, _("Unrecognized value for +ellps")); + return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } /* Now, get things ready for ellps_size/ellps_shape, make them do their thing */ err = proj_errno_reset (P); paralist* new_params = pj_mkparam (ellps->major); if (nullptr == new_params) - return proj_errno_set (P, ENOMEM); + return proj_errno_set (P, PROJ_ERR_OTHER /*ENOMEM*/); new_params->next = pj_mkparam (ellps->ell); if (nullptr == new_params->next) { free(new_params); - return proj_errno_set (P, ENOMEM); + return proj_errno_set (P, PROJ_ERR_OTHER /*ENOMEM*/); } paralist* old_params = P->params; P->params = new_params; @@ -207,15 +212,26 @@ static int ellps_size (PJ *P) { if (nullptr==par) par = pj_get_param (P->params, "a"); if (nullptr==par) - return a_was_set? 0: proj_errno_set (P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN); + { + if( a_was_set ) + return 0; + proj_log_error(P, _("Major axis not given")); + return proj_errno_set (P, PROJ_ERR_INVALID_OP_MISSING_ARG); + } P->def_size = pj_strdup(par->param); par->used = 1; P->a = pj_atof (pj_param_value (par)); if (P->a <= 0) - return proj_errno_set (P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN); + { + proj_log_error(P, _("Invalid value for major axis")); + return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } if (HUGE_VAL==P->a) - return proj_errno_set (P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN); + { + proj_log_error(P, _("Invalid value for major axis")); + return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } if ('R'==par->param[0]) { P->es = P->f = P->e = P->rf = 0; @@ -264,10 +280,11 @@ static int ellps_shape (PJ *P) { /* reverse flattening, rf */ case 0: P->rf = pj_atof (pj_param_value (par)); - if (HUGE_VAL==P->rf) - return proj_errno_set (P, PJD_ERR_INVALID_ARG); - if (0==P->rf) - return proj_errno_set (P, PJD_ERR_REV_FLATTENING_IS_ZERO); + if (HUGE_VAL==P->rf || P->rf <= 0) + { + proj_log_error(P, _("Invalid value for rf. Should be > 0")); + return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } P->f = 1 / P->rf; P->es = 2*P->f - P->f*P->f; break; @@ -275,8 +292,11 @@ static int ellps_shape (PJ *P) { /* flattening, f */ case 1: P->f = pj_atof (pj_param_value (par)); - if (HUGE_VAL==P->f) - return proj_errno_set (P, PJD_ERR_INVALID_ARG); + if (HUGE_VAL==P->f || P->f < 0) + { + proj_log_error(P, _("Invalid value for f. Should be >= 0")); + return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } P->rf = P->f != 0.0 ? 1.0/P->f: HUGE_VAL; P->es = 2*P->f - P->f*P->f; @@ -285,42 +305,49 @@ static int ellps_shape (PJ *P) { /* eccentricity squared, es */ case 2: P->es = pj_atof (pj_param_value (par)); - if (HUGE_VAL==P->es) - return proj_errno_set (P, PJD_ERR_INVALID_ARG); - if (P->es >= 1) - return proj_errno_set (P, PJD_ERR_INVALID_ECCENTRICITY); + if (HUGE_VAL==P->es || P->es < 0 || P->es >= 1) + { + proj_log_error(P, _("Invalid value for es. Should be in [0,1[ range")); + return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } break; /* eccentricity, e */ case 3: P->e = pj_atof (pj_param_value (par)); - if (HUGE_VAL==P->e) - return proj_errno_set (P, PJD_ERR_INVALID_ARG); - if (P->e < 0 || P->e >= 1) - return proj_errno_set (P, PJD_ERR_INVALID_ECCENTRICITY); + if (HUGE_VAL==P->e || P->e < 0 || P->e >= 1) + { + proj_log_error(P, _("Invalid value for e. Should be in [0,1[ range")); + return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } P->es = P->e * P->e; break; /* semiminor axis, b */ case 4: P->b = pj_atof (pj_param_value (par)); - if (HUGE_VAL==P->b) - return proj_errno_set (P, PJD_ERR_INVALID_ARG); - if (P->b <= 0) - return proj_errno_set (P, PJD_ERR_INVALID_ECCENTRICITY); + if (HUGE_VAL==P->b || P->b <= 0) + { + proj_log_error(P, _("Invalid value for b. Should be > 0")); + return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } if (P->b==P->a) break; P->f = (P->a - P->b) / P->a; P->es = 2*P->f - P->f*P->f; break; default: - return PJD_ERR_INVALID_ARG; + // shouldn't happen + return PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE; } // Written that way to catch NaN if (!(P->es >= 0)) - return proj_errno_set (P, PJD_ERR_ES_LESS_THAN_ZERO); + { + proj_log_error(P, _("Invalid eccentricity")); + return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } return 0; } @@ -384,7 +411,7 @@ static int ellps_spherification (PJ *P) { /* R_h - a sphere with R = the harmonic mean of the ellipsoid */ case 4: if (P->a + P->b == 0) - return proj_errno_set (P, PJD_ERR_TOLERANCE_CONDITION); + return proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); P->a = (2*P->a * P->b) / (P->a + P->b); break; @@ -395,11 +422,15 @@ static int ellps_spherification (PJ *P) { v = pj_param_value (par); t = proj_dmstor (v, &endp); if (fabs (t) > M_HALFPI) - return proj_errno_set (P, PJD_ERR_REF_RAD_LARGER_THAN_90); + { + proj_log_error(P, _("Invalid value for lat_g. |lat_g| should be <= 90°")); + return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } t = sin (t); t = 1 - P->es * t * t; if (t == 0.) { - return proj_errno_set(P, PJD_ERR_INVALID_ECCENTRICITY); + proj_log_error(P, _("Invalid eccentricity")); + return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (i==5) /* arithmetic */ P->a *= (1. - P->es + t) / (2 * t * sqrt(t)); @@ -409,7 +440,8 @@ static int ellps_spherification (PJ *P) { } if (P->a <= 0.) { - return proj_errno_set(P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN); + proj_log_error(P, _("Invalid or missing major axis")); + return proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } /* Clean up the ellipsoidal parameters to reflect the sphere */ @@ -552,8 +584,9 @@ int pj_calc_ellipsoid_params (PJ *P, double a, double es) { if (0==P->f) P->f = 1 - cos (P->alpha); /* = 1 - sqrt (1 - PIN->es); */ if (P->f == 1.0) { - proj_context_errno_set( P->ctx, PJD_ERR_INVALID_ECCENTRICITY); - return PJD_ERR_INVALID_ECCENTRICITY; + proj_log_error(P, _("Invalid eccentricity")); + proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + return PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE; } P->rf = P->f != 0.0 ? 1.0/P->f: HUGE_VAL; @@ -573,8 +606,9 @@ int pj_calc_ellipsoid_params (PJ *P, double a, double es) { P->one_es = 1. - P->es; if (P->one_es == 0.) { - proj_context_errno_set( P->ctx, PJD_ERR_INVALID_ECCENTRICITY); - return PJD_ERR_INVALID_ECCENTRICITY; + proj_log_error(P, _("Invalid eccentricity")); + proj_errno_set (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + return PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE; } P->rone_es = 1./P->one_es; @@ -582,9 +616,6 @@ int pj_calc_ellipsoid_params (PJ *P, double a, double es) { return 0; } - - -#ifndef KEEP_ORIGINAL_PJ_ELL_SET /**************************************************************************************/ int pj_ell_set (PJ_CONTEXT *ctx, paralist *pl, double *a, double *es) { /*************************************************************************************** @@ -605,142 +636,3 @@ int pj_ell_set (PJ_CONTEXT *ctx, paralist *pl, double *a, double *es) { *es = B.es; return 0; } -#else - - -/**************************************************************************************/ -int pj_ell_set (PJ_CONTEXT *ctx, paralist *pl, double *a, double *es) { -/*************************************************************************************** - Initialize ellipsoidal parameters: This is the original ellipsoid setup - function by Gerald Evenden - significantly more compact than pj_ellipsoid and - its many helper functions, and still quite readable. - - It is, however, also so tight that it is hard to modify and add functionality, - and equally hard to find the right place to add further commentary for improved - future maintainability. - - Hence, when the need to store in the PJ object, the parameters actually used to - define the ellipsoid came up, rather than modifying this little gem of - "economy of expression", a much more verbose reimplementation, pj_ellipsoid, - was written. -***************************************************************************************/ - int i; - double b=0.0, e; - char *name; - paralist *start = 0; - - /* clear any previous error */ - proj_context_errno_set(ctx,0); - - /* check for varying forms of ellipsoid input */ - *a = *es = 0.; - - /* R takes precedence */ - if (pj_param(ctx, pl, "tR").i) - *a = pj_param(ctx,pl, "dR").f; - - /* probable elliptical figure */ - else { - /* check if ellps present and temporarily append its values to pl */ - if ((name = pj_param(ctx,pl, "sellps").s) != NULL) { - char *s; - - for (start = pl; start && start->next ; start = start->next) ; - for (i = 0; (s = pj_ellps[i].id) && strcmp(name, s) ; ++i) ; - if (!s) { - proj_context_errno_set( ctx, PJD_ERR_UNKNOWN_ELLP_PARAM); - return 1; - } - start->next = pj_mkparam(pj_ellps[i].major); - start->next->next = pj_mkparam(pj_ellps[i].ell); - } - - *a = pj_param(ctx,pl, "da").f; - - if (pj_param(ctx,pl, "tes").i) /* eccentricity squared */ - *es = pj_param(ctx,pl, "des").f; - else if (pj_param(ctx,pl, "te").i) { /* eccentricity */ - e = pj_param(ctx,pl, "de").f; - if (e < 0) { - proj_context_errno_set(ctx, PJD_ERR_INVALID_ECCENTRICITY); - return 1; - } - *es = e * e; - } else if (pj_param(ctx,pl, "trf").i) { /* recip flattening */ - *es = pj_param(ctx,pl, "drf").f; - if (*es == 0.0) { - proj_context_errno_set(ctx, PJD_ERR_REV_FLATTENING_IS_ZERO); - goto bomb; - } - *es = 1./ *es; - *es = *es * (2. - *es); - } else if (pj_param(ctx,pl, "tf").i) { /* flattening */ - *es = pj_param(ctx,pl, "df").f; - *es = *es * (2. - *es); - } else if (pj_param(ctx,pl, "tb").i) { /* minor axis */ - b = pj_param(ctx,pl, "db").f; - *es = 1. - (b * b) / (*a * *a); - } /* else *es == 0. and sphere of radius *a */ - if (b == 0.0) - b = *a * sqrt(1. - *es); - - - /* following options turn ellipsoid into equivalent sphere */ - if (pj_param(ctx,pl, "bR_A").i) { /* sphere--area of ellipsoid */ - *a *= 1. - *es * (SIXTH + *es * (RA4 + *es * RA6)); - *es = 0.; - } else if (pj_param(ctx,pl, "bR_V").i) { /* sphere--vol. of ellipsoid */ - *a *= 1. - *es * (SIXTH + *es * (RV4 + *es * RV6)); - *es = 0.; - } else if (pj_param(ctx,pl, "bR_a").i) { /* sphere--arithmetic mean */ - *a = .5 * (*a + b); - *es = 0.; - } else if (pj_param(ctx,pl, "bR_g").i) { /* sphere--geometric mean */ - *a = sqrt(*a * b); - *es = 0.; - } else if (pj_param(ctx,pl, "bR_h").i) { /* sphere--harmonic mean */ - if ( (*a + b) == 0.0) { - proj_context_errno_set(ctx, PJD_ERR_TOLERANCE_CONDITION); - goto bomb; - } - *a = 2. * *a * b / (*a + b); - *es = 0.; - } else if ((i = pj_param(ctx,pl, "tR_lat_a").i) || /* sphere--arith. */ - pj_param(ctx,pl, "tR_lat_g").i) { /* or geom. mean at latitude */ - double tmp; - - tmp = sin(pj_param(ctx,pl, i ? "rR_lat_a" : "rR_lat_g").f); - if (fabs(tmp) > M_HALFPI) { - proj_context_errno_set(ctx, PJD_ERR_REF_RAD_LARGER_THAN_90); - goto bomb; - } - tmp = 1. - *es * tmp * tmp; - *a *= i ? .5 * (1. - *es + tmp) / ( tmp * sqrt(tmp)) : - sqrt(1. - *es) / tmp; - *es = 0.; - } -bomb: - if (start) { /* clean up temporary extension of list */ - free(start->next->next); - free(start->next); - start->next = 0; - } - if (ctx->last_errno) - return 1; - } - /* some remaining checks */ - if (*es < 0.) { - proj_context_errno_set(ctx, PJD_ERR_ES_LESS_THAN_ZERO); - return 1; - } - if (*es >= 1.) { - proj_context_errno_set(ctx, PJD_ERR_INVALID_ECCENTRICITY); - return 1; - } - if (*a <= 0.) { - proj_context_errno_set(ctx, PJD_ERR_MAJOR_AXIS_NOT_GIVEN); - return 1; - } - return 0; -} -#endif diff --git a/src/factors.cpp b/src/factors.cpp index ff733d07..d6c24f01 100644 --- a/src/factors.cpp +++ b/src/factors.cpp @@ -36,8 +36,15 @@ int pj_factors(PJ_LP lp, const PJ *P, double h, struct FACTORS *fac) { fac->code = 0; /* Check for latitude or longitude overange */ - if ((fabs (lp.phi)-M_HALFPI) > EPS || fabs (lp.lam) > 10.) { - proj_errno_set (P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); + if ((fabs (lp.phi)-M_HALFPI) > EPS ) + { + proj_log_error(P, _("Invalid latitude")); + proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_INVALID_COORD); + return 1; + } + if( fabs (lp.lam) > 10.) { + proj_log_error(P, _("Invalid longitude")); + proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_INVALID_COORD); return 1; } @@ -62,7 +69,8 @@ int pj_factors(PJ_LP lp, const PJ *P, double h, struct FACTORS *fac) { /* Derivatives */ if (pj_deriv (lp, h, P, &(fac->der))) { - proj_errno_set (P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); + proj_log_error(P, _("Invalid latitude or longitude")); + proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_INVALID_COORD); return 1; } diff --git a/src/filemanager.cpp b/src/filemanager.cpp index b51205eb..0a0af8cf 100644 --- a/src/filemanager.cpp +++ b/src/filemanager.cpp @@ -1489,13 +1489,13 @@ static void *pj_open_lib_internal( if (ctx->last_errno == 0 && errno != 0) proj_context_errno_set(ctx, errno); - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "pj_open_lib(%s): call fopen(%s) - %s", - name, sysname, fid == nullptr ? "failed" : "succeeded"); + pj_log(ctx, PJ_LOG_DEBUG, "pj_open_lib(%s): call fopen(%s) - %s", name, + sysname, fid == nullptr ? "failed" : "succeeded"); return (fid); } catch (const std::exception &) { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "pj_open_lib(%s): out of memory", name); + pj_log(ctx, PJ_LOG_DEBUG, "pj_open_lib(%s): out of memory", name); return nullptr; } @@ -1640,8 +1640,7 @@ NS_PROJ::FileManager::open_resource_file(PJ_CONTEXT *ctx, const char *name) { file = open(ctx, remote_file.c_str(), NS_PROJ::FileAccess::READ_ONLY); if (file) { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Using %s", - remote_file.c_str()); + pj_log(ctx, PJ_LOG_DEBUG, "Using %s", remote_file.c_str()); proj_context_errno_set(ctx, 0); } } diff --git a/src/fwd.cpp b/src/fwd.cpp index 962a5051..97ba5999 100644 --- a/src/fwd.cpp +++ b/src/fwd.cpp @@ -52,10 +52,19 @@ static PJ_COORD fwd_prepare (PJ *P, PJ_COORD coo) { /* check for latitude or longitude over-range */ t = (coo.lp.phi < 0 ? -coo.lp.phi : coo.lp.phi) - M_HALFPI; - if (t > PJ_EPS_LAT || coo.lp.lam > 10 || coo.lp.lam < -10) { - proj_errno_set (P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); + if (t > PJ_EPS_LAT) + { + proj_log_error(P, _("Invalid latitude")); + proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_INVALID_COORD); return proj_coord_error (); } + if (coo.lp.lam > 10 || coo.lp.lam < -10) + { + proj_log_error(P, _("Invalid longitude")); + proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_INVALID_COORD); + return proj_coord_error (); + } + /* Clamp latitude to -90..90 degree range */ if (coo.lp.phi > M_HALFPI) @@ -186,7 +195,7 @@ PJ_XY pj_fwd(PJ_LP lp, PJ *P) { else if (P->fwd4d) coo = P->fwd4d (coo, P); else { - proj_errno_set (P, EINVAL); + proj_errno_set (P, PROJ_ERR_OTHER_NO_INVERSE_OP); return proj_coord_error ().xy; } if (HUGE_VAL==coo.v[0]) @@ -220,7 +229,7 @@ PJ_XYZ pj_fwd3d(PJ_LPZ lpz, PJ *P) { else if (P->fwd) coo.xy = P->fwd (coo.lp, P); else { - proj_errno_set (P, EINVAL); + proj_errno_set (P, PROJ_ERR_OTHER_NO_INVERSE_OP); return proj_coord_error ().xyz; } if (HUGE_VAL==coo.v[0]) @@ -250,7 +259,7 @@ PJ_COORD pj_fwd4d (PJ_COORD coo, PJ *P) { else if (P->fwd) coo.xy = P->fwd (coo.lp, P); else { - proj_errno_set (P, EINVAL); + proj_errno_set (P, PROJ_ERR_OTHER_NO_INVERSE_OP); return proj_coord_error (); } if (HUGE_VAL==coo.v[0]) diff --git a/src/gauss.cpp b/src/gauss.cpp index 96bd5166..07d0433a 100644 --- a/src/gauss.cpp +++ b/src/gauss.cpp @@ -109,6 +109,6 @@ PJ_LP pj_inv_gauss(PJ_CONTEXT *ctx, PJ_LP slp, const void *data) { } /* convergence failed */ if (!i) - proj_context_errno_set(ctx, PJD_ERR_NON_CONV_INV_MERI_DIST); + proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return (elp); } diff --git a/src/generic_inverse.cpp b/src/generic_inverse.cpp index ddd5060b..41cf6c50 100644 --- a/src/generic_inverse.cpp +++ b/src/generic_inverse.cpp @@ -109,6 +109,7 @@ PJ_LP pj_generic_inverse_2d(PJ_XY xy, PJ *P, PJ_LP lpInitial) { lp.phi = M_HALFPI; } } - proj_context_errno_set(P->ctx, PJD_ERR_NON_CONVERGENT); + proj_context_errno_set(P->ctx, + PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } diff --git a/src/grids.cpp b/src/grids.cpp index 871e21ed..b7ab526a 100644 --- a/src/grids.cpp +++ b/src/grids.cpp @@ -197,7 +197,9 @@ GTXVerticalShiftGrid *GTXVerticalShiftGrid::open(PJ_CONTEXT *ctx, /* Read the header. */ /* -------------------------------------------------------------------- */ if (fp->read(header, sizeof(header)) != sizeof(header)) { - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + pj_log(ctx, PJ_LOG_ERROR, _("Cannot read grid header")); + proj_context_errno_set(ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } @@ -222,8 +224,9 @@ GTXVerticalShiftGrid *GTXVerticalShiftGrid::open(PJ_CONTEXT *ctx, if (xorigin < -360 || xorigin > 360 || yorigin < -90 || yorigin > 90) { pj_log(ctx, PJ_LOG_ERROR, - "gtx file header has invalid extents, corrupt?"); - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + _("gtx file header has invalid extents, corrupt?")); + proj_context_errno_set(ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } @@ -234,7 +237,7 @@ GTXVerticalShiftGrid *GTXVerticalShiftGrid::open(PJ_CONTEXT *ctx, xorigin -= 360.0; if (xorigin >= 0.0 && xorigin + xstep * columns > 180.0) { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, + pj_log(ctx, PJ_LOG_DEBUG, "This GTX spans the dateline! This will cause problems."); } @@ -258,7 +261,8 @@ bool GTXVerticalShiftGrid::valueAt(int x, int y, float &out) const { m_fp->seek(40 + sizeof(float) * (y * m_width + x)); if (m_fp->read(&out, sizeof(out)) != sizeof(out)) { - proj_context_errno_set(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set(m_ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return false; } if (IS_LSB) { @@ -621,7 +625,7 @@ bool GTiffGrid::valueAt(uint16 sample, int x, int yFromBottom, try { m_buffer.resize(blockSize); } catch (const std::exception &e) { - pj_log(m_ctx, PJ_LOG_ERROR, "Exception %s", e.what()); + pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what()); return false; } } @@ -642,7 +646,7 @@ bool GTiffGrid::valueAt(uint16 sample, int x, int yFromBottom, m_cache.insert(m_ifdIdx, blockId, m_buffer); } catch (const std::exception &e) { // Should normally not happen - pj_log(m_ctx, PJ_LOG_ERROR, "Exception %s", e.what()); + pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what()); } } @@ -847,35 +851,35 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() { TIFFGetField(m_hTIFF, TIFFTAG_IMAGEWIDTH, &width); TIFFGetField(m_hTIFF, TIFFTAG_IMAGELENGTH, &height); if (width == 0 || height == 0 || width > INT_MAX || height > INT_MAX) { - pj_log(m_ctx, PJ_LOG_ERROR, "Invalid image size"); + pj_log(m_ctx, PJ_LOG_ERROR, _("Invalid image size")); return nullptr; } uint16 samplesPerPixel = 0; if (!TIFFGetField(m_hTIFF, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel)) { - pj_log(m_ctx, PJ_LOG_ERROR, "Missing SamplesPerPixel tag"); + pj_log(m_ctx, PJ_LOG_ERROR, _("Missing SamplesPerPixel tag")); return nullptr; } if (samplesPerPixel == 0) { - pj_log(m_ctx, PJ_LOG_ERROR, "Invalid SamplesPerPixel value"); + pj_log(m_ctx, PJ_LOG_ERROR, _("Invalid SamplesPerPixel value")); return nullptr; } uint16 bitsPerSample = 0; if (!TIFFGetField(m_hTIFF, TIFFTAG_BITSPERSAMPLE, &bitsPerSample)) { - pj_log(m_ctx, PJ_LOG_ERROR, "Missing BitsPerSample tag"); + pj_log(m_ctx, PJ_LOG_ERROR, _("Missing BitsPerSample tag")); return nullptr; } uint16 planarConfig = 0; if (!TIFFGetField(m_hTIFF, TIFFTAG_PLANARCONFIG, &planarConfig)) { - pj_log(m_ctx, PJ_LOG_ERROR, "Missing PlanarConfig tag"); + pj_log(m_ctx, PJ_LOG_ERROR, _("Missing PlanarConfig tag")); return nullptr; } uint16 sampleFormat = 0; if (!TIFFGetField(m_hTIFF, TIFFTAG_SAMPLEFORMAT, &sampleFormat)) { - pj_log(m_ctx, PJ_LOG_ERROR, "Missing SampleFormat tag"); + pj_log(m_ctx, PJ_LOG_ERROR, _("Missing SampleFormat tag")); return nullptr; } @@ -893,9 +897,8 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() { else if (sampleFormat == SAMPLEFORMAT_IEEEFP && bitsPerSample == 64) dt = TIFFDataType::Float64; else { - pj_log( - m_ctx, PJ_LOG_ERROR, - "Unsupported combination of SampleFormat and BitsPerSample values"); + pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported combination of SampleFormat " + "and BitsPerSample values")); return nullptr; } @@ -903,7 +906,7 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() { if (!TIFFGetField(m_hTIFF, TIFFTAG_PHOTOMETRIC, &photometric)) photometric = PHOTOMETRIC_MINISBLACK; if (photometric != PHOTOMETRIC_MINISBLACK) { - pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported Photometric value"); + pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported Photometric value")); return nullptr; } @@ -914,19 +917,19 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() { if (compression != COMPRESSION_NONE && !TIFFIsCODECConfigured(compression)) { pj_log(m_ctx, PJ_LOG_ERROR, - "Cannot open TIFF file due to missing codec."); + _("Cannot open TIFF file due to missing codec.")); return nullptr; } // We really don't want to try dealing with old-JPEG images if (compression == COMPRESSION_OJPEG) { - pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported compression method."); + pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported compression method.")); return nullptr; } const auto blockSize = TIFFIsTiled(m_hTIFF) ? TIFFTileSize64(m_hTIFF) : TIFFStripSize64(m_hTIFF); if (blockSize == 0 || blockSize > 64 * 1024 * 2014) { - pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported block size."); + pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported block size.")); return nullptr; } @@ -938,23 +941,22 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() { extent.isGeographic = true; if (!TIFFGetField(m_hTIFF, TIFFTAG_GEOKEYDIRECTORY, &count, &geokeys)) { - pj_log(m_ctx, PJ_LOG_DEBUG_MINOR, "No GeoKeys tag"); + pj_log(m_ctx, PJ_LOG_TRACE, "No GeoKeys tag"); } else { if (count < 4 || (count % 4) != 0) { pj_log(m_ctx, PJ_LOG_ERROR, - "Wrong number of values in GeoKeys tag"); + _("Wrong number of values in GeoKeys tag")); return nullptr; } if (geokeys[0] != 1) { - pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported GeoTIFF major version"); + pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported GeoTIFF major version")); return nullptr; } // We only know that we support GeoTIFF 1.0 and 1.1 at that time if (geokeys[1] != 1 || geokeys[2] > 1) { - pj_log(m_ctx, PJ_LOG_DEBUG_MINOR, - "GeoTIFF %d.%d possibly not handled", geokeys[1], - geokeys[2]); + pj_log(m_ctx, PJ_LOG_TRACE, "GeoTIFF %d.%d possibly not handled", + geokeys[1], geokeys[2]); } for (unsigned int i = 4; i + 3 < count; i += 4) { @@ -971,9 +973,9 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() { extent.isGeographic = false; } else if (geokeys[i + 3] != ModelTypeGeographic) { pj_log(m_ctx, PJ_LOG_ERROR, - "Only GTModelTypeGeoKey = " - "ModelTypeGeographic or ModelTypeProjected are " - "supported"); + _("Only GTModelTypeGeoKey = " + "ModelTypeGeographic or ModelTypeProjected are " + "supported")); return nullptr; } } else if (geokeys[i] == GTRasterTypeGeoKey) { @@ -996,8 +998,8 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() { // a GeoTransformationMatrix, since negative values in GeoPixelScale // have historically been implementation bugs. if (matrix[1] != 0 || matrix[4] != 0) { - pj_log(m_ctx, PJ_LOG_ERROR, "Rotational terms not supported in " - "GeoTransformationMatrix tag"); + pj_log(m_ctx, PJ_LOG_ERROR, _("Rotational terms not supported in " + "GeoTransformationMatrix tag")); return nullptr; } @@ -1009,12 +1011,12 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() { double *geopixelscale = nullptr; if (TIFFGetField(m_hTIFF, TIFFTAG_GEOPIXELSCALE, &count, &geopixelscale) != 1) { - pj_log(m_ctx, PJ_LOG_ERROR, "No GeoPixelScale tag"); + pj_log(m_ctx, PJ_LOG_ERROR, _("No GeoPixelScale tag")); return nullptr; } if (count != 3) { pj_log(m_ctx, PJ_LOG_ERROR, - "Wrong number of values in GeoPixelScale tag"); + _("Wrong number of values in GeoPixelScale tag")); return nullptr; } hRes = geopixelscale[0]; @@ -1023,12 +1025,12 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() { double *geotiepoints = nullptr; if (TIFFGetField(m_hTIFF, TIFFTAG_GEOTIEPOINTS, &count, &geotiepoints) != 1) { - pj_log(m_ctx, PJ_LOG_ERROR, "No GeoTiePoints tag"); + pj_log(m_ctx, PJ_LOG_ERROR, _("No GeoTiePoints tag")); return nullptr; } if (count != 6) { pj_log(m_ctx, PJ_LOG_ERROR, - "Wrong number of values in GeoTiePoints tag"); + _("Wrong number of values in GeoTiePoints tag")); return nullptr; } @@ -1059,7 +1061,7 @@ std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() { fabs(extent.south) <= M_PI + 1e-5)) && extent.west < extent.east && extent.south < extent.north && extent.resX > 1e-10 && extent.resY > 1e-10)) { - pj_log(m_ctx, PJ_LOG_ERROR, "Inconsistent georeferencing for %s", + pj_log(m_ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"), m_filename.c_str()); return nullptr; } @@ -1097,7 +1099,7 @@ class GTiffVGridShiftSet : public VerticalShiftGridSet { } bool reopen(PJ_CONTEXT *ctx) override { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it", + pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it", m_name.c_str()); m_grids.clear(); m_GTiffDataset.reset(); @@ -1130,7 +1132,7 @@ insertIntoHierarchy(PJ_CONTEXT *ctx, std::unique_ptr<GridType> &&grid, // the names to recreate the hierarchy if (!gridName.empty()) { if (mapGrids.find(gridName) != mapGrids.end()) { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Several grids called %s found!", + pj_log(ctx, PJ_LOG_DEBUG, "Several grids called %s found!", gridName.c_str()); } mapGrids[gridName] = grid.get(); @@ -1139,7 +1141,7 @@ insertIntoHierarchy(PJ_CONTEXT *ctx, std::unique_ptr<GridType> &&grid, if (!parentName.empty()) { auto iter = mapGrids.find(parentName); if (iter == mapGrids.end()) { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, + pj_log(ctx, PJ_LOG_DEBUG, "Grid %s refers to non-existing parent %s. " "Using bounding-box method.", gridName.c_str(), parentName.c_str()); @@ -1148,7 +1150,7 @@ insertIntoHierarchy(PJ_CONTEXT *ctx, std::unique_ptr<GridType> &&grid, iter->second->m_children.emplace_back(std::move(grid)); gridInserted = true; } else { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, + pj_log(ctx, PJ_LOG_DEBUG, "Grid %s refers to parent %s, but its extent is " "not included in it. Using bounding-box method.", gridName.c_str(), parentName.c_str()); @@ -1169,7 +1171,7 @@ insertIntoHierarchy(PJ_CONTEXT *ctx, std::unique_ptr<GridType> &&grid, gridInserted = true; break; } else if (candidateParentExtent.intersects(extent)) { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, + pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!"); } } @@ -1243,8 +1245,7 @@ void GTiffVGrid::insertGrid(PJ_CONTEXT *ctx, gridInserted = true; break; } else if (candidateParentExtent.intersects(extent)) { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, - "Partially intersecting grids found!"); + pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!"); } } if (!gridInserted) { @@ -1279,10 +1280,10 @@ GTiffVGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, const auto subfileType = grid->subfileType(); if (subfileType != 0 && subfileType != FILETYPE_PAGE) { if (ifd == 0) { - pj_log(ctx, PJ_LOG_ERROR, "Invalid subfileType"); + pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType")); return nullptr; } else { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, + pj_log(ctx, PJ_LOG_DEBUG, "Ignoring IFD %d as it has a unsupported subfileType", ifd); continue; @@ -1310,13 +1311,13 @@ GTiffVGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, // can be ignored // One could imagine to put the accuracy values in separate // IFD for example - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, + pj_log(ctx, PJ_LOG_DEBUG, "Ignoring IFD %d as it has no " "geoid_undulation/vertical_offset channel", ifd); continue; } else { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, + pj_log(ctx, PJ_LOG_DEBUG, "IFD 0 has channel descriptions, but no " "geoid_undulation/vertical_offset channel"); return nullptr; @@ -1325,7 +1326,7 @@ GTiffVGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, } if (idxSample >= grid->samplesPerPixel()) { - pj_log(ctx, PJ_LOG_ERROR, "Invalid sample index"); + pj_log(ctx, PJ_LOG_ERROR, _("Invalid sample index")); return nullptr; } @@ -1389,23 +1390,24 @@ VerticalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) { auto set = std::unique_ptr<VerticalShiftGridSet>( GTiffVGridShiftSet::open(ctx, std::move(fp), actualName)); if (!set) - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set( + ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return set; #else pj_log(ctx, PJ_LOG_ERROR, - "TIFF grid, but TIFF support disabled in this build"); + _("TIFF grid, but TIFF support disabled in this build")); return nullptr; #endif } - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Unrecognized vertical grid format"); + pj_log(ctx, PJ_LOG_ERROR, _("Unrecognized vertical grid format")); return nullptr; } // --------------------------------------------------------------------------- bool VerticalShiftGridSet::reopen(PJ_CONTEXT *ctx) { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it", + pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it", m_name.c_str()); auto newGS = open(ctx, m_name); m_grids.clear(); @@ -1570,7 +1572,8 @@ NTv1Grid *NTv1Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, /* Read the header. */ /* -------------------------------------------------------------------- */ if (fp->read(header, sizeof(header)) != sizeof(header)) { - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set(ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } @@ -1589,8 +1592,9 @@ NTv1Grid *NTv1Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, if (*((int *)(header + 8)) != 12) { pj_log(ctx, PJ_LOG_ERROR, - "NTv1 grid shift file has wrong record count, corrupt?"); - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + _("NTv1 grid shift file has wrong record count, corrupt?")); + proj_context_errno_set(ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } @@ -1607,9 +1611,10 @@ NTv1Grid *NTv1Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, fabs(extent.south) <= M_PI + 1e-5 && extent.west < extent.east && extent.south < extent.north && extent.resX > 1e-10 && extent.resY > 1e-10)) { - pj_log(ctx, PJ_LOG_ERROR, "Inconsistent georeferencing for %s", + pj_log(ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"), filename.c_str()); - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set(ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } const int columns = static_cast<int>( @@ -1631,7 +1636,8 @@ bool NTv1Grid::valueAt(int x, int y, bool compensateNTConvention, m_fp->seek(192 + 2 * sizeof(double) * (y * m_width + m_width - 1 - x)); if (m_fp->read(&two_doubles[0], sizeof(two_doubles)) != sizeof(two_doubles)) { - proj_context_errno_set(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set(m_ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return false; } if (IS_LSB) { @@ -1692,7 +1698,8 @@ CTable2Grid *CTable2Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, /* Read the header. */ /* -------------------------------------------------------------------- */ if (fp->read(header, sizeof(header)) != sizeof(header)) { - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set(ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } @@ -1716,9 +1723,10 @@ CTable2Grid *CTable2Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, memcpy(&extent.resY, header + 120, 8); if (!(fabs(extent.west) <= 4 * M_PI && fabs(extent.south) <= M_PI + 1e-5 && extent.resX > 1e-10 && extent.resY > 1e-10)) { - pj_log(ctx, PJ_LOG_ERROR, "Inconsistent georeferencing for %s", + pj_log(ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"), filename.c_str()); - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set(ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } int width; @@ -1726,7 +1734,8 @@ CTable2Grid *CTable2Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, memcpy(&width, header + 128, 4); memcpy(&height, header + 132, 4); if (width <= 0 || height <= 0) { - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set(ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } extent.east = extent.west + (width - 1) * extent.resX; @@ -1744,7 +1753,8 @@ bool CTable2Grid::valueAt(int x, int y, bool compensateNTConvention, float two_floats[2]; m_fp->seek(160 + 2 * sizeof(float) * (y * m_width + x)); if (m_fp->read(&two_floats[0], sizeof(two_floats)) != sizeof(two_floats)) { - proj_context_errno_set(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set(m_ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return false; } if (!IS_LSB) { @@ -1828,7 +1838,8 @@ bool NTv2Grid::valueAt(int x, int y, bool compensateNTConvention, 4 * sizeof(float) * (static_cast<unsigned long long>(y) * m_width + m_width - 1 - x)); if (m_fp->read(&two_float[0], sizeof(two_float)) != sizeof(two_float)) { - proj_context_errno_set(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set(m_ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return false; } if (m_mustSwap) { @@ -1862,14 +1873,16 @@ std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx, /* Read the header. */ /* -------------------------------------------------------------------- */ if (fpRaw->read(header, sizeof(header)) != sizeof(header)) { - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set(ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } constexpr int OFFSET_GS_TYPE = 56; if (memcmp(header + OFFSET_GS_TYPE, "SECONDS", 7) != 0) { - pj_log(ctx, PJ_LOG_ERROR, "Only GS_TYPE=SECONDS is supported"); - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + pj_log(ctx, PJ_LOG_ERROR, _("Only GS_TYPE=SECONDS is supported")); + proj_context_errno_set(ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } @@ -1899,12 +1912,14 @@ std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx, for (unsigned subfile = 0; subfile < num_subfiles; subfile++) { // Read header if (fpRaw->read(header, sizeof(header)) != sizeof(header)) { - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set( + ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } if (strncmp(header, "SUB_NAME", 8) != 0) { - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set( + ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } @@ -1944,9 +1959,10 @@ std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx, fabs(extent.south) <= M_PI + 1e-5 && extent.west < extent.east && extent.south < extent.north && extent.resX > 1e-10 && extent.resY > 1e-10)) { - pj_log(ctx, PJ_LOG_ERROR, "Inconsistent georeferencing for %s", + pj_log(ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"), filename.c_str()); - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set( + ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } const int columns = static_cast<int>( @@ -1954,7 +1970,7 @@ std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx, const int rows = static_cast<int>( fabs((extent.north - extent.south) / extent.resY + 0.5) + 1); - pj_log(ctx, PJ_LOG_DEBUG_MINOR, + pj_log(ctx, PJ_LOG_TRACE, "NTv2 %s %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", gridName.c_str(), columns, rows, extent.west * RAD_TO_DEG, extent.south * RAD_TO_DEG, extent.east * RAD_TO_DEG, @@ -1964,9 +1980,10 @@ std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx, memcpy(&gs_count, header + OFFSET_GS_COUNT, 4); if (gs_count / columns != static_cast<unsigned>(rows)) { pj_log(ctx, PJ_LOG_ERROR, - "GS_COUNT(%u) does not match expected cells (%dx%d)", + _("GS_COUNT(%u) does not match expected cells (%dx%d)"), gs_count, columns, rows); - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set( + ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } @@ -2018,7 +2035,7 @@ class GTiffHGridShiftSet : public HorizontalShiftGridSet { } bool reopen(PJ_CONTEXT *ctx) override { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it", + pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it", m_name.c_str()); m_grids.clear(); m_GTiffDataset.reset(); @@ -2119,8 +2136,7 @@ void GTiffHGrid::insertGrid(PJ_CONTEXT *ctx, gridInserted = true; break; } else if (candidateParentExtent.intersects(extent)) { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, - "Partially intersecting grids found!"); + pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!"); } } if (!gridInserted) { @@ -2161,11 +2177,11 @@ GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, const auto subfileType = grid->subfileType(); if (subfileType != 0 && subfileType != FILETYPE_PAGE) { if (ifd == 0) { - pj_log(ctx, PJ_LOG_ERROR, "Invalid subfileType"); + pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType")); return nullptr; } else { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, - "Ignoring IFD %d as it has a unsupported subfileType", + pj_log(ctx, PJ_LOG_DEBUG, + _("Ignoring IFD %d as it has a unsupported subfileType"), ifd); continue; } @@ -2174,11 +2190,12 @@ GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, if (grid->samplesPerPixel() < 2) { if (ifd == 0) { pj_log(ctx, PJ_LOG_ERROR, - "At least 2 samples per pixel needed"); + _("At least 2 samples per pixel needed")); return nullptr; } else { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, - "Ignoring IFD %d as it has not at least 2 samples", ifd); + pj_log(ctx, PJ_LOG_DEBUG, + _("Ignoring IFD %d as it has not at least 2 samples"), + ifd); continue; } } @@ -2209,13 +2226,13 @@ GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, // longitude_offset/latitude_offset can be ignored // One could imagine to put the accuracy values in separate // IFD for example - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, + pj_log(ctx, PJ_LOG_DEBUG, "Ignoring IFD %d as it has no " "longitude_offset/latitude_offset channel", ifd); continue; } else { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, + pj_log(ctx, PJ_LOG_DEBUG, "IFD 0 has channel descriptions, but no " "longitude_offset/latitude_offset channel"); return nullptr; @@ -2223,19 +2240,21 @@ GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, } } if (foundDescriptionForLatOffset && !foundDescriptionForLonOffset) { - pj_log(ctx, PJ_LOG_ERROR, - "Found latitude_offset channel, but not longitude_offset"); + pj_log( + ctx, PJ_LOG_ERROR, + _("Found latitude_offset channel, but not longitude_offset")); return nullptr; } else if (foundDescriptionForLonOffset && !foundDescriptionForLatOffset) { - pj_log(ctx, PJ_LOG_ERROR, - "Found longitude_offset channel, but not latitude_offset"); + pj_log( + ctx, PJ_LOG_ERROR, + _("Found longitude_offset channel, but not latitude_offset")); return nullptr; } if (idxLatShift >= grid->samplesPerPixel() || idxLonShift >= grid->samplesPerPixel()) { - pj_log(ctx, PJ_LOG_ERROR, "Invalid sample index"); + pj_log(ctx, PJ_LOG_ERROR, _("Invalid sample index")); return nullptr; } @@ -2249,7 +2268,7 @@ GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, positiveEast = true; } else { pj_log(ctx, PJ_LOG_ERROR, - "Unsupported value %s for 'positive_value'", + _("Unsupported value %s for 'positive_value'"), positiveValue.c_str()); return nullptr; } @@ -2264,7 +2283,7 @@ GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, grid->metadataItem("UNITTYPE", idxLonShift); if (unitLatShift != unitLonShift) { pj_log(ctx, PJ_LOG_ERROR, - "Different unit for longitude and latitude offset"); + _("Different unit for longitude and latitude offset")); return nullptr; } if (!unitLatShift.empty()) { @@ -2275,7 +2294,7 @@ GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, } else if (unitLatShift == "degree") { convFactorToRadian = M_PI / 180.0; } else { - pj_log(ctx, PJ_LOG_ERROR, "Unsupported unit %s", + pj_log(ctx, PJ_LOG_ERROR, _("Unsupported unit %s"), unitLatShift.c_str()); return nullptr; } @@ -2324,7 +2343,7 @@ HorizontalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) { if (header_size != sizeof(header)) { /* some files may be smaller that sizeof(header), eg 160, so */ ctx->last_errno = 0; /* don't treat as a persistent error */ - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, + pj_log(ctx, PJ_LOG_DEBUG, "pj_gridinfo_init: short header read of %d bytes", (int)header_size); } @@ -2367,23 +2386,24 @@ HorizontalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) { auto set = std::unique_ptr<HorizontalShiftGridSet>( GTiffHGridShiftSet::open(ctx, std::move(fp), actualName)); if (!set) - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set( + ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return set; #else pj_log(ctx, PJ_LOG_ERROR, - "TIFF grid, but TIFF support disabled in this build"); + _("TIFF grid, but TIFF support disabled in this build")); return nullptr; #endif } - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Unrecognized horizontal grid format"); + pj_log(ctx, PJ_LOG_ERROR, _("Unrecognized horizontal grid format")); return nullptr; } // --------------------------------------------------------------------------- bool HorizontalShiftGridSet::reopen(PJ_CONTEXT *ctx) { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it", + pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it", m_name.c_str()); auto newGS = open(ctx, m_name); m_grids.clear(); @@ -2460,7 +2480,7 @@ class GTiffGenericGridShiftSet : public GenericShiftGridSet { } bool reopen(PJ_CONTEXT *ctx) override { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it", + pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it", m_name.c_str()); m_grids.clear(); m_GTiffDataset.reset(); @@ -2558,8 +2578,7 @@ void GTiffGenericGrid::insertGrid(PJ_CONTEXT *ctx, gridInserted = true; break; } else if (candidateParentExtent.intersects(extent)) { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, - "Partially intersecting grids found!"); + pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!"); } } if (!gridInserted) { @@ -2628,11 +2647,11 @@ GTiffGenericGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp, const auto subfileType = grid->subfileType(); if (subfileType != 0 && subfileType != FILETYPE_PAGE) { if (ifd == 0) { - pj_log(ctx, PJ_LOG_ERROR, "Invalid subfileType"); + pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType")); return nullptr; } else { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, - "Ignoring IFD %d as it has a unsupported subfileType", + pj_log(ctx, PJ_LOG_DEBUG, + _("Ignoring IFD %d as it has a unsupported subfileType"), ifd); continue; } @@ -2703,23 +2722,24 @@ GenericShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) { auto set = std::unique_ptr<GenericShiftGridSet>( GTiffGenericGridShiftSet::open(ctx, std::move(fp), actualName)); if (!set) - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set( + ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return set; #else pj_log(ctx, PJ_LOG_ERROR, - "TIFF grid, but TIFF support disabled in this build"); + _("TIFF grid, but TIFF support disabled in this build")); return nullptr; #endif } - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Unrecognized generic grid format"); + pj_log(ctx, PJ_LOG_ERROR, _("Unrecognized generic grid format")); return nullptr; } // --------------------------------------------------------------------------- bool GenericShiftGridSet::reopen(PJ_CONTEXT *ctx) { - pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it", + pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it", m_name.c_str()); auto newGS = open(ctx, m_name); m_grids.clear(); @@ -2785,8 +2805,10 @@ ListOfGenericGrids pj_generic_grid_init(PJ *P, const char *gridkey) { auto gridSet = GenericShiftGridSet::open(P->ctx, gridname); if (!gridSet) { if (!canFail) { - if (proj_context_errno(P->ctx) != PJD_ERR_NETWORK_ERROR) { - proj_context_errno_set(P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + if (proj_context_errno(P->ctx) != + PROJ_ERR_OTHER_NETWORK_ERROR) { + proj_context_errno_set( + P->ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } return {}; } @@ -2830,8 +2852,9 @@ static ListOfHGrids getListOfGridSets(PJ_CONTEXT *ctx, const char *grids) { auto gridSet = HorizontalShiftGridSet::open(ctx, gridname); if (!gridSet) { if (!canFail) { - if (proj_context_errno(ctx) != PJD_ERR_NETWORK_ERROR) { - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + if (proj_context_errno(ctx) != PROJ_ERR_OTHER_NETWORK_ERROR) { + proj_context_errno_set( + ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } return {}; } @@ -3008,7 +3031,7 @@ static PJ_LP pj_hgrid_apply_internal(PJ_CONTEXT *ctx, PJ_LP in, auto newGrid = findGrid(grids, lp, gridset); if (newGrid == nullptr || newGrid == grid || newGrid->isNullGrid()) break; - pj_log(ctx, PJ_LOG_DEBUG_MINOR, "Switching from grid %s to grid %s", + pj_log(ctx, PJ_LOG_TRACE, "Switching from grid %s to grid %s", grid->name().c_str(), newGrid->name().c_str()); grid = newGrid; extent = &(grid->extentAndRes()); @@ -3067,7 +3090,7 @@ PJ_LP pj_hgrid_apply(PJ_CONTEXT *ctx, const ListOfHGrids &grids, PJ_LP lp, HorizontalShiftGridSet *gridset = nullptr; const auto grid = findGrid(grids, lp, gridset); if (!grid) { - proj_context_errno_set(ctx, PJD_ERR_GRID_AREA); + proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); return out; } if (grid->isNullGrid()) { @@ -3083,7 +3106,7 @@ PJ_LP pj_hgrid_apply(PJ_CONTEXT *ctx, const ListOfHGrids &grids, PJ_LP lp, } if (out.lam == HUGE_VAL || out.phi == HUGE_VAL) - proj_context_errno_set(ctx, PJD_ERR_GRID_AREA); + proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); return out; } @@ -3099,7 +3122,7 @@ PJ_LP pj_hgrid_value(PJ *P, const ListOfHGrids &grids, PJ_LP lp) { HorizontalShiftGridSet *gridset = nullptr; const auto grid = findGrid(grids, lp, gridset); if (!grid) { - proj_context_errno_set(P->ctx, PJD_ERR_GRID_AREA); + proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); return out; } @@ -3107,8 +3130,9 @@ PJ_LP pj_hgrid_value(PJ *P, const ListOfHGrids &grids, PJ_LP lp) { const auto &extent = grid->extentAndRes(); if (!extent.isGeographic) { pj_log(P->ctx, PJ_LOG_ERROR, - "Can only handle grids referenced in a geographic CRS"); - proj_context_errno_set(P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + _("Can only handle grids referenced in a geographic CRS")); + proj_context_errno_set(P->ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return out; } @@ -3131,7 +3155,7 @@ PJ_LP pj_hgrid_value(PJ *P, const ListOfHGrids &grids, PJ_LP lp) { } if (out.lam == HUGE_VAL || out.phi == HUGE_VAL) { - proj_context_errno_set(P->ctx, PJD_ERR_GRID_AREA); + proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); } return out; @@ -3158,7 +3182,7 @@ static double read_vgrid_value(PJ_CONTEXT *ctx, const ListOfVGrids &grids, } } if (!grid) { - proj_context_errno_set(ctx, PJD_ERR_GRID_AREA); + proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); return HUGE_VAL; } if (grid->isNullGrid()) { @@ -3168,8 +3192,9 @@ static double read_vgrid_value(PJ_CONTEXT *ctx, const ListOfVGrids &grids, const auto &extent = grid->extentAndRes(); if (!extent.isGeographic) { pj_log(ctx, PJ_LOG_ERROR, - "Can only handle grids referenced in a geographic CRS"); - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + _("Can only handle grids referenced in a geographic CRS")); + proj_context_errno_set(ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return HUGE_VAL; } @@ -3200,8 +3225,8 @@ static double read_vgrid_value(PJ_CONTEXT *ctx, const ListOfVGrids &grids, int grid_ix = static_cast<int>(lround(floor(grid_x))); if (!(grid_ix >= 0 && grid_ix < grid->width())) { // in the unlikely case we end up here... - pj_log(ctx, PJ_LOG_ERROR, "grid_ix not in grid"); - proj_context_errno_set(ctx, PJD_ERR_GRID_AREA); + pj_log(ctx, PJ_LOG_ERROR, _("grid_ix not in grid")); + proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); return HUGE_VAL; } int grid_iy = static_cast<int>(lround(floor(grid_y))); @@ -3268,9 +3293,10 @@ static double read_vgrid_value(PJ_CONTEXT *ctx, const ListOfVGrids &grids, total_weight += weight; n_weights++; } - if (n_weights == 0) + if (n_weights == 0) { + proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_GRID_AT_NODATA); value = HUGE_VAL; - else if (n_weights != 4) + } else if (n_weights != 4) value /= total_weight; return value * vmultiplier; @@ -3309,8 +3335,10 @@ ListOfVGrids pj_vgrid_init(PJ *P, const char *gridkey) { auto gridSet = VerticalShiftGridSet::open(P->ctx, gridname); if (!gridSet) { if (!canFail) { - if (proj_context_errno(P->ctx) != PJD_ERR_NETWORK_ERROR) { - proj_context_errno_set(P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + if (proj_context_errno(P->ctx) != + PROJ_ERR_OTHER_NETWORK_ERROR) { + proj_context_errno_set( + P->ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } return {}; } @@ -3379,7 +3407,8 @@ bool pj_bilinear_interpolation_three_samples( if (!extent.isGeographic) { pj_log(ctx, PJ_LOG_ERROR, "Can only handle grids referenced in a geographic CRS"); - proj_context_errno_set(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_context_errno_set(ctx, + PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return false; } diff --git a/src/init.cpp b/src/init.cpp index 1e89402d..a0d727cb 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -30,7 +30,6 @@ #define PJ_LIB__ #include <ctype.h> -#include <errno.h> #include <math.h> #include <stddef.h> #include <stdio.h> @@ -55,7 +54,7 @@ static paralist *string_to_paralist (PJ_CONTEXT *ctx, char *definition) { /* Keep a handle to the start of the list, so we have something to return */ auto param = pj_mkparam_ws (c, &c); if (nullptr==param) { - free_params (ctx, first, ENOMEM); + free_params (ctx, first, PROJ_ERR_OTHER /*ENOMEM*/); return nullptr; } if (nullptr==last) { @@ -104,7 +103,8 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) { /* Locate the name of the section we search for */ section = strrchr(fname, ':'); if (nullptr==section) { - proj_context_errno_set (ctx, PJD_ERR_NO_COLON_IN_INIT_STRING); + pj_log(ctx, PJ_LOG_ERROR, _("Missing colon in +init")); + proj_context_errno_set (ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); free (fname); return nullptr; } @@ -117,8 +117,9 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) { auto file = NS_PROJ::FileManager::open_resource_file(ctx, fname); if (nullptr==file) { + pj_log(ctx, PJ_LOG_ERROR, _("Cannot open %s"), fname); + proj_context_errno_set (ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); free (fname); - proj_context_errno_set (ctx, PJD_ERR_NO_OPTION_IN_INIT_FILE); return nullptr; } @@ -131,8 +132,9 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) { line = file->read_line(MAX_LINE_LENGTH, maxLenReached, eofReached); /* End of file? */ if (maxLenReached || eofReached) { + pj_log(ctx, PJ_LOG_ERROR, _("Invalid content for %s"), fname); + proj_context_errno_set (ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); free (fname); - proj_context_errno_set (ctx, PJD_ERR_NO_OPTION_IN_INIT_FILE); return nullptr; } @@ -441,7 +443,8 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all ctx->last_errno = 0; if (argc <= 0) { - proj_context_errno_set (ctx, PJD_ERR_NO_ARGS); + pj_log(ctx, PJ_LOG_ERROR, _("No arguments")); + proj_context_errno_set (ctx, PROJ_ERR_INVALID_OP_MISSING_ARG); return nullptr; } @@ -455,13 +458,15 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all /* can't have nested pipelines directly */ if (n_pipelines > 1) { - proj_context_errno_set (ctx, PJD_ERR_MALFORMED_PIPELINE); + pj_log(ctx, PJ_LOG_ERROR, _("Nested pipelines are not supported")); + proj_context_errno_set (ctx, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); return nullptr; } /* don't allow more than one +init in non-pipeline operations */ if (n_pipelines == 0 && n_inits > 1) { - proj_context_errno_set (ctx, PJD_ERR_TOO_MANY_INITS); + pj_log(ctx, PJ_LOG_ERROR, _("Too many inits")); + proj_context_errno_set (ctx, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); return nullptr; } @@ -469,14 +474,14 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all /* put arguments into internal linked list */ start = curr = pj_mkparam(argv[0]); if (!curr) { - free_params (ctx, start, ENOMEM); + free_params (ctx, start, PROJ_ERR_OTHER /*ENOMEM*/); return nullptr; } for (i = 1; i < argc; ++i) { curr->next = pj_mkparam(argv[i]); if (!curr->next) { - free_params (ctx, start, ENOMEM); + free_params (ctx, start, PROJ_ERR_OTHER /*ENOMEM*/); return nullptr; } curr = curr->next; @@ -491,7 +496,7 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all if (init && n_pipelines == 0) { init = pj_expand_init_internal (ctx, init, allow_init_epsg); if (!init) { - free_params (ctx, start, PJD_ERR_NO_ARGS); + free_params (ctx, start, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); return nullptr; } } @@ -503,19 +508,22 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all /* Find projection selection */ curr = pj_param_exists (start, "proj"); if (nullptr==curr) { - free_params (ctx, start, PJD_ERR_PROJ_NOT_NAMED); + pj_log(ctx, PJ_LOG_ERROR, _("Missing proj")); + free_params (ctx, start, PROJ_ERR_INVALID_OP_MISSING_ARG); return nullptr; } name = curr->param; if (strlen (name) < 6) { - free_params (ctx, start, PJD_ERR_PROJ_NOT_NAMED); + pj_log(ctx, PJ_LOG_ERROR, _("Invalid value for proj")); + free_params (ctx, start, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return nullptr; } name += 5; proj = locate_constructor (name); if (nullptr==proj) { - free_params (ctx, start, PJD_ERR_UNKNOWN_PROJECTION_ID); + pj_log(ctx, PJ_LOG_ERROR, _("Unknown projection")); + free_params (ctx, start, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return nullptr; } @@ -524,7 +532,7 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all /* Allocate projection structure */ PIN = proj(nullptr); if (nullptr==PIN) { - free_params (ctx, start, ENOMEM); + free_params (ctx, start, PROJ_ERR_OTHER /*ENOMEM*/); return nullptr; } @@ -550,11 +558,11 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all if (err) { /* Didn't get an ellps, but doesn't need one: Get a free WGS84 */ if (PIN->need_ellps) { - pj_log (ctx, PJ_LOG_DEBUG_MINOR, "pj_init_ctx: Must specify ellipsoid or sphere"); + pj_log (ctx, PJ_LOG_ERROR, _("pj_init_ctx: Must specify ellipsoid or sphere")); return pj_default_destructor (PIN, proj_errno(PIN)); } else { - if (PJD_ERR_MAJOR_AXIS_NOT_GIVEN==proj_errno (PIN)) + if (PIN->a == 0) proj_errno_reset (PIN); PIN->f = 1.0/298.257223563; PIN->a = 6378137.0; @@ -564,7 +572,7 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all PIN->a_orig = PIN->a; PIN->es_orig = PIN->es; if (pj_calc_ellipsoid_params (PIN, PIN->a, PIN->es)) - return pj_default_destructor (PIN, PJD_ERR_INVALID_ECCENTRICITY); + return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); /* Now that we have ellipse information check for WGS84 datum */ if( PIN->datum_type == PJD_3PARAM @@ -596,7 +604,10 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all /* when correcting longitudes around it */ /* The test is written this way to error on long_wrap_center "=" NaN */ if( !(fabs(PIN->long_wrap_center) < 10 * M_TWOPI) ) - return pj_default_destructor (PIN, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); + { + proj_log_error(PIN, _("Invalid value for lon_wrap")); + return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } } /* Axis orientation */ @@ -605,12 +616,18 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all const char *axis_legal = "ewnsud"; const char *axis_arg = pj_param(ctx, start,"saxis").s; if( strlen(axis_arg) != 3 ) - return pj_default_destructor (PIN, PJD_ERR_AXIS); + { + proj_log_error(PIN, _("Invalid value for axis")); + return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } if( strchr( axis_legal, axis_arg[0] ) == nullptr || strchr( axis_legal, axis_arg[1] ) == nullptr || strchr( axis_legal, axis_arg[2] ) == nullptr) - return pj_default_destructor (PIN, PJD_ERR_AXIS); + { + proj_log_error(PIN, _("Invalid value for axis")); + return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } /* TODO: it would be nice to validate we don't have on axis repeated */ strcpy( PIN->axis, axis_arg ); @@ -622,7 +639,10 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all /* Central latitude */ PIN->phi0 = pj_param(ctx, start, "rlat_0").f; if( fabs(PIN->phi0) > M_HALFPI ) - return pj_default_destructor (PIN, PJD_ERR_LAT_LARGER_THAN_90); + { + proj_log_error(PIN, _("Invalid value for lat_0: |lat_0| should be <= 90°")); + return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } /* False easting and northing */ PIN->x0 = pj_param(ctx, start, "dx_0").f; @@ -638,7 +658,10 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all else PIN->k0 = 1.; if (PIN->k0 <= 0.) - return pj_default_destructor (PIN, PJD_ERR_K_LESS_THAN_ZERO); + { + proj_log_error(PIN, _("Invalid value for k/k_0: it should be > 0")); + return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } /* Set units */ units = pj_list_linear_units(); @@ -646,7 +669,10 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all if ((name = pj_param(ctx, start, "sunits").s) != nullptr) { for (i = 0; (s = units[i].id) && strcmp(name, s) ; ++i) ; if (!s) - return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_UNIT_ID); + { + proj_log_error(PIN, _("Invalid value for units")); + return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } s = units[i].to_meter; } if (s || (s = pj_param(ctx, start, "sto_meter").s)) { @@ -657,11 +683,17 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all ++s; double denom = pj_strtod(s, nullptr); if (denom == 0.0) - return pj_default_destructor (PIN, PJD_ERR_UNIT_FACTOR_LESS_THAN_0); + { + proj_log_error(PIN, _("Invalid value for to_meter donominator")); + return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } PIN->to_meter /= denom; } if (PIN->to_meter <= 0.0) - return pj_default_destructor (PIN, PJD_ERR_UNIT_FACTOR_LESS_THAN_0); + { + proj_log_error(PIN, _("Invalid value for to_meter")); + return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } PIN->fr_meter = 1 / PIN->to_meter; } else @@ -672,7 +704,10 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all if ((name = pj_param(ctx, start, "svunits").s) != nullptr) { for (i = 0; (s = units[i].id) && strcmp(name, s) ; ++i) ; if (!s) - return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_UNIT_ID); + { + proj_log_error(PIN, _("Invalid value for vunits")); + return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } s = units[i].to_meter; } if (s || (s = pj_param(ctx, start, "svto_meter").s)) { @@ -683,11 +718,17 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all ++s; double denom = pj_strtod(s, nullptr); if (denom == 0.0) - return pj_default_destructor (PIN, PJD_ERR_UNIT_FACTOR_LESS_THAN_0); + { + proj_log_error(PIN, _("Invalid value for vto_meter donominator")); + return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } PIN->vto_meter /= denom; } if (PIN->vto_meter <= 0.0) - return pj_default_destructor (PIN, PJD_ERR_UNIT_FACTOR_LESS_THAN_0); + { + proj_log_error(PIN, _("Invalid value for vto_meter")); + return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } PIN->vfr_meter = 1. / PIN->vto_meter; } else { PIN->vto_meter = PIN->to_meter; @@ -716,7 +757,10 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all value = name; if (!value) - return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_PRIME_MERIDIAN); + { + proj_log_error(PIN, _("Invalid value for pm")); + return pj_default_destructor (PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } PIN->from_greenwich = dmstor_ctx(ctx,value,nullptr); } else @@ -725,7 +769,7 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all /* Private object for the geodesic functions */ PIN->geod = static_cast<struct geod_geodesic*>(calloc (1, sizeof (struct geod_geodesic))); if (nullptr==PIN->geod) - return pj_default_destructor (PIN, ENOMEM); + return pj_default_destructor (PIN, PROJ_ERR_OTHER /*ENOMEM*/); geod_init(PIN->geod, PIN->a, (1 - sqrt (1 - PIN->es))); /* Projection specific initialization */ diff --git a/src/internal.cpp b/src/internal.cpp index 175ffa9b..b96e2160 100644 --- a/src/internal.cpp +++ b/src/internal.cpp @@ -89,12 +89,9 @@ chained calls starting out with a call to its 2D interface. coo.lp = pj_inv (coo.xy, P); return coo; case PJ_IDENT: - return coo; - default: break; } - proj_errno_set (P, EINVAL); - return proj_coord_error (); + return coo; } @@ -119,12 +116,9 @@ chained calls starting out with a call to its 3D interface. coo.lpz = pj_inv3d (coo.xyz, P); return coo; case PJ_IDENT: - return coo; - default: break; } - proj_errno_set (P, EINVAL); - return proj_coord_error (); + return coo; } /**************************************************************************************/ @@ -416,86 +410,3 @@ to that context. return; errno = err; } - -/* logging */ - -/* pj_vlog resides in pj_log.c and relates to pj_log as vsprintf relates to sprintf */ -void pj_vlog( PJ_CONTEXT *ctx, int level, const char *fmt, va_list args ); - - -/***************************************************************************************/ -PJ_LOG_LEVEL proj_log_level (PJ_CONTEXT *ctx, PJ_LOG_LEVEL log_level) { -/**************************************************************************************** - Set logging level 0-3. Higher number means more debug info. 0 turns it off -****************************************************************************************/ - PJ_LOG_LEVEL previous; - if (nullptr==ctx) - ctx = pj_get_default_ctx(); - if (nullptr==ctx) - return PJ_LOG_TELL; - previous = static_cast<PJ_LOG_LEVEL>(abs (ctx->debug_level)); - if (PJ_LOG_TELL==log_level) - return previous; - ctx->debug_level = log_level; - return previous; -} - - -/*****************************************************************************/ -void proj_log_error (PJ *P, const char *fmt, ...) { -/****************************************************************************** - For reporting the most severe events. -******************************************************************************/ - va_list args; - va_start( args, fmt ); - pj_vlog (pj_get_ctx (P), PJ_LOG_ERROR , fmt, args); - va_end( args ); -} - - -/*****************************************************************************/ -void proj_log_debug (PJ *P, const char *fmt, ...) { -/****************************************************************************** - For reporting debugging information. -******************************************************************************/ - va_list args; - va_start( args, fmt ); - pj_vlog (pj_get_ctx (P), PJ_LOG_DEBUG_MAJOR , fmt, args); - va_end( args ); -} - -/*****************************************************************************/ -void proj_context_log_debug (PJ_CONTEXT *ctx, const char *fmt, ...) { -/****************************************************************************** - For reporting debugging information. -******************************************************************************/ - va_list args; - va_start( args, fmt ); - pj_vlog (ctx, PJ_LOG_DEBUG_MAJOR , fmt, args); - va_end( args ); -} - -/*****************************************************************************/ -void proj_log_trace (PJ *P, const char *fmt, ...) { -/****************************************************************************** - For reporting embarrassingly detailed debugging information. -******************************************************************************/ - va_list args; - va_start( args, fmt ); - pj_vlog (pj_get_ctx (P), PJ_LOG_DEBUG_MINOR , fmt, args); - va_end( args ); -} - - -/*****************************************************************************/ -void proj_log_func (PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf) { -/****************************************************************************** - Put a new logging function into P's context. The opaque object app_data is - passed as first arg at each call to the logger -******************************************************************************/ - if (nullptr==ctx) - ctx = pj_get_default_ctx (); - ctx->logger_app_data = app_data; - if (nullptr!=logf) - ctx->logger = logf; -} diff --git a/src/inv.cpp b/src/inv.cpp index 626c9ee5..b4a42189 100644 --- a/src/inv.cpp +++ b/src/inv.cpp @@ -38,7 +38,7 @@ static PJ_COORD inv_prepare (PJ *P, PJ_COORD coo) { if (coo.v[0] == HUGE_VAL || coo.v[1] == HUGE_VAL || coo.v[2] == HUGE_VAL) { - proj_errno_set (P, PJD_ERR_INVALID_X_OR_Y); + proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error (); } @@ -97,7 +97,7 @@ static PJ_COORD inv_prepare (PJ *P, PJ_COORD coo) { static PJ_COORD inv_finalize (PJ *P, PJ_COORD coo) { if (coo.xyz.x == HUGE_VAL) { - proj_errno_set (P, PJD_ERR_INVALID_X_OR_Y); + proj_errno_set (P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error (); } @@ -163,7 +163,7 @@ PJ_LP pj_inv(PJ_XY xy, PJ *P) { else if (P->inv4d) coo = P->inv4d (coo, P); else { - proj_errno_set (P, EINVAL); + proj_errno_set (P, PROJ_ERR_OTHER_NO_INVERSE_OP); return proj_coord_error ().lp; } if (HUGE_VAL==coo.v[0]) @@ -197,7 +197,7 @@ PJ_LPZ pj_inv3d (PJ_XYZ xyz, PJ *P) { else if (P->inv) coo.lp = P->inv (coo.xy, P); else { - proj_errno_set (P, EINVAL); + proj_errno_set (P, PROJ_ERR_OTHER_NO_INVERSE_OP); return proj_coord_error ().lpz; } if (HUGE_VAL==coo.v[0]) @@ -227,7 +227,7 @@ PJ_COORD pj_inv4d (PJ_COORD coo, PJ *P) { else if (P->inv) coo.lp = P->inv (coo.xy, P); else { - proj_errno_set (P, EINVAL); + proj_errno_set (P, PROJ_ERR_OTHER_NO_INVERSE_OP); return proj_coord_error (); } if (HUGE_VAL==coo.v[0]) diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp index 6bc1f166..cb0c113a 100644 --- a/src/iso19111/c_api.cpp +++ b/src/iso19111/c_api.cpp @@ -82,7 +82,7 @@ static void PROJ_NO_INLINE proj_log_error(PJ_CONTEXT *ctx, const char *function, auto previous_errno = proj_context_errno(ctx); if (previous_errno == 0) { // only set errno if it wasn't set deeper down the call stack - proj_context_errno_set(ctx, PJD_ERR_GENERIC_ERROR); + proj_context_errno_set(ctx, PROJ_ERR_OTHER); } } @@ -344,6 +344,7 @@ const char *proj_context_get_database_metadata(PJ_CONTEXT *ctx, const char *key) { SANITIZE_CTX(ctx); if (!key) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -375,6 +376,7 @@ PJ_GUESSED_WKT_DIALECT proj_context_guess_wkt_dialect(PJ_CONTEXT *ctx, const char *wkt) { (void)ctx; if (!wkt) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return PJ_GUESSED_NOT_WKT; } @@ -423,6 +425,7 @@ static const char *getOptionValue(const char *option, PJ *proj_clone(PJ_CONTEXT *ctx, const PJ *obj) { SANITIZE_CTX(ctx); if (!obj) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -458,6 +461,7 @@ PJ *proj_clone(PJ_CONTEXT *ctx, const PJ *obj) { PJ *proj_create(PJ_CONTEXT *ctx, const char *text) { SANITIZE_CTX(ctx); if (!text) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -534,6 +538,7 @@ PJ *proj_create_from_wkt(PJ_CONTEXT *ctx, const char *wkt, PROJ_STRING_LIST *out_grammar_errors) { SANITIZE_CTX(ctx); if (!wkt) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -647,6 +652,7 @@ PJ *proj_create_from_database(PJ_CONTEXT *ctx, const char *auth_name, const char *const *options) { SANITIZE_CTX(ctx); if (!auth_name || !code) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -752,6 +758,7 @@ int proj_uom_get_info_from_database(PJ_CONTEXT *ctx, const char *auth_name, SANITIZE_CTX(ctx); if (!auth_name || !code) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } @@ -804,6 +811,7 @@ int PROJ_DLL proj_grid_get_info_from_database( int *out_direct_download, int *out_open_license, int *out_available) { SANITIZE_CTX(ctx); if (!grid_name) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } @@ -863,6 +871,7 @@ PJ_OBJ_LIST *proj_query_geodetic_crs_from_datum(PJ_CONTEXT *ctx, const char *crs_type) { SANITIZE_CTX(ctx); if (!datum_auth_name || !datum_code) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -1200,6 +1209,7 @@ int proj_is_deprecated(const PJ *obj) { PJ_OBJ_LIST *proj_get_non_deprecated(PJ_CONTEXT *ctx, const PJ *obj) { SANITIZE_CTX(ctx); if (!obj) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -1230,6 +1240,7 @@ static int proj_is_equivalent_to_internal(PJ_CONTEXT *ctx, const PJ *obj, if (!obj || !other) { if (ctx) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); } return false; @@ -1435,6 +1446,7 @@ const char *proj_as_wkt(PJ_CONTEXT *ctx, const PJ *obj, PJ_WKT_TYPE type, const char *const *options) { SANITIZE_CTX(ctx); if (!obj) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -1536,6 +1548,7 @@ const char *proj_as_proj_string(PJ_CONTEXT *ctx, const PJ *obj, const char *const *options) { SANITIZE_CTX(ctx); if (!obj) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -1625,6 +1638,7 @@ const char *proj_as_projjson(PJ_CONTEXT *ctx, const PJ *obj, const char *const *options) { SANITIZE_CTX(ctx); if (!obj) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -1784,6 +1798,7 @@ int proj_get_area_of_use(PJ_CONTEXT *ctx, const PJ *obj, static const GeodeticCRS *extractGeodeticCRS(PJ_CONTEXT *ctx, const PJ *crs, const char *fname) { if (!crs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, fname, "missing required input"); return nullptr; } @@ -1841,6 +1856,7 @@ PJ *proj_crs_get_geodetic_crs(PJ_CONTEXT *ctx, const PJ *crs) { PJ *proj_crs_get_sub_crs(PJ_CONTEXT *ctx, const PJ *crs, int index) { SANITIZE_CTX(ctx); if (!crs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -1875,6 +1891,7 @@ PJ *proj_crs_create_bound_crs(PJ_CONTEXT *ctx, const PJ *base_crs, const PJ *hub_crs, const PJ *transformation) { SANITIZE_CTX(ctx); if (!base_crs || !hub_crs || !transformation) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -1934,6 +1951,7 @@ PJ *proj_crs_create_bound_crs_to_WGS84(PJ_CONTEXT *ctx, const PJ *crs, const char *const *options) { SANITIZE_CTX(ctx); if (!crs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -1996,6 +2014,7 @@ PJ *proj_crs_create_bound_vertical_crs(PJ_CONTEXT *ctx, const PJ *vert_crs, const char *grid_name) { SANITIZE_CTX(ctx); if (!vert_crs || !hub_geographic_3D_crs || !grid_name) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -2122,6 +2141,7 @@ int proj_ellipsoid_get_parameters(PJ_CONTEXT *ctx, const PJ *ellipsoid, double *out_inv_flattening) { SANITIZE_CTX(ctx); if (!ellipsoid) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return FALSE; } @@ -2203,6 +2223,7 @@ int proj_prime_meridian_get_parameters(PJ_CONTEXT *ctx, const char **out_unit_name) { SANITIZE_CTX(ctx); if (!prime_meridian) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } @@ -2288,6 +2309,7 @@ PJ *proj_get_source_crs(PJ_CONTEXT *ctx, const PJ *obj) { PJ *proj_get_target_crs(PJ_CONTEXT *ctx, const PJ *obj) { SANITIZE_CTX(ctx); if (!obj) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -2372,6 +2394,7 @@ PJ_OBJ_LIST *proj_identify(PJ_CONTEXT *ctx, const PJ *obj, int **out_confidence) { SANITIZE_CTX(ctx); if (!obj) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -2468,6 +2491,7 @@ PROJ_STRING_LIST proj_get_codes_from_database(PJ_CONTEXT *ctx, int allow_deprecated) { SANITIZE_CTX(ctx); if (!auth_name) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -2824,6 +2848,7 @@ void proj_unit_list_destroy(PROJ_UNIT_INFO **list) { PJ *proj_crs_get_coordoperation(PJ_CONTEXT *ctx, const PJ *crs) { SANITIZE_CTX(ctx); if (!crs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -2868,6 +2893,7 @@ int proj_coordoperation_get_method_info(PJ_CONTEXT *ctx, const char **out_method_code) { SANITIZE_CTX(ctx); if (!coordoperation) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } @@ -3416,6 +3442,7 @@ PJ *proj_create_compound_crs(PJ_CONTEXT *ctx, const char *crs_name, SANITIZE_CTX(ctx); if (!horiz_crs || !vert_crs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -3458,6 +3485,7 @@ PJ *proj_create_compound_crs(PJ_CONTEXT *ctx, const char *crs_name, PJ PROJ_DLL *proj_alter_name(PJ_CONTEXT *ctx, const PJ *obj, const char *name) { SANITIZE_CTX(ctx); if (!obj || !name) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -3495,6 +3523,7 @@ PJ PROJ_DLL *proj_alter_id(PJ_CONTEXT *ctx, const PJ *obj, const char *auth_name, const char *code) { SANITIZE_CTX(ctx); if (!obj || !auth_name || !code) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -3535,6 +3564,7 @@ PJ *proj_crs_alter_geodetic_crs(PJ_CONTEXT *ctx, const PJ *obj, const PJ *new_geod_crs) { SANITIZE_CTX(ctx); if (!obj || !new_geod_crs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -3648,6 +3678,7 @@ PJ *proj_crs_alter_cs_linear_unit(PJ_CONTEXT *ctx, const PJ *obj, const char *unit_code) { SANITIZE_CTX(ctx); if (!obj) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -3700,6 +3731,7 @@ PJ *proj_crs_alter_parameters_linear_unit(PJ_CONTEXT *ctx, const PJ *obj, int convert_to_new_unit) { SANITIZE_CTX(ctx); if (!obj) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -3745,6 +3777,7 @@ PJ *proj_crs_promote_to_3D(PJ_CONTEXT *ctx, const char *crs_3D_name, const PJ *crs_2D) { SANITIZE_CTX(ctx); if (!crs_2D) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -3804,6 +3837,7 @@ PJ *proj_crs_create_projected_3D_crs_from_2D(PJ_CONTEXT *ctx, const PJ *geog_3D_crs) { SANITIZE_CTX(ctx); if (!projected_2D_crs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -3889,6 +3923,7 @@ PJ *proj_crs_demote_to_2D(PJ_CONTEXT *ctx, const char *crs_2D_name, const PJ *crs_3D) { SANITIZE_CTX(ctx); if (!crs_3D) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -4097,6 +4132,7 @@ PJ *proj_create_transformation(PJ_CONTEXT *ctx, const char *name, double accuracy) { SANITIZE_CTX(ctx); if (!source_crs || !target_crs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -4182,6 +4218,7 @@ PJ *proj_convert_conversion_to_other_method(PJ_CONTEXT *ctx, const char *new_method_name) { SANITIZE_CTX(ctx); if (!conversion) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -4566,6 +4603,7 @@ PJ *proj_create_projected_crs(PJ_CONTEXT *ctx, const char *crs_name, const PJ *coordinate_system) { SANITIZE_CTX(ctx); if (!geodetic_crs || !conversion || !coordinate_system) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -6897,6 +6935,7 @@ int proj_coordoperation_is_instantiable(PJ_CONTEXT *ctx, const PJ *coordoperation) { SANITIZE_CTX(ctx); if (!coordoperation) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } @@ -6942,6 +6981,7 @@ int proj_coordoperation_has_ballpark_transformation(PJ_CONTEXT *ctx, const PJ *coordoperation) { SANITIZE_CTX(ctx); if (!coordoperation) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } @@ -6968,6 +7008,7 @@ int proj_coordoperation_get_param_count(PJ_CONTEXT *ctx, const PJ *coordoperation) { SANITIZE_CTX(ctx); if (!coordoperation) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } @@ -6996,6 +7037,7 @@ int proj_coordoperation_get_param_index(PJ_CONTEXT *ctx, const char *name) { SANITIZE_CTX(ctx); if (!coordoperation || !name) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return -1; } @@ -7057,6 +7099,7 @@ int proj_coordoperation_get_param( const char **out_unit_code, const char **out_unit_category) { SANITIZE_CTX(ctx); if (!coordoperation) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } @@ -7186,6 +7229,7 @@ int proj_coordoperation_get_towgs84_values(PJ_CONTEXT *ctx, int emit_error_if_incompatible) { SANITIZE_CTX(ctx); if (!coordoperation) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } @@ -7225,6 +7269,7 @@ int proj_coordoperation_get_grid_used_count(PJ_CONTEXT *ctx, const PJ *coordoperation) { SANITIZE_CTX(ctx); if (!coordoperation) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } @@ -7418,6 +7463,7 @@ void proj_operation_factory_context_set_desired_accuracy( double accuracy) { SANITIZE_CTX(ctx); if (!factory_ctx) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } @@ -7449,6 +7495,7 @@ void proj_operation_factory_context_set_area_of_interest( double north_lat_degree) { SANITIZE_CTX(ctx); if (!factory_ctx) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } @@ -7478,6 +7525,7 @@ void proj_operation_factory_context_set_crs_extent_use( PROJ_CRS_EXTENT_USE use) { SANITIZE_CTX(ctx); if (!factory_ctx) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } @@ -7527,6 +7575,7 @@ void PROJ_DLL proj_operation_factory_context_set_spatial_criterion( PROJ_SPATIAL_CRITERION criterion) { SANITIZE_CTX(ctx); if (!factory_ctx) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } @@ -7564,6 +7613,7 @@ void PROJ_DLL proj_operation_factory_context_set_grid_availability_use( PROJ_GRID_AVAILABILITY_USE use) { SANITIZE_CTX(ctx); if (!factory_ctx) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } @@ -7614,6 +7664,7 @@ void proj_operation_factory_context_set_use_proj_alternative_grid_names( int usePROJNames) { SANITIZE_CTX(ctx); if (!factory_ctx) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } @@ -7650,6 +7701,7 @@ void proj_operation_factory_context_set_allow_use_intermediate_crs( PROJ_INTERMEDIATE_CRS_USE use) { SANITIZE_CTX(ctx); if (!factory_ctx) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } @@ -7691,6 +7743,7 @@ void proj_operation_factory_context_set_allowed_intermediate_crs( const char *const *list_of_auth_name_codes) { SANITIZE_CTX(ctx); if (!factory_ctx) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } @@ -7720,6 +7773,7 @@ void PROJ_DLL proj_operation_factory_context_set_discard_superseded( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, int discard) { SANITIZE_CTX(ctx); if (!factory_ctx) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } @@ -7743,6 +7797,7 @@ 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); if (!factory_ctx) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } @@ -7840,6 +7895,7 @@ proj_create_operations(PJ_CONTEXT *ctx, const PJ *source_crs, const PJ_OPERATION_FACTORY_CONTEXT *operationContext) { SANITIZE_CTX(ctx); if (!source_crs || !target_crs || !operationContext) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -7951,6 +8007,7 @@ int proj_list_get_count(const PJ_OBJ_LIST *result) { PJ *proj_list_get(PJ_CONTEXT *ctx, const PJ_OBJ_LIST *result, int index) { SANITIZE_CTX(ctx); if (!result) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -7984,6 +8041,7 @@ double proj_coordoperation_get_accuracy(PJ_CONTEXT *ctx, const PJ *coordoperation) { SANITIZE_CTX(ctx); if (!coordoperation) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return -1; } @@ -8024,6 +8082,7 @@ double proj_coordoperation_get_accuracy(PJ_CONTEXT *ctx, PJ *proj_crs_get_datum(PJ_CONTEXT *ctx, const PJ *crs) { SANITIZE_CTX(ctx); if (!crs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -8060,6 +8119,7 @@ PJ *proj_crs_get_datum(PJ_CONTEXT *ctx, const PJ *crs) { PJ *proj_crs_get_datum_ensemble(PJ_CONTEXT *ctx, const PJ *crs) { SANITIZE_CTX(ctx); if (!crs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -8088,6 +8148,7 @@ int proj_datum_ensemble_get_member_count(PJ_CONTEXT *ctx, const PJ *datum_ensemble) { SANITIZE_CTX(ctx); if (!datum_ensemble) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return 0; } @@ -8114,6 +8175,7 @@ double proj_datum_ensemble_get_accuracy(PJ_CONTEXT *ctx, const PJ *datum_ensemble) { SANITIZE_CTX(ctx); if (!datum_ensemble) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return -1; } @@ -8152,6 +8214,7 @@ PJ *proj_datum_ensemble_get_member(PJ_CONTEXT *ctx, const PJ *datum_ensemble, int member_index) { SANITIZE_CTX(ctx); if (!datum_ensemble) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -8191,6 +8254,7 @@ PJ *proj_datum_ensemble_get_member(PJ_CONTEXT *ctx, const PJ *datum_ensemble, PJ *proj_crs_get_datum_forced(PJ_CONTEXT *ctx, const PJ *crs) { SANITIZE_CTX(ctx); if (!crs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -8225,6 +8289,7 @@ double proj_dynamic_datum_get_frame_reference_epoch(PJ_CONTEXT *ctx, const PJ *datum) { SANITIZE_CTX(ctx); if (!datum) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return -1; } @@ -8259,6 +8324,7 @@ double proj_dynamic_datum_get_frame_reference_epoch(PJ_CONTEXT *ctx, PJ *proj_crs_get_coordinate_system(PJ_CONTEXT *ctx, const PJ *crs) { SANITIZE_CTX(ctx); if (!crs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -8281,6 +8347,7 @@ PJ *proj_crs_get_coordinate_system(PJ_CONTEXT *ctx, const PJ *crs) { PJ_COORDINATE_SYSTEM_TYPE proj_cs_get_type(PJ_CONTEXT *ctx, const PJ *cs) { SANITIZE_CTX(ctx); if (!cs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return PJ_CS_TYPE_UNKNOWN; } @@ -8330,6 +8397,7 @@ PJ_COORDINATE_SYSTEM_TYPE proj_cs_get_type(PJ_CONTEXT *ctx, const PJ *cs) { int proj_cs_get_axis_count(PJ_CONTEXT *ctx, const PJ *cs) { SANITIZE_CTX(ctx); if (!cs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return -1; } @@ -8373,6 +8441,7 @@ int proj_cs_get_axis_info(PJ_CONTEXT *ctx, const PJ *cs, int index, const char **out_unit_code) { SANITIZE_CTX(ctx); if (!cs) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } @@ -8526,6 +8595,7 @@ PJ *proj_coordoperation_create_inverse(PJ_CONTEXT *ctx, const PJ *obj) { SANITIZE_CTX(ctx); if (!obj) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } @@ -8557,6 +8627,7 @@ int proj_concatoperation_get_step_count(PJ_CONTEXT *ctx, const PJ *concatoperation) { SANITIZE_CTX(ctx); if (!concatoperation) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } @@ -8590,6 +8661,7 @@ PJ *proj_concatoperation_get_step(PJ_CONTEXT *ctx, const PJ *concatoperation, int i_step) { SANITIZE_CTX(ctx); if (!concatoperation) { + proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp deleted file mode 100644 index 83b626b3..00000000 --- a/src/iso19111/coordinateoperation.cpp +++ /dev/null @@ -1,16224 +0,0 @@ -/****************************************************************************** - * - * Project: PROJ - * Purpose: ISO19111:2019 implementation - * Author: Even Rouault <even dot rouault at spatialys dot com> - * - ****************************************************************************** - * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - ****************************************************************************/ - -#ifndef FROM_PROJ_CPP -#define FROM_PROJ_CPP -#endif -#define FROM_COORDINATE_OPERATION_CPP - -#include "proj/coordinateoperation.hpp" -#include "proj/common.hpp" -#include "proj/crs.hpp" -#include "proj/io.hpp" -#include "proj/metadata.hpp" -#include "proj/util.hpp" - -#include "proj/internal/internal.hpp" -#include "proj/internal/io_internal.hpp" -#include "proj/internal/tracing.hpp" - -// PROJ include order is sensitive -// clang-format off -#include "proj.h" -#include "proj_internal.h" // M_PI -// clang-format on - -#include "proj_json_streaming_writer.hpp" - -#include <algorithm> -#include <cassert> -#include <cmath> -#include <cstring> -#include <memory> -#include <set> -#include <string> -#include <vector> - -// #define TRACE_CREATE_OPERATIONS -// #define DEBUG_SORT -// #define DEBUG_CONCATENATED_OPERATION -#if defined(DEBUG_SORT) || defined(DEBUG_CONCATENATED_OPERATION) -#include <iostream> - -void dumpWKT(const NS_PROJ::crs::CRS *crs); -void dumpWKT(const NS_PROJ::crs::CRS *crs) { - auto f(NS_PROJ::io::WKTFormatter::create( - NS_PROJ::io::WKTFormatter::Convention::WKT2_2019)); - std::cerr << crs->exportToWKT(f.get()) << std::endl; -} - -void dumpWKT(const NS_PROJ::crs::CRSPtr &crs); -void dumpWKT(const NS_PROJ::crs::CRSPtr &crs) { dumpWKT(crs.get()); } - -void dumpWKT(const NS_PROJ::crs::CRSNNPtr &crs); -void dumpWKT(const NS_PROJ::crs::CRSNNPtr &crs) { - dumpWKT(crs.as_nullable().get()); -} - -void dumpWKT(const NS_PROJ::crs::GeographicCRSPtr &crs); -void dumpWKT(const NS_PROJ::crs::GeographicCRSPtr &crs) { dumpWKT(crs.get()); } - -void dumpWKT(const NS_PROJ::crs::GeographicCRSNNPtr &crs); -void dumpWKT(const NS_PROJ::crs::GeographicCRSNNPtr &crs) { - dumpWKT(crs.as_nullable().get()); -} - -#endif - -using namespace NS_PROJ::internal; - -#if 0 -namespace dropbox{ namespace oxygen { -template<> nn<NS_PROJ::operation::CoordinateOperationPtr>::~nn() = default; -template<> nn<NS_PROJ::operation::SingleOperationPtr>::~nn() = default; -template<> nn<NS_PROJ::operation::ConversionPtr>::~nn() = default; -template<> nn<NS_PROJ::operation::TransformationPtr>::~nn() = default; -template<> nn<NS_PROJ::operation::ConcatenatedOperationPtr>::~nn() = default; -template<> nn<NS_PROJ::operation::PointMotionOperationPtr>::~nn() = default; -template<> nn<NS_PROJ::operation::GeneralOperationParameterPtr>::~nn() = default; -template<> nn<NS_PROJ::operation::OperationParameterPtr>::~nn() = default; -template<> nn<NS_PROJ::operation::GeneralParameterValuePtr>::~nn() = default; -template<> nn<NS_PROJ::operation::ParameterValuePtr>::~nn() = default; -template<> nn<NS_PROJ::operation::OperationMethodPtr>::~nn() = default; -template<> nn<NS_PROJ::operation::OperationParameterValuePtr>::~nn() = default; -template<> nn<std::unique_ptr<NS_PROJ::operation::CoordinateOperationFactory, std::default_delete<NS_PROJ::operation::CoordinateOperationFactory> > >::~nn() = default; -template<> nn<std::unique_ptr<NS_PROJ::operation::CoordinateOperationContext, std::default_delete<NS_PROJ::operation::CoordinateOperationContext> > >::~nn() = default; -}} -#endif - -#include "proj/internal/coordinateoperation_constants.hpp" -#include "proj/internal/coordinateoperation_internal.hpp" -#include "proj/internal/esri_projection_mappings.hpp" - -#if 0 -namespace dropbox{ namespace oxygen { -template<> nn<std::shared_ptr<NS_PROJ::operation::InverseCoordinateOperation>>::~nn() = default; -template<> nn<std::shared_ptr<NS_PROJ::operation::InverseConversion>>::~nn() = default; -template<> nn<std::shared_ptr<NS_PROJ::operation::InverseTransformation>>::~nn() = default; -template<> nn<NS_PROJ::operation::PROJBasedOperationPtr>::~nn() = default; -}} -#endif - -// --------------------------------------------------------------------------- - -NS_PROJ_START -namespace operation { - -//! @cond Doxygen_Suppress - -constexpr double UTM_LATITUDE_OF_NATURAL_ORIGIN = 0.0; -constexpr double UTM_SCALE_FACTOR = 0.9996; -constexpr double UTM_FALSE_EASTING = 500000.0; -constexpr double UTM_NORTH_FALSE_NORTHING = 0.0; -constexpr double UTM_SOUTH_FALSE_NORTHING = 10000000.0; - -static const std::string INVERSE_OF = "Inverse of "; -static const char *BALLPARK_GEOCENTRIC_TRANSLATION = - "Ballpark geocentric translation"; -static const char *NULL_GEOGRAPHIC_OFFSET = "Null geographic offset"; -static const char *NULL_GEOCENTRIC_TRANSLATION = "Null geocentric translation"; -static const char *BALLPARK_GEOGRAPHIC_OFFSET = "Ballpark geographic offset"; -static const char *BALLPARK_VERTICAL_TRANSFORMATION = - " (ballpark vertical transformation)"; -static const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT = - " (ballpark vertical transformation, without ellipsoid height to vertical " - "height correction)"; - -static const std::string AXIS_ORDER_CHANGE_2D_NAME = "axis order change (2D)"; -static const std::string AXIS_ORDER_CHANGE_3D_NAME = - "axis order change (geographic3D horizontal)"; -//! @endcond - -//! @cond Doxygen_Suppress -static util::PropertyMap -createPropertiesForInverse(const CoordinateOperation *op, bool derivedFrom, - bool approximateInversion); -//! @endcond - -// --------------------------------------------------------------------------- - -#ifdef TRACE_CREATE_OPERATIONS - -//! @cond Doxygen_Suppress - -static std::string objectAsStr(const common::IdentifiedObject *obj) { - std::string ret(obj->nameStr()); - const auto &ids = obj->identifiers(); - if (!ids.empty()) { - ret += " ("; - ret += (*ids[0]->codeSpace()) + ":" + ids[0]->code(); - ret += ")"; - } - return ret; -} -//! @endcond - -#endif - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -class InvalidOperationEmptyIntersection : public InvalidOperation { - public: - explicit InvalidOperationEmptyIntersection(const std::string &message); - InvalidOperationEmptyIntersection( - const InvalidOperationEmptyIntersection &other); - ~InvalidOperationEmptyIntersection() override; -}; - -InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection( - const std::string &message) - : InvalidOperation(message) {} - -InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection( - const InvalidOperationEmptyIntersection &) = default; - -InvalidOperationEmptyIntersection::~InvalidOperationEmptyIntersection() = - default; - -// --------------------------------------------------------------------------- - -static std::string createEntryEqParam(const std::string &a, - const std::string &b) { - return a < b ? a + b : b + a; -} - -static std::set<std::string> buildSetEquivalentParameters() { - - std::set<std::string> set; - - const char *const listOfEquivalentParameterNames[][7] = { - {"latitude_of_point_1", "Latitude_Of_1st_Point", nullptr}, - {"longitude_of_point_1", "Longitude_Of_1st_Point", nullptr}, - {"latitude_of_point_2", "Latitude_Of_2nd_Point", nullptr}, - {"longitude_of_point_2", "Longitude_Of_2nd_Point", nullptr}, - - {"satellite_height", "height", nullptr}, - - {EPSG_NAME_PARAMETER_FALSE_EASTING, - EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, - EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, nullptr}, - - {EPSG_NAME_PARAMETER_FALSE_NORTHING, - EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, - EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, nullptr}, - - {EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR, - EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, - EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, nullptr}, - - {WKT1_LATITUDE_OF_ORIGIN, WKT1_LATITUDE_OF_CENTER, - EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, - EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, "Central_Parallel", - nullptr}, - - {WKT1_CENTRAL_MERIDIAN, WKT1_LONGITUDE_OF_CENTER, - EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, - EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, - EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, nullptr}, - - {"pseudo_standard_parallel_1", WKT1_STANDARD_PARALLEL_1, nullptr}, - }; - - for (const auto ¶mList : listOfEquivalentParameterNames) { - for (size_t i = 0; paramList[i]; i++) { - auto a = metadata::Identifier::canonicalizeName(paramList[i]); - for (size_t j = i + 1; paramList[j]; j++) { - auto b = metadata::Identifier::canonicalizeName(paramList[j]); - set.insert(createEntryEqParam(a, b)); - } - } - } - return set; -} - -bool areEquivalentParameters(const std::string &a, const std::string &b) { - - static const std::set<std::string> setEquivalentParameters = - buildSetEquivalentParameters(); - - auto a_can = metadata::Identifier::canonicalizeName(a); - auto b_can = metadata::Identifier::canonicalizeName(b); - return setEquivalentParameters.find(createEntryEqParam(a_can, b_can)) != - setEquivalentParameters.end(); -} - -// --------------------------------------------------------------------------- - -PROJ_NO_INLINE const MethodMapping *getMapping(int epsg_code) noexcept { - for (const auto &mapping : projectionMethodMappings) { - if (mapping.epsg_code == epsg_code) { - return &mapping; - } - } - return nullptr; -} - -// --------------------------------------------------------------------------- - -const MethodMapping *getMapping(const OperationMethod *method) noexcept { - const std::string &name(method->nameStr()); - const int epsg_code = method->getEPSGCode(); - for (const auto &mapping : projectionMethodMappings) { - if ((epsg_code != 0 && mapping.epsg_code == epsg_code) || - metadata::Identifier::isEquivalentName(mapping.wkt2_name, - name.c_str())) { - return &mapping; - } - } - return nullptr; -} - -// --------------------------------------------------------------------------- - -const MethodMapping *getMappingFromWKT1(const std::string &wkt1_name) noexcept { - // Unusual for a WKT1 projection name, but mentioned in OGC 12-063r5 C.4.2 - if (ci_starts_with(wkt1_name, "UTM zone")) { - return getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR); - } - - for (const auto &mapping : projectionMethodMappings) { - if (mapping.wkt1_name && metadata::Identifier::isEquivalentName( - mapping.wkt1_name, wkt1_name.c_str())) { - return &mapping; - } - } - return nullptr; -} -// --------------------------------------------------------------------------- - -const MethodMapping *getMapping(const char *wkt2_name) noexcept { - for (const auto &mapping : projectionMethodMappings) { - if (metadata::Identifier::isEquivalentName(mapping.wkt2_name, - wkt2_name)) { - return &mapping; - } - } - for (const auto &mapping : otherMethodMappings) { - if (metadata::Identifier::isEquivalentName(mapping.wkt2_name, - wkt2_name)) { - return &mapping; - } - } - return nullptr; -} - -// --------------------------------------------------------------------------- - -std::vector<const MethodMapping *> -getMappingsFromPROJName(const std::string &projName) { - std::vector<const MethodMapping *> res; - for (const auto &mapping : projectionMethodMappings) { - if (mapping.proj_name_main && projName == mapping.proj_name_main) { - res.push_back(&mapping); - } - } - return res; -} - -// --------------------------------------------------------------------------- - -static const ParamMapping *getMapping(const MethodMapping *mapping, - const OperationParameterNNPtr ¶m) { - if (mapping->params == nullptr) { - return nullptr; - } - - // First try with id - const int epsg_code = param->getEPSGCode(); - if (epsg_code) { - for (int i = 0; mapping->params[i] != nullptr; ++i) { - const auto *paramMapping = mapping->params[i]; - if (paramMapping->epsg_code == epsg_code) { - return paramMapping; - } - } - } - - // then equivalent name - const std::string &name = param->nameStr(); - for (int i = 0; mapping->params[i] != nullptr; ++i) { - const auto *paramMapping = mapping->params[i]; - if (metadata::Identifier::isEquivalentName(paramMapping->wkt2_name, - name.c_str())) { - return paramMapping; - } - } - - // and finally different name, but equivalent parameter - for (int i = 0; mapping->params[i] != nullptr; ++i) { - const auto *paramMapping = mapping->params[i]; - if (areEquivalentParameters(paramMapping->wkt2_name, name)) { - return paramMapping; - } - } - - return nullptr; -} - -// --------------------------------------------------------------------------- - -const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping, - const std::string &wkt1_name) { - for (int i = 0; mapping->params[i] != nullptr; ++i) { - const auto *paramMapping = mapping->params[i]; - if (paramMapping->wkt1_name && - (metadata::Identifier::isEquivalentName(paramMapping->wkt1_name, - wkt1_name.c_str()) || - areEquivalentParameters(paramMapping->wkt1_name, wkt1_name))) { - return paramMapping; - } - } - return nullptr; -} - -// --------------------------------------------------------------------------- - -std::vector<const ESRIMethodMapping *> -getMappingsFromESRI(const std::string &esri_name) { - std::vector<const ESRIMethodMapping *> res; - for (const auto &mapping : esriMappings) { - if (ci_equal(esri_name, mapping.esri_name)) { - res.push_back(&mapping); - } - } - return res; -} - -// --------------------------------------------------------------------------- - -static const ESRIMethodMapping *getESRIMapping(const std::string &wkt2_name, - int epsg_code) { - for (const auto &mapping : esriMappings) { - if ((epsg_code != 0 && mapping.epsg_code == epsg_code) || - ci_equal(wkt2_name, mapping.wkt2_name)) { - return &mapping; - } - } - return nullptr; -} - -// --------------------------------------------------------------------------- - -static double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops); - -// Returns the accuracy of an operation, or -1 if unknown -static double getAccuracy(const CoordinateOperationNNPtr &op) { - - if (dynamic_cast<const Conversion *>(op.get())) { - // A conversion is perfectly accurate. - return 0.0; - } - - double accuracy = -1.0; - const auto &accuracies = op->coordinateOperationAccuracies(); - if (!accuracies.empty()) { - try { - accuracy = c_locale_stod(accuracies[0]->value()); - } catch (const std::exception &) { - } - } else { - auto concatenated = - dynamic_cast<const ConcatenatedOperation *>(op.get()); - if (concatenated) { - accuracy = getAccuracy(concatenated->operations()); - } - } - return accuracy; -} - -// --------------------------------------------------------------------------- - -// Returns the accuracy of a set of concatenated operations, or -1 if unknown -static double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops) { - double accuracy = -1.0; - for (const auto &subop : ops) { - const double subops_accuracy = getAccuracy(subop); - if (subops_accuracy < 0.0) { - return -1.0; - } - if (accuracy < 0.0) { - accuracy = 0.0; - } - accuracy += subops_accuracy; - } - return accuracy; -} - -// --------------------------------------------------------------------------- - -static metadata::ExtentPtr -getExtent(const std::vector<CoordinateOperationNNPtr> &ops, - bool conversionExtentIsWorld, bool &emptyIntersection); - -static metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op, - bool conversionExtentIsWorld, - bool &emptyIntersection) { - auto conv = dynamic_cast<const Conversion *>(op.get()); - if (conv) { - emptyIntersection = false; - return metadata::Extent::WORLD; - } - const auto &domains = op->domains(); - if (!domains.empty()) { - emptyIntersection = false; - return domains[0]->domainOfValidity(); - } - auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get()); - if (!concatenated) { - emptyIntersection = false; - return nullptr; - } - return getExtent(concatenated->operations(), conversionExtentIsWorld, - emptyIntersection); -} - -// --------------------------------------------------------------------------- - -static const metadata::ExtentPtr nullExtent{}; - -static const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs) { - const auto &domains = crs->domains(); - if (!domains.empty()) { - return domains[0]->domainOfValidity(); - } - const auto *boundCRS = dynamic_cast<const crs::BoundCRS *>(crs.get()); - if (boundCRS) { - return getExtent(boundCRS->baseCRS()); - } - return nullExtent; -} - -static const metadata::ExtentPtr -getExtentPossiblySynthetized(const crs::CRSNNPtr &crs, bool &approxOut) { - const auto &rawExtent(getExtent(crs)); - approxOut = false; - if (rawExtent) - return rawExtent; - const auto compoundCRS = dynamic_cast<const crs::CompoundCRS *>(crs.get()); - if (compoundCRS) { - // For a compoundCRS, take the intersection of the extent of its - // components. - const auto &components = compoundCRS->componentReferenceSystems(); - metadata::ExtentPtr extent; - approxOut = true; - for (const auto &component : components) { - const auto &componentExtent(getExtent(component)); - if (extent && componentExtent) - extent = extent->intersection(NN_NO_CHECK(componentExtent)); - else if (componentExtent) - extent = componentExtent; - } - return extent; - } - return rawExtent; -} - -// --------------------------------------------------------------------------- - -static metadata::ExtentPtr -getExtent(const std::vector<CoordinateOperationNNPtr> &ops, - bool conversionExtentIsWorld, bool &emptyIntersection) { - metadata::ExtentPtr res = nullptr; - for (const auto &subop : ops) { - - const auto &subExtent = - getExtent(subop, conversionExtentIsWorld, emptyIntersection); - if (!subExtent) { - if (emptyIntersection) { - return nullptr; - } - continue; - } - if (res == nullptr) { - res = subExtent; - } else { - res = res->intersection(NN_NO_CHECK(subExtent)); - if (!res) { - emptyIntersection = true; - return nullptr; - } - } - } - emptyIntersection = false; - return res; -} - -// --------------------------------------------------------------------------- - -static double getPseudoArea(const metadata::ExtentPtr &extent) { - if (!extent) - return 0.0; - const auto &geographicElements = extent->geographicElements(); - if (geographicElements.empty()) - return 0.0; - auto bbox = dynamic_cast<const metadata::GeographicBoundingBox *>( - geographicElements[0].get()); - if (!bbox) - return 0; - double w = bbox->westBoundLongitude(); - double s = bbox->southBoundLatitude(); - double e = bbox->eastBoundLongitude(); - double n = bbox->northBoundLatitude(); - if (w > e) { - e += 360.0; - } - // Integrate cos(lat) between south_lat and north_lat - return (e - w) * (std::sin(common::Angle(n).getSIValue()) - - std::sin(common::Angle(s).getSIValue())); -} - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct CoordinateOperation::Private { - util::optional<std::string> operationVersion_{}; - std::vector<metadata::PositionalAccuracyNNPtr> - coordinateOperationAccuracies_{}; - std::weak_ptr<crs::CRS> sourceCRSWeak_{}; - std::weak_ptr<crs::CRS> targetCRSWeak_{}; - crs::CRSPtr interpolationCRS_{}; - util::optional<common::DataEpoch> sourceCoordinateEpoch_{}; - util::optional<common::DataEpoch> targetCoordinateEpoch_{}; - bool hasBallparkTransformation_ = false; - bool use3DHelmert_ = false; - - // do not set this for a ProjectedCRS.definingConversion - struct CRSStrongRef { - crs::CRSNNPtr sourceCRS_; - crs::CRSNNPtr targetCRS_; - CRSStrongRef(const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn) - : sourceCRS_(sourceCRSIn), targetCRS_(targetCRSIn) {} - }; - std::unique_ptr<CRSStrongRef> strongRef_{}; - - Private() = default; - Private(const Private &other) - : operationVersion_(other.operationVersion_), - coordinateOperationAccuracies_(other.coordinateOperationAccuracies_), - sourceCRSWeak_(other.sourceCRSWeak_), - targetCRSWeak_(other.targetCRSWeak_), - interpolationCRS_(other.interpolationCRS_), - sourceCoordinateEpoch_(other.sourceCoordinateEpoch_), - targetCoordinateEpoch_(other.targetCoordinateEpoch_), - hasBallparkTransformation_(other.hasBallparkTransformation_), - strongRef_(other.strongRef_ ? internal::make_unique<CRSStrongRef>( - *(other.strongRef_)) - : nullptr) {} - - Private &operator=(const Private &) = delete; -}; - -// --------------------------------------------------------------------------- - -GridDescription::GridDescription() - : shortName{}, fullName{}, packageName{}, url{}, directDownload(false), - openLicense(false), available(false) {} - -GridDescription::~GridDescription() = default; - -GridDescription::GridDescription(const GridDescription &) = default; - -GridDescription::GridDescription(GridDescription &&other) noexcept - : shortName(std::move(other.shortName)), - fullName(std::move(other.fullName)), - packageName(std::move(other.packageName)), - url(std::move(other.url)), - directDownload(other.directDownload), - openLicense(other.openLicense), - available(other.available) {} - -//! @endcond - -// --------------------------------------------------------------------------- - -CoordinateOperation::CoordinateOperation() - : d(internal::make_unique<Private>()) {} - -// --------------------------------------------------------------------------- - -CoordinateOperation::CoordinateOperation(const CoordinateOperation &other) - : ObjectUsage(other), d(internal::make_unique<Private>(*other.d)) {} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -CoordinateOperation::~CoordinateOperation() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Return the version of the coordinate transformation (i.e. - * instantiation - * due to the stochastic nature of the parameters). - * - * Mandatory when describing a coordinate transformation or point motion - * operation, and should not be supplied for a coordinate conversion. - * - * @return version or empty. - */ -const util::optional<std::string> & -CoordinateOperation::operationVersion() const { - return d->operationVersion_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return estimate(s) of the impact of this coordinate operation on - * point accuracy. - * - * Gives position error estimates for target coordinates of this coordinate - * operation, assuming no errors in source coordinates. - * - * @return estimate(s) or empty vector. - */ -const std::vector<metadata::PositionalAccuracyNNPtr> & -CoordinateOperation::coordinateOperationAccuracies() const { - return d->coordinateOperationAccuracies_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the source CRS of this coordinate operation. - * - * This should not be null, expect for of a derivingConversion of a DerivedCRS - * when the owning DerivedCRS has been destroyed. - * - * @return source CRS, or null. - */ -const crs::CRSPtr CoordinateOperation::sourceCRS() const { - return d->sourceCRSWeak_.lock(); -} - -// --------------------------------------------------------------------------- - -/** \brief Return the target CRS of this coordinate operation. - * - * This should not be null, expect for of a derivingConversion of a DerivedCRS - * when the owning DerivedCRS has been destroyed. - * - * @return target CRS, or null. - */ -const crs::CRSPtr CoordinateOperation::targetCRS() const { - return d->targetCRSWeak_.lock(); -} - -// --------------------------------------------------------------------------- - -/** \brief Return the interpolation CRS of this coordinate operation. - * - * @return interpolation CRS, or null. - */ -const crs::CRSPtr &CoordinateOperation::interpolationCRS() const { - return d->interpolationCRS_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the source epoch of coordinates. - * - * @return source epoch of coordinates, or empty. - */ -const util::optional<common::DataEpoch> & -CoordinateOperation::sourceCoordinateEpoch() const { - return d->sourceCoordinateEpoch_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the target epoch of coordinates. - * - * @return target epoch of coordinates, or empty. - */ -const util::optional<common::DataEpoch> & -CoordinateOperation::targetCoordinateEpoch() const { - return d->targetCoordinateEpoch_; -} - -// --------------------------------------------------------------------------- - -void CoordinateOperation::setWeakSourceTargetCRS( - std::weak_ptr<crs::CRS> sourceCRSIn, std::weak_ptr<crs::CRS> targetCRSIn) { - d->sourceCRSWeak_ = sourceCRSIn; - d->targetCRSWeak_ = targetCRSIn; -} - -// --------------------------------------------------------------------------- - -void CoordinateOperation::setCRSs(const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, - const crs::CRSPtr &interpolationCRSIn) { - d->strongRef_ = - internal::make_unique<Private::CRSStrongRef>(sourceCRSIn, targetCRSIn); - d->sourceCRSWeak_ = sourceCRSIn.as_nullable(); - d->targetCRSWeak_ = targetCRSIn.as_nullable(); - d->interpolationCRS_ = interpolationCRSIn; -} -// --------------------------------------------------------------------------- - -void CoordinateOperation::setCRSs(const CoordinateOperation *in, - bool inverseSourceTarget) { - auto l_sourceCRS = in->sourceCRS(); - auto l_targetCRS = in->targetCRS(); - if (l_sourceCRS && l_targetCRS) { - auto nn_sourceCRS = NN_NO_CHECK(l_sourceCRS); - auto nn_targetCRS = NN_NO_CHECK(l_targetCRS); - if (inverseSourceTarget) { - setCRSs(nn_targetCRS, nn_sourceCRS, in->interpolationCRS()); - } else { - setCRSs(nn_sourceCRS, nn_targetCRS, in->interpolationCRS()); - } - } -} - -// --------------------------------------------------------------------------- - -void CoordinateOperation::setAccuracies( - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - d->coordinateOperationAccuracies_ = accuracies; -} - -// --------------------------------------------------------------------------- - -/** \brief Return whether a coordinate operation can be instantiated as - * a PROJ pipeline, checking in particular that referenced grids are - * available. - */ -bool CoordinateOperation::isPROJInstantiable( - const io::DatabaseContextPtr &databaseContext, - bool considerKnownGridsAsAvailable) const { - try { - exportToPROJString(io::PROJStringFormatter::create().get()); - } catch (const std::exception &) { - return false; - } - for (const auto &gridDesc : - gridsNeeded(databaseContext, considerKnownGridsAsAvailable)) { - if (!gridDesc.available) { - return false; - } - } - return true; -} - -// --------------------------------------------------------------------------- - -/** \brief Return whether a coordinate operation has a "ballpark" - * transformation, - * that is a very approximate one, due to lack of more accurate transformations. - * - * Typically a null geographic offset between two horizontal datum, or a - * null vertical offset (or limited to unit changes) between two vertical - * datum. Errors of several tens to one hundred meters might be expected, - * compared to more accurate transformations. - */ -bool CoordinateOperation::hasBallparkTransformation() const { - return d->hasBallparkTransformation_; -} - -// --------------------------------------------------------------------------- - -void CoordinateOperation::setHasBallparkTransformation(bool b) { - d->hasBallparkTransformation_ = b; -} - -// --------------------------------------------------------------------------- - -void CoordinateOperation::setProperties( - const util::PropertyMap &properties) // throw(InvalidValueTypeException) -{ - ObjectUsage::setProperties(properties); - properties.getStringValue(OPERATION_VERSION_KEY, d->operationVersion_); -} - -// --------------------------------------------------------------------------- - -/** \brief Return a variation of the current coordinate operation whose axis - * order is the one expected for visualization purposes. - */ -CoordinateOperationNNPtr -CoordinateOperation::normalizeForVisualization() const { - auto l_sourceCRS = sourceCRS(); - auto l_targetCRS = targetCRS(); - if (!l_sourceCRS || !l_targetCRS) { - throw util::UnsupportedOperationException( - "Cannot retrieve source or target CRS"); - } - const bool swapSource = - l_sourceCRS->mustAxisOrderBeSwitchedForVisualization(); - const bool swapTarget = - l_targetCRS->mustAxisOrderBeSwitchedForVisualization(); - auto l_this = NN_NO_CHECK(std::dynamic_pointer_cast<CoordinateOperation>( - shared_from_this().as_nullable())); - if (!swapSource && !swapTarget) { - return l_this; - } - std::vector<CoordinateOperationNNPtr> subOps; - if (swapSource) { - auto op = Conversion::createAxisOrderReversal(false); - op->setCRSs(l_sourceCRS->normalizeForVisualization(), - NN_NO_CHECK(l_sourceCRS), nullptr); - subOps.emplace_back(op); - } - subOps.emplace_back(l_this); - if (swapTarget) { - auto op = Conversion::createAxisOrderReversal(false); - op->setCRSs(NN_NO_CHECK(l_targetCRS), - l_targetCRS->normalizeForVisualization(), nullptr); - subOps.emplace_back(op); - } - return util::nn_static_pointer_cast<CoordinateOperation>( - ConcatenatedOperation::createComputeMetadata(subOps, true)); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -CoordinateOperationNNPtr CoordinateOperation::shallowClone() const { - return _shallowClone(); -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct OperationMethod::Private { - util::optional<std::string> formula_{}; - util::optional<metadata::Citation> formulaCitation_{}; - std::vector<GeneralOperationParameterNNPtr> parameters_{}; - std::string projMethodOverride_{}; -}; -//! @endcond - -// --------------------------------------------------------------------------- - -OperationMethod::OperationMethod() : d(internal::make_unique<Private>()) {} - -// --------------------------------------------------------------------------- - -OperationMethod::OperationMethod(const OperationMethod &other) - : IdentifiedObject(other), d(internal::make_unique<Private>(*other.d)) {} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -OperationMethod::~OperationMethod() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Return the formula(s) or procedure used by this coordinate operation - * method. - * - * This may be a reference to a publication (in which case use - * formulaCitation()). - * - * Note that the operation method may not be analytic, in which case this - * attribute references or contains the procedure, not an analytic formula. - * - * @return the formula, or empty. - */ -const util::optional<std::string> &OperationMethod::formula() PROJ_PURE_DEFN { - return d->formula_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return a reference to a publication giving the formula(s) or - * procedure - * used by the coordinate operation method. - * - * @return the formula citation, or empty. - */ -const util::optional<metadata::Citation> & -OperationMethod::formulaCitation() PROJ_PURE_DEFN { - return d->formulaCitation_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the parameters of this operation method. - * - * @return the parameters. - */ -const std::vector<GeneralOperationParameterNNPtr> & -OperationMethod::parameters() PROJ_PURE_DEFN { - return d->parameters_; -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a operation method from a vector of - * GeneralOperationParameter. - * - * @param properties See \ref general_properties. At minimum the name should be - * defined. - * @param parameters Vector of GeneralOperationParameterNNPtr. - * @return a new OperationMethod. - */ -OperationMethodNNPtr OperationMethod::create( - const util::PropertyMap &properties, - const std::vector<GeneralOperationParameterNNPtr> ¶meters) { - OperationMethodNNPtr method( - OperationMethod::nn_make_shared<OperationMethod>()); - method->assignSelf(method); - method->setProperties(properties); - method->d->parameters_ = parameters; - properties.getStringValue("proj_method", method->d->projMethodOverride_); - return method; -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a operation method from a vector of OperationParameter. - * - * @param properties See \ref general_properties. At minimum the name should be - * defined. - * @param parameters Vector of OperationParameterNNPtr. - * @return a new OperationMethod. - */ -OperationMethodNNPtr OperationMethod::create( - const util::PropertyMap &properties, - const std::vector<OperationParameterNNPtr> ¶meters) { - std::vector<GeneralOperationParameterNNPtr> parametersGeneral; - parametersGeneral.reserve(parameters.size()); - for (const auto &p : parameters) { - parametersGeneral.push_back(p); - } - return create(properties, parametersGeneral); -} - -// --------------------------------------------------------------------------- - -/** \brief Return the EPSG code, either directly, or through the name - * @return code, or 0 if not found - */ -int OperationMethod::getEPSGCode() PROJ_PURE_DEFN { - int epsg_code = IdentifiedObject::getEPSGCode(); - if (epsg_code == 0) { - auto l_name = nameStr(); - if (ends_with(l_name, " (3D)")) { - l_name.resize(l_name.size() - strlen(" (3D)")); - } - for (const auto &tuple : methodNameCodes) { - if (metadata::Identifier::isEquivalentName(l_name.c_str(), - tuple.name)) { - return tuple.epsg_code; - } - } - } - return epsg_code; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -void OperationMethod::_exportToWKT(io::WKTFormatter *formatter) const { - const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; - formatter->startNode(isWKT2 ? io::WKTConstants::METHOD - : io::WKTConstants::PROJECTION, - !identifiers().empty()); - std::string l_name(nameStr()); - if (!isWKT2) { - const MethodMapping *mapping = getMapping(this); - if (mapping == nullptr) { - l_name = replaceAll(l_name, " ", "_"); - } else { - if (l_name == - PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) { - l_name = "Geostationary_Satellite"; - } else { - if (mapping->wkt1_name == nullptr) { - throw io::FormattingException( - std::string("Unsupported conversion method: ") + - mapping->wkt2_name); - } - l_name = mapping->wkt1_name; - } - } - } - formatter->addQuotedString(l_name); - if (formatter->outputId()) { - formatID(formatter); - } - formatter->endNode(); -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -void OperationMethod::_exportToJSON( - io::JSONFormatter *formatter) const // throw(FormattingException) -{ - auto writer = formatter->writer(); - auto objectContext(formatter->MakeObjectContext("OperationMethod", - !identifiers().empty())); - - writer->AddObjKey("name"); - writer->Add(nameStr()); - - if (formatter->outputId()) { - formatID(formatter); - } -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -bool OperationMethod::_isEquivalentTo( - const util::IComparable *other, util::IComparable::Criterion criterion, - const io::DatabaseContextPtr &dbContext) const { - auto otherOM = dynamic_cast<const OperationMethod *>(other); - if (otherOM == nullptr || - !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { - return false; - } - // TODO test formula and formulaCitation - const auto ¶ms = parameters(); - const auto &otherParams = otherOM->parameters(); - const auto paramsSize = params.size(); - if (paramsSize != otherParams.size()) { - return false; - } - if (criterion == util::IComparable::Criterion::STRICT) { - for (size_t i = 0; i < paramsSize; i++) { - if (!params[i]->_isEquivalentTo(otherParams[i].get(), criterion, - dbContext)) { - return false; - } - } - } else { - std::vector<bool> candidateIndices(paramsSize, true); - for (size_t i = 0; i < paramsSize; i++) { - bool found = false; - for (size_t j = 0; j < paramsSize; j++) { - if (candidateIndices[j] && - params[i]->_isEquivalentTo(otherParams[j].get(), criterion, - dbContext)) { - candidateIndices[j] = false; - found = true; - break; - } - } - if (!found) { - return false; - } - } - } - return true; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct GeneralParameterValue::Private {}; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -GeneralParameterValue::GeneralParameterValue() : d(nullptr) {} - -// --------------------------------------------------------------------------- - -GeneralParameterValue::GeneralParameterValue(const GeneralParameterValue &) - : d(nullptr) {} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -GeneralParameterValue::~GeneralParameterValue() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct OperationParameterValue::Private { - OperationParameterNNPtr parameter; - ParameterValueNNPtr parameterValue; - - Private(const OperationParameterNNPtr ¶meterIn, - const ParameterValueNNPtr &valueIn) - : parameter(parameterIn), parameterValue(valueIn) {} -}; -//! @endcond - -// --------------------------------------------------------------------------- - -OperationParameterValue::OperationParameterValue( - const OperationParameterValue &other) - : GeneralParameterValue(other), - d(internal::make_unique<Private>(*other.d)) {} - -// --------------------------------------------------------------------------- - -OperationParameterValue::OperationParameterValue( - const OperationParameterNNPtr ¶meterIn, - const ParameterValueNNPtr &valueIn) - : GeneralParameterValue(), - d(internal::make_unique<Private>(parameterIn, valueIn)) {} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a OperationParameterValue. - * - * @param parameterIn Parameter (definition). - * @param valueIn Parameter value. - * @return a new OperationParameterValue. - */ -OperationParameterValueNNPtr -OperationParameterValue::create(const OperationParameterNNPtr ¶meterIn, - const ParameterValueNNPtr &valueIn) { - return OperationParameterValue::nn_make_shared<OperationParameterValue>( - parameterIn, valueIn); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -OperationParameterValue::~OperationParameterValue() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Return the parameter (definition) - * - * @return the parameter (definition). - */ -const OperationParameterNNPtr & -OperationParameterValue::parameter() PROJ_PURE_DEFN { - return d->parameter; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the parameter value. - * - * @return the parameter value. - */ -const ParameterValueNNPtr & -OperationParameterValue::parameterValue() PROJ_PURE_DEFN { - return d->parameterValue; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -void OperationParameterValue::_exportToWKT( - // cppcheck-suppress passedByValue - io::WKTFormatter *formatter) const { - _exportToWKT(formatter, nullptr); -} - -void OperationParameterValue::_exportToWKT(io::WKTFormatter *formatter, - const MethodMapping *mapping) const { - const ParamMapping *paramMapping = - mapping ? getMapping(mapping, d->parameter) : nullptr; - if (paramMapping && paramMapping->wkt1_name == nullptr) { - return; - } - const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; - if (isWKT2 && parameterValue()->type() == ParameterValue::Type::FILENAME) { - formatter->startNode(io::WKTConstants::PARAMETERFILE, - !parameter()->identifiers().empty()); - } else { - formatter->startNode(io::WKTConstants::PARAMETER, - !parameter()->identifiers().empty()); - } - if (paramMapping) { - formatter->addQuotedString(paramMapping->wkt1_name); - } else { - formatter->addQuotedString(parameter()->nameStr()); - } - parameterValue()->_exportToWKT(formatter); - if (formatter->outputId()) { - parameter()->formatID(formatter); - } - formatter->endNode(); -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -void OperationParameterValue::_exportToJSON( - io::JSONFormatter *formatter) const { - auto writer = formatter->writer(); - auto objectContext(formatter->MakeObjectContext( - "ParameterValue", !parameter()->identifiers().empty())); - - writer->AddObjKey("name"); - writer->Add(parameter()->nameStr()); - - const auto &l_value(parameterValue()); - if (l_value->type() == ParameterValue::Type::MEASURE) { - writer->AddObjKey("value"); - writer->Add(l_value->value().value(), 15); - writer->AddObjKey("unit"); - const auto &l_unit(l_value->value().unit()); - if (l_unit == common::UnitOfMeasure::METRE || - l_unit == common::UnitOfMeasure::DEGREE || - l_unit == common::UnitOfMeasure::SCALE_UNITY) { - writer->Add(l_unit.name()); - } else { - l_unit._exportToJSON(formatter); - } - } else if (l_value->type() == ParameterValue::Type::FILENAME) { - writer->AddObjKey("value"); - writer->Add(l_value->valueFile()); - } - - if (formatter->outputId()) { - parameter()->formatID(formatter); - } -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -/** Utility method used on WKT2 import to convert from abridged transformation - * to "normal" transformation parameters. - */ -bool OperationParameterValue::convertFromAbridged( - const std::string ¶mName, double &val, - const common::UnitOfMeasure *&unit, int ¶mEPSGCode) { - if (metadata::Identifier::isEquivalentName( - paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION) || - paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) { - unit = &common::UnitOfMeasure::METRE; - paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION; - return true; - } else if (metadata::Identifier::isEquivalentName( - paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION) || - paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) { - unit = &common::UnitOfMeasure::METRE; - paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION; - return true; - } else if (metadata::Identifier::isEquivalentName( - paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION) || - paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) { - unit = &common::UnitOfMeasure::METRE; - paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION; - return true; - } else if (metadata::Identifier::isEquivalentName( - paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_ROTATION) || - paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_ROTATION) { - unit = &common::UnitOfMeasure::ARC_SECOND; - paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_ROTATION; - return true; - } else if (metadata::Identifier::isEquivalentName( - paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_ROTATION) || - paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) { - unit = &common::UnitOfMeasure::ARC_SECOND; - paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_ROTATION; - return true; - - } else if (metadata::Identifier::isEquivalentName( - paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_ROTATION) || - paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) { - unit = &common::UnitOfMeasure::ARC_SECOND; - paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_ROTATION; - return true; - - } else if (metadata::Identifier::isEquivalentName( - paramName.c_str(), EPSG_NAME_PARAMETER_SCALE_DIFFERENCE) || - paramEPSGCode == EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) { - val = (val - 1.0) * 1e6; - unit = &common::UnitOfMeasure::PARTS_PER_MILLION; - paramEPSGCode = EPSG_CODE_PARAMETER_SCALE_DIFFERENCE; - return true; - } - return false; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -bool OperationParameterValue::_isEquivalentTo( - const util::IComparable *other, util::IComparable::Criterion criterion, - const io::DatabaseContextPtr &dbContext) const { - auto otherOPV = dynamic_cast<const OperationParameterValue *>(other); - if (otherOPV == nullptr) { - return false; - } - if (!d->parameter->_isEquivalentTo(otherOPV->d->parameter.get(), criterion, - dbContext)) { - return false; - } - if (criterion == util::IComparable::Criterion::STRICT) { - return d->parameterValue->_isEquivalentTo( - otherOPV->d->parameterValue.get(), criterion); - } - if (d->parameterValue->_isEquivalentTo(otherOPV->d->parameterValue.get(), - criterion, dbContext)) { - return true; - } - if (d->parameter->getEPSGCode() == - EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE || - d->parameter->getEPSGCode() == - EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) { - if (parameterValue()->type() == ParameterValue::Type::MEASURE && - otherOPV->parameterValue()->type() == - ParameterValue::Type::MEASURE) { - const double a = std::fmod(parameterValue()->value().convertToUnit( - common::UnitOfMeasure::DEGREE) + - 360.0, - 360.0); - const double b = - std::fmod(otherOPV->parameterValue()->value().convertToUnit( - common::UnitOfMeasure::DEGREE) + - 360.0, - 360.0); - return std::fabs(a - b) <= 1e-10 * std::fabs(a); - } - } - return false; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct GeneralOperationParameter::Private {}; -//! @endcond - -// --------------------------------------------------------------------------- - -GeneralOperationParameter::GeneralOperationParameter() : d(nullptr) {} - -// --------------------------------------------------------------------------- - -GeneralOperationParameter::GeneralOperationParameter( - const GeneralOperationParameter &other) - : IdentifiedObject(other), d(nullptr) {} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -GeneralOperationParameter::~GeneralOperationParameter() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct OperationParameter::Private {}; -//! @endcond - -// --------------------------------------------------------------------------- - -OperationParameter::OperationParameter() : d(nullptr) {} - -// --------------------------------------------------------------------------- - -OperationParameter::OperationParameter(const OperationParameter &other) - : GeneralOperationParameter(other), d(nullptr) {} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -OperationParameter::~OperationParameter() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a OperationParameter. - * - * @param properties See \ref general_properties. At minimum the name should be - * defined. - * @return a new OperationParameter. - */ -OperationParameterNNPtr -OperationParameter::create(const util::PropertyMap &properties) { - OperationParameterNNPtr op( - OperationParameter::nn_make_shared<OperationParameter>()); - op->assignSelf(op); - op->setProperties(properties); - return op; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -bool OperationParameter::_isEquivalentTo( - const util::IComparable *other, util::IComparable::Criterion criterion, - const io::DatabaseContextPtr &dbContext) const { - auto otherOP = dynamic_cast<const OperationParameter *>(other); - if (otherOP == nullptr) { - return false; - } - if (criterion == util::IComparable::Criterion::STRICT) { - return IdentifiedObject::_isEquivalentTo(other, criterion, dbContext); - } - if (IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { - return true; - } - auto l_epsgCode = getEPSGCode(); - return l_epsgCode != 0 && l_epsgCode == otherOP->getEPSGCode(); -} -//! @endcond - -// --------------------------------------------------------------------------- - -void OperationParameter::_exportToWKT(io::WKTFormatter *) const {} - -// --------------------------------------------------------------------------- - -/** \brief Return the name of a parameter designed by its EPSG code - * @return name, or nullptr if not found - */ -const char *OperationParameter::getNameForEPSGCode(int epsg_code) noexcept { - for (const auto &tuple : paramNameCodes) { - if (tuple.epsg_code == epsg_code) { - return tuple.name; - } - } - return nullptr; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the EPSG code, either directly, or through the name - * @return code, or 0 if not found - */ -int OperationParameter::getEPSGCode() PROJ_PURE_DEFN { - int epsg_code = IdentifiedObject::getEPSGCode(); - if (epsg_code == 0) { - const auto &l_name = nameStr(); - for (const auto &tuple : paramNameCodes) { - if (metadata::Identifier::isEquivalentName(l_name.c_str(), - tuple.name)) { - return tuple.epsg_code; - } - } - if (metadata::Identifier::isEquivalentName(l_name.c_str(), - "Latitude of origin")) { - return EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN; - } - if (metadata::Identifier::isEquivalentName(l_name.c_str(), - "Scale factor")) { - return EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN; - } - } - return epsg_code; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct SingleOperation::Private { - std::vector<GeneralParameterValueNNPtr> parameterValues_{}; - OperationMethodNNPtr method_; - - explicit Private(const OperationMethodNNPtr &methodIn) - : method_(methodIn) {} -}; -//! @endcond - -// --------------------------------------------------------------------------- - -SingleOperation::SingleOperation(const OperationMethodNNPtr &methodIn) - : d(internal::make_unique<Private>(methodIn)) {} - -// --------------------------------------------------------------------------- - -SingleOperation::SingleOperation(const SingleOperation &other) - : -#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) - CoordinateOperation(other), -#endif - d(internal::make_unique<Private>(*other.d)) { -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -SingleOperation::~SingleOperation() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Return the parameter values. - * - * @return the parameter values. - */ -const std::vector<GeneralParameterValueNNPtr> & -SingleOperation::parameterValues() PROJ_PURE_DEFN { - return d->parameterValues_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the operation method associated to the operation. - * - * @return the operation method. - */ -const OperationMethodNNPtr &SingleOperation::method() PROJ_PURE_DEFN { - return d->method_; -} - -// --------------------------------------------------------------------------- - -void SingleOperation::setParameterValues( - const std::vector<GeneralParameterValueNNPtr> &values) { - d->parameterValues_ = values; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static const ParameterValuePtr nullParameterValue; -//! @endcond - -/** \brief Return the parameter value corresponding to a parameter name or - * EPSG code - * - * @param paramName the parameter name (or empty, in which case epsg_code - * should be non zero) - * @param epsg_code the parameter EPSG code (possibly zero) - * @return the value, or nullptr if not found. - */ -const ParameterValuePtr & -SingleOperation::parameterValue(const std::string ¶mName, - int epsg_code) const noexcept { - if (epsg_code) { - for (const auto &genOpParamvalue : parameterValues()) { - auto opParamvalue = dynamic_cast<const OperationParameterValue *>( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶meter = opParamvalue->parameter(); - if (parameter->getEPSGCode() == epsg_code) { - return opParamvalue->parameterValue(); - } - } - } - } - for (const auto &genOpParamvalue : parameterValues()) { - auto opParamvalue = dynamic_cast<const OperationParameterValue *>( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶meter = opParamvalue->parameter(); - if (metadata::Identifier::isEquivalentName( - paramName.c_str(), parameter->nameStr().c_str())) { - return opParamvalue->parameterValue(); - } - } - } - for (const auto &genOpParamvalue : parameterValues()) { - auto opParamvalue = dynamic_cast<const OperationParameterValue *>( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶meter = opParamvalue->parameter(); - if (areEquivalentParameters(paramName, parameter->nameStr())) { - return opParamvalue->parameterValue(); - } - } - } - return nullParameterValue; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the parameter value corresponding to a EPSG code - * - * @param epsg_code the parameter EPSG code - * @return the value, or nullptr if not found. - */ -const ParameterValuePtr &SingleOperation::parameterValue(int epsg_code) const - noexcept { - for (const auto &genOpParamvalue : parameterValues()) { - auto opParamvalue = dynamic_cast<const OperationParameterValue *>( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶meter = opParamvalue->parameter(); - if (parameter->getEPSGCode() == epsg_code) { - return opParamvalue->parameterValue(); - } - } - } - return nullParameterValue; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static const common::Measure nullMeasure; -//! @endcond - -/** \brief Return the parameter value, as a measure, corresponding to a - * parameter name or EPSG code - * - * @param paramName the parameter name (or empty, in which case epsg_code - * should be non zero) - * @param epsg_code the parameter EPSG code (possibly zero) - * @return the measure, or the empty Measure() object if not found. - */ -const common::Measure & -SingleOperation::parameterValueMeasure(const std::string ¶mName, - int epsg_code) const noexcept { - const auto &val = parameterValue(paramName, epsg_code); - if (val && val->type() == ParameterValue::Type::MEASURE) { - return val->value(); - } - return nullMeasure; -} - -/** \brief Return the parameter value, as a measure, corresponding to a - * EPSG code - * - * @param epsg_code the parameter EPSG code - * @return the measure, or the empty Measure() object if not found. - */ -const common::Measure & -SingleOperation::parameterValueMeasure(int epsg_code) const noexcept { - const auto &val = parameterValue(epsg_code); - if (val && val->type() == ParameterValue::Type::MEASURE) { - return val->value(); - } - return nullMeasure; -} - -//! @cond Doxygen_Suppress - -double SingleOperation::parameterValueNumericAsSI(int epsg_code) const - noexcept { - const auto &val = parameterValue(epsg_code); - if (val && val->type() == ParameterValue::Type::MEASURE) { - return val->value().getSIValue(); - } - return 0.0; -} - -double SingleOperation::parameterValueNumeric( - int epsg_code, const common::UnitOfMeasure &targetUnit) const noexcept { - const auto &val = parameterValue(epsg_code); - if (val && val->type() == ParameterValue::Type::MEASURE) { - return val->value().convertToUnit(targetUnit); - } - return 0.0; -} - -double SingleOperation::parameterValueNumeric( - const char *param_name, const common::UnitOfMeasure &targetUnit) const - noexcept { - const auto &val = parameterValue(param_name, 0); - if (val && val->type() == ParameterValue::Type::MEASURE) { - return val->value().convertToUnit(targetUnit); - } - return 0.0; -} - -//! @endcond -// --------------------------------------------------------------------------- - -/** \brief Instantiate a PROJ-based single operation. - * - * \note The operation might internally be a pipeline chaining several - * operations. - * The use of the SingleOperation modeling here is mostly to be able to get - * the PROJ string as a parameter. - * - * @param properties Properties - * @param PROJString the PROJ string. - * @param sourceCRS source CRS (might be null). - * @param targetCRS target CRS (might be null). - * @param accuracies Vector of positional accuracy (might be empty). - * @return the new instance - */ -SingleOperationNNPtr SingleOperation::createPROJBased( - const util::PropertyMap &properties, const std::string &PROJString, - const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - return util::nn_static_pointer_cast<SingleOperation>( - PROJBasedOperation::create(properties, PROJString, sourceCRS, targetCRS, - accuracies)); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static SingleOperationNNPtr createPROJBased( - const util::PropertyMap &properties, - const io::IPROJStringExportableNNPtr &projExportable, - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - const crs::CRSPtr &interpolationCRS, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies, - bool hasBallparkTransformation) { - return util::nn_static_pointer_cast<SingleOperation>( - PROJBasedOperation::create(properties, projExportable, false, sourceCRS, - targetCRS, interpolationCRS, accuracies, - hasBallparkTransformation)); -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -bool SingleOperation::_isEquivalentTo( - const util::IComparable *other, util::IComparable::Criterion criterion, - const io::DatabaseContextPtr &dbContext) const { - return _isEquivalentTo(other, criterion, dbContext, false); -} - -bool SingleOperation::_isEquivalentTo(const util::IComparable *other, - util::IComparable::Criterion criterion, - const io::DatabaseContextPtr &dbContext, - bool inOtherDirection) const { - - auto otherSO = dynamic_cast<const SingleOperation *>(other); - if (otherSO == nullptr || - (criterion == util::IComparable::Criterion::STRICT && - !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { - return false; - } - - const int methodEPSGCode = d->method_->getEPSGCode(); - const int otherMethodEPSGCode = otherSO->d->method_->getEPSGCode(); - - bool equivalentMethods = - (criterion == util::IComparable::Criterion::EQUIVALENT && - methodEPSGCode != 0 && methodEPSGCode == otherMethodEPSGCode) || - d->method_->_isEquivalentTo(otherSO->d->method_.get(), criterion, - dbContext); - if (!equivalentMethods && - criterion == util::IComparable::Criterion::EQUIVALENT) { - if ((methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA && - otherMethodEPSGCode == - EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) || - (otherMethodEPSGCode == - EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA && - methodEPSGCode == - EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) || - (methodEPSGCode == - EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA && - otherMethodEPSGCode == - EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) || - (otherMethodEPSGCode == - EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA && - methodEPSGCode == - EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) || - (methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL && - otherMethodEPSGCode == - EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) || - (otherMethodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL && - methodEPSGCode == - EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) { - auto geodCRS = - dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get()); - auto otherGeodCRS = dynamic_cast<const crs::GeodeticCRS *>( - otherSO->sourceCRS().get()); - if (geodCRS && otherGeodCRS && geodCRS->ellipsoid()->isSphere() && - otherGeodCRS->ellipsoid()->isSphere()) { - equivalentMethods = true; - } - } - } - - if (!equivalentMethods) { - if (criterion == util::IComparable::Criterion::EQUIVALENT) { - - const auto isTOWGS84Transf = [](int code) { - return code == - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || - code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || - code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || - code == - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || - code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || - code == - EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || - code == - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D || - code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D || - code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D; - }; - - // Translation vs (PV or CF) - // or different PV vs CF convention - if (isTOWGS84Transf(methodEPSGCode) && - isTOWGS84Transf(otherMethodEPSGCode)) { - auto transf = static_cast<const Transformation *>(this); - auto otherTransf = static_cast<const Transformation *>(otherSO); - auto params = transf->getTOWGS84Parameters(); - auto otherParams = otherTransf->getTOWGS84Parameters(); - assert(params.size() == 7); - assert(otherParams.size() == 7); - for (size_t i = 0; i < 7; i++) { - if (std::fabs(params[i] - otherParams[i]) > - 1e-10 * std::fabs(params[i])) { - return false; - } - } - return true; - } - - // _1SP methods can sometimes be equivalent to _2SP ones - // Check it by using convertToOtherMethod() - if (methodEPSGCode == - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP && - otherMethodEPSGCode == - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { - // Convert from 2SP to 1SP as the other direction has more - // degree of liberties. - return otherSO->_isEquivalentTo(this, criterion, dbContext); - } else if ((methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A && - otherMethodEPSGCode == - EPSG_CODE_METHOD_MERCATOR_VARIANT_B) || - (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && - otherMethodEPSGCode == - EPSG_CODE_METHOD_MERCATOR_VARIANT_A) || - (methodEPSGCode == - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP && - otherMethodEPSGCode == - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)) { - auto conv = dynamic_cast<const Conversion *>(this); - if (conv) { - auto eqConv = - conv->convertToOtherMethod(otherMethodEPSGCode); - if (eqConv) { - return eqConv->_isEquivalentTo(other, criterion, - dbContext); - } - } - } - } - - return false; - } - - const auto &values = d->parameterValues_; - const auto &otherValues = otherSO->d->parameterValues_; - const auto valuesSize = values.size(); - const auto otherValuesSize = otherValues.size(); - if (criterion == util::IComparable::Criterion::STRICT) { - if (valuesSize != otherValuesSize) { - return false; - } - for (size_t i = 0; i < valuesSize; i++) { - if (!values[i]->_isEquivalentTo(otherValues[i].get(), criterion, - dbContext)) { - return false; - } - } - return true; - } - - std::vector<bool> candidateIndices(otherValuesSize, true); - bool equivalent = true; - bool foundMissingArgs = valuesSize != otherValuesSize; - - for (size_t i = 0; equivalent && i < valuesSize; i++) { - auto opParamvalue = - dynamic_cast<const OperationParameterValue *>(values[i].get()); - if (!opParamvalue) - return false; - - equivalent = false; - bool sameNameDifferentValue = false; - for (size_t j = 0; j < otherValuesSize; j++) { - if (candidateIndices[j] && - values[i]->_isEquivalentTo(otherValues[j].get(), criterion, - dbContext)) { - candidateIndices[j] = false; - equivalent = true; - break; - } else if (candidateIndices[j]) { - auto otherOpParamvalue = - dynamic_cast<const OperationParameterValue *>( - otherValues[j].get()); - if (!otherOpParamvalue) - return false; - sameNameDifferentValue = - opParamvalue->parameter()->_isEquivalentTo( - otherOpParamvalue->parameter().get(), criterion, - dbContext); - if (sameNameDifferentValue) { - candidateIndices[j] = false; - break; - } - } - } - - if (!equivalent && - methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { - // For LCC_2SP, the standard parallels can be switched and - // this will result in the same result. - const int paramEPSGCode = opParamvalue->parameter()->getEPSGCode(); - if (paramEPSGCode == - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL || - paramEPSGCode == - EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) { - auto value_1st = parameterValue( - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL); - auto value_2nd = parameterValue( - EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL); - if (value_1st && value_2nd) { - equivalent = - value_1st->_isEquivalentTo( - otherSO - ->parameterValue( - EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) - .get(), - criterion, dbContext) && - value_2nd->_isEquivalentTo( - otherSO - ->parameterValue( - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) - .get(), - criterion, dbContext); - } - } - } - - if (equivalent) { - continue; - } - - if (sameNameDifferentValue) { - break; - } - - // If there are parameters in this method not found in the other one, - // check that they are set to a default neutral value, that is 1 - // for scale, and 0 otherwise. - foundMissingArgs = true; - const auto &value = opParamvalue->parameterValue(); - if (value->type() != ParameterValue::Type::MEASURE) { - break; - } - if (value->value().unit().type() == - common::UnitOfMeasure::Type::SCALE) { - equivalent = value->value().getSIValue() == 1.0; - } else { - equivalent = value->value().getSIValue() == 0.0; - } - } - - // In the case the arguments don't perfectly match, try the reverse - // check. - if (equivalent && foundMissingArgs && !inOtherDirection) { - return otherSO->_isEquivalentTo(this, criterion, dbContext, true); - } - - // Equivalent formulations of 2SP can have different parameters - // Then convert to 1SP and compare. - if (!equivalent && - methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { - auto conv = dynamic_cast<const Conversion *>(this); - auto otherConv = dynamic_cast<const Conversion *>(other); - if (conv && otherConv) { - auto thisAs1SP = conv->convertToOtherMethod( - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); - auto otherAs1SP = otherConv->convertToOtherMethod( - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); - if (thisAs1SP && otherAs1SP) { - equivalent = thisAs1SP->_isEquivalentTo(otherAs1SP.get(), - criterion, dbContext); - } - } - } - return equivalent; -} -//! @endcond - -// --------------------------------------------------------------------------- - -std::set<GridDescription> -SingleOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext, - bool considerKnownGridsAsAvailable) const { - std::set<GridDescription> res; - for (const auto &genOpParamvalue : parameterValues()) { - auto opParamvalue = dynamic_cast<const OperationParameterValue *>( - genOpParamvalue.get()); - if (opParamvalue) { - const auto &value = opParamvalue->parameterValue(); - if (value->type() == ParameterValue::Type::FILENAME) { - const auto gridNames = split(value->valueFile(), ","); - for (const auto &gridName : gridNames) { - GridDescription desc; - desc.shortName = gridName; - if (databaseContext) { - databaseContext->lookForGridInfo( - desc.shortName, considerKnownGridsAsAvailable, - desc.fullName, desc.packageName, desc.url, - desc.directDownload, desc.openLicense, - desc.available); - } - res.insert(desc); - } - } - } - } - return res; -} - -// --------------------------------------------------------------------------- - -/** \brief Validate the parameters used by a coordinate operation. - * - * Return whether the method is known or not, or a list of missing or extra - * parameters for the operations recognized by this implementation. - */ -std::list<std::string> SingleOperation::validateParameters() const { - std::list<std::string> res; - - const auto &l_method = method(); - const auto &methodName = l_method->nameStr(); - const MethodMapping *methodMapping = nullptr; - const auto methodEPSGCode = l_method->getEPSGCode(); - for (const auto &mapping : projectionMethodMappings) { - if (metadata::Identifier::isEquivalentName(mapping.wkt2_name, - methodName.c_str()) || - (methodEPSGCode != 0 && methodEPSGCode == mapping.epsg_code)) { - methodMapping = &mapping; - } - } - if (methodMapping == nullptr) { - for (const auto &mapping : otherMethodMappings) { - if (metadata::Identifier::isEquivalentName(mapping.wkt2_name, - methodName.c_str()) || - (methodEPSGCode != 0 && methodEPSGCode == mapping.epsg_code)) { - methodMapping = &mapping; - } - } - } - if (!methodMapping) { - res.emplace_back("Unknown method " + methodName); - return res; - } - if (methodMapping->wkt2_name != methodName) { - if (metadata::Identifier::isEquivalentName(methodMapping->wkt2_name, - methodName.c_str())) { - std::string msg("Method name "); - msg += methodName; - msg += " is equivalent to official "; - msg += methodMapping->wkt2_name; - msg += " but not strictly equal"; - res.emplace_back(msg); - } else { - std::string msg("Method name "); - msg += methodName; - msg += ", matched to "; - msg += methodMapping->wkt2_name; - msg += ", through its EPSG code has not an equivalent name"; - res.emplace_back(msg); - } - } - if (methodEPSGCode != 0 && methodEPSGCode != methodMapping->epsg_code) { - std::string msg("Method of EPSG code "); - msg += toString(methodEPSGCode); - msg += " does not match official code ("; - msg += toString(methodMapping->epsg_code); - msg += ')'; - res.emplace_back(msg); - } - - // Check if expected parameters are found - for (int i = 0; - methodMapping->params && methodMapping->params[i] != nullptr; ++i) { - const auto *paramMapping = methodMapping->params[i]; - - const OperationParameterValue *opv = nullptr; - for (const auto &genOpParamvalue : parameterValues()) { - auto opParamvalue = dynamic_cast<const OperationParameterValue *>( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶meter = opParamvalue->parameter(); - if ((paramMapping->epsg_code != 0 && - parameter->getEPSGCode() == paramMapping->epsg_code) || - ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) { - opv = opParamvalue; - break; - } - } - } - - if (!opv) { - if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL || - methodEPSGCode == - EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) && - paramMapping == ¶mLatitudeNatOrigin) { - // extension of EPSG used by GDAL/PROJ, so we should not - // warn on its absence. - continue; - } - std::string msg("Cannot find expected parameter "); - msg += paramMapping->wkt2_name; - res.emplace_back(msg); - continue; - } - const auto ¶meter = opv->parameter(); - if (paramMapping->wkt2_name != parameter->nameStr()) { - if (ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) { - std::string msg("Parameter name "); - msg += parameter->nameStr(); - msg += " is equivalent to official "; - msg += paramMapping->wkt2_name; - msg += " but not strictly equal"; - res.emplace_back(msg); - } else { - std::string msg("Parameter name "); - msg += parameter->nameStr(); - msg += ", matched to "; - msg += paramMapping->wkt2_name; - msg += ", through its EPSG code has not an equivalent name"; - res.emplace_back(msg); - } - } - const auto paramEPSGCode = parameter->getEPSGCode(); - if (paramEPSGCode != 0 && paramEPSGCode != paramMapping->epsg_code) { - std::string msg("Parameter of EPSG code "); - msg += toString(paramEPSGCode); - msg += " does not match official code ("; - msg += toString(paramMapping->epsg_code); - msg += ')'; - res.emplace_back(msg); - } - } - - // Check if there are extra parameters - for (const auto &genOpParamvalue : parameterValues()) { - auto opParamvalue = dynamic_cast<const OperationParameterValue *>( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶meter = opParamvalue->parameter(); - if (!getMapping(methodMapping, parameter)) { - std::string msg("Parameter "); - msg += parameter->nameStr(); - msg += " found but not expected for this method"; - res.emplace_back(msg); - } - } - } - - return res; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct ParameterValue::Private { - ParameterValue::Type type_{ParameterValue::Type::STRING}; - std::unique_ptr<common::Measure> measure_{}; - std::unique_ptr<std::string> stringValue_{}; - int integerValue_{}; - bool booleanValue_{}; - - explicit Private(const common::Measure &valueIn) - : type_(ParameterValue::Type::MEASURE), - measure_(internal::make_unique<common::Measure>(valueIn)) {} - - Private(const std::string &stringValueIn, ParameterValue::Type typeIn) - : type_(typeIn), - stringValue_(internal::make_unique<std::string>(stringValueIn)) {} - - explicit Private(int integerValueIn) - : type_(ParameterValue::Type::INTEGER), integerValue_(integerValueIn) {} - - explicit Private(bool booleanValueIn) - : type_(ParameterValue::Type::BOOLEAN), booleanValue_(booleanValueIn) {} -}; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -ParameterValue::~ParameterValue() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -ParameterValue::ParameterValue(const common::Measure &measureIn) - : d(internal::make_unique<Private>(measureIn)) {} - -// --------------------------------------------------------------------------- - -ParameterValue::ParameterValue(const std::string &stringValueIn, - ParameterValue::Type typeIn) - : d(internal::make_unique<Private>(stringValueIn, typeIn)) {} - -// --------------------------------------------------------------------------- - -ParameterValue::ParameterValue(int integerValueIn) - : d(internal::make_unique<Private>(integerValueIn)) {} - -// --------------------------------------------------------------------------- - -ParameterValue::ParameterValue(bool booleanValueIn) - : d(internal::make_unique<Private>(booleanValueIn)) {} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a ParameterValue from a Measure (i.e. a value associated - * with a - * unit) - * - * @return a new ParameterValue. - */ -ParameterValueNNPtr ParameterValue::create(const common::Measure &measureIn) { - return ParameterValue::nn_make_shared<ParameterValue>(measureIn); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a ParameterValue from a string value. - * - * @return a new ParameterValue. - */ -ParameterValueNNPtr ParameterValue::create(const char *stringValueIn) { - return ParameterValue::nn_make_shared<ParameterValue>( - std::string(stringValueIn), ParameterValue::Type::STRING); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a ParameterValue from a string value. - * - * @return a new ParameterValue. - */ -ParameterValueNNPtr ParameterValue::create(const std::string &stringValueIn) { - return ParameterValue::nn_make_shared<ParameterValue>( - stringValueIn, ParameterValue::Type::STRING); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a ParameterValue from a filename. - * - * @return a new ParameterValue. - */ -ParameterValueNNPtr -ParameterValue::createFilename(const std::string &stringValueIn) { - return ParameterValue::nn_make_shared<ParameterValue>( - stringValueIn, ParameterValue::Type::FILENAME); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a ParameterValue from a integer value. - * - * @return a new ParameterValue. - */ -ParameterValueNNPtr ParameterValue::create(int integerValueIn) { - return ParameterValue::nn_make_shared<ParameterValue>(integerValueIn); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a ParameterValue from a boolean value. - * - * @return a new ParameterValue. - */ -ParameterValueNNPtr ParameterValue::create(bool booleanValueIn) { - return ParameterValue::nn_make_shared<ParameterValue>(booleanValueIn); -} - -// --------------------------------------------------------------------------- - -/** \brief Returns the type of a parameter value. - * - * @return the type. - */ -const ParameterValue::Type &ParameterValue::type() PROJ_PURE_DEFN { - return d->type_; -} - -// --------------------------------------------------------------------------- - -/** \brief Returns the value as a Measure (assumes type() == Type::MEASURE) - * @return the value as a Measure. - */ -const common::Measure &ParameterValue::value() PROJ_PURE_DEFN { - return *d->measure_; -} - -// --------------------------------------------------------------------------- - -/** \brief Returns the value as a string (assumes type() == Type::STRING) - * @return the value as a string. - */ -const std::string &ParameterValue::stringValue() PROJ_PURE_DEFN { - return *d->stringValue_; -} - -// --------------------------------------------------------------------------- - -/** \brief Returns the value as a filename (assumes type() == Type::FILENAME) - * @return the value as a filename. - */ -const std::string &ParameterValue::valueFile() PROJ_PURE_DEFN { - return *d->stringValue_; -} - -// --------------------------------------------------------------------------- - -/** \brief Returns the value as a integer (assumes type() == Type::INTEGER) - * @return the value as a integer. - */ -int ParameterValue::integerValue() PROJ_PURE_DEFN { return d->integerValue_; } - -// --------------------------------------------------------------------------- - -/** \brief Returns the value as a boolean (assumes type() == Type::BOOLEAN) - * @return the value as a boolean. - */ -bool ParameterValue::booleanValue() PROJ_PURE_DEFN { return d->booleanValue_; } - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -void ParameterValue::_exportToWKT(io::WKTFormatter *formatter) const { - const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; - - const auto &l_type = type(); - if (l_type == Type::MEASURE) { - const auto &l_value = value(); - if (formatter->abridgedTransformation()) { - const auto &unit = l_value.unit(); - const auto &unitType = unit.type(); - if (unitType == common::UnitOfMeasure::Type::LINEAR) { - formatter->add(l_value.getSIValue()); - } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) { - formatter->add( - l_value.convertToUnit(common::UnitOfMeasure::ARC_SECOND)); - } else if (unit == common::UnitOfMeasure::PARTS_PER_MILLION) { - formatter->add(1.0 + l_value.value() * 1e-6); - } else { - formatter->add(l_value.value()); - } - } else { - const auto &unit = l_value.unit(); - if (isWKT2) { - formatter->add(l_value.value()); - } else { - // In WKT1, as we don't output the natural unit, output to the - // registered linear / angular unit. - const auto &unitType = unit.type(); - if (unitType == common::UnitOfMeasure::Type::LINEAR) { - const auto &targetUnit = *(formatter->axisLinearUnit()); - if (targetUnit.conversionToSI() == 0.0) { - throw io::FormattingException( - "cannot convert value to target linear unit"); - } - formatter->add(l_value.convertToUnit(targetUnit)); - } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) { - const auto &targetUnit = *(formatter->axisAngularUnit()); - if (targetUnit.conversionToSI() == 0.0) { - throw io::FormattingException( - "cannot convert value to target angular unit"); - } - formatter->add(l_value.convertToUnit(targetUnit)); - } else { - formatter->add(l_value.getSIValue()); - } - } - if (isWKT2 && unit != common::UnitOfMeasure::NONE) { - if (!formatter - ->primeMeridianOrParameterUnitOmittedIfSameAsAxis() || - (unit != common::UnitOfMeasure::SCALE_UNITY && - unit != *(formatter->axisLinearUnit()) && - unit != *(formatter->axisAngularUnit()))) { - unit._exportToWKT(formatter); - } - } - } - } else if (l_type == Type::STRING || l_type == Type::FILENAME) { - formatter->addQuotedString(stringValue()); - } else if (l_type == Type::INTEGER) { - formatter->add(integerValue()); - } else { - throw io::FormattingException("boolean parameter value not handled"); - } -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -bool ParameterValue::_isEquivalentTo(const util::IComparable *other, - util::IComparable::Criterion criterion, - const io::DatabaseContextPtr &) const { - auto otherPV = dynamic_cast<const ParameterValue *>(other); - if (otherPV == nullptr) { - return false; - } - if (type() != otherPV->type()) { - return false; - } - switch (type()) { - case Type::MEASURE: { - return value()._isEquivalentTo(otherPV->value(), criterion, 2e-10); - } - - case Type::STRING: - case Type::FILENAME: { - return stringValue() == otherPV->stringValue(); - } - - case Type::INTEGER: { - return integerValue() == otherPV->integerValue(); - } - - case Type::BOOLEAN: { - return booleanValue() == otherPV->booleanValue(); - } - - default: { - assert(false); - break; - } - } - return true; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct Conversion::Private {}; -//! @endcond - -// --------------------------------------------------------------------------- - -Conversion::Conversion(const OperationMethodNNPtr &methodIn, - const std::vector<GeneralParameterValueNNPtr> &values) - : SingleOperation(methodIn), d(nullptr) { - setParameterValues(values); -} - -// --------------------------------------------------------------------------- - -Conversion::Conversion(const Conversion &other) - : CoordinateOperation(other), SingleOperation(other), d(nullptr) {} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -Conversion::~Conversion() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -ConversionNNPtr Conversion::shallowClone() const { - auto conv = Conversion::nn_make_shared<Conversion>(*this); - conv->assignSelf(conv); - conv->setCRSs(this, false); - return conv; -} - -CoordinateOperationNNPtr Conversion::_shallowClone() const { - return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone()); -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -ConversionNNPtr -Conversion::alterParametersLinearUnit(const common::UnitOfMeasure &unit, - bool convertToNewUnit) const { - - std::vector<GeneralParameterValueNNPtr> newValues; - bool changesDone = false; - for (const auto &genOpParamvalue : parameterValues()) { - bool updated = false; - auto opParamvalue = dynamic_cast<const OperationParameterValue *>( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶mValue = opParamvalue->parameterValue(); - if (paramValue->type() == ParameterValue::Type::MEASURE) { - const auto &measure = paramValue->value(); - if (measure.unit().type() == - common::UnitOfMeasure::Type::LINEAR) { - if (!measure.unit()._isEquivalentTo( - unit, util::IComparable::Criterion::EQUIVALENT)) { - const double newValue = - convertToNewUnit ? measure.convertToUnit(unit) - : measure.value(); - newValues.emplace_back(OperationParameterValue::create( - opParamvalue->parameter(), - ParameterValue::create( - common::Measure(newValue, unit)))); - updated = true; - } - } - } - } - if (updated) { - changesDone = true; - } else { - newValues.emplace_back(genOpParamvalue); - } - } - if (changesDone) { - auto conv = create(util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, "unknown"), - method(), newValues); - conv->setCRSs(this, false); - return conv; - } else { - return NN_NO_CHECK( - util::nn_dynamic_pointer_cast<Conversion>(shared_from_this())); - } -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a Conversion from a vector of GeneralParameterValue. - * - * @param properties See \ref general_properties. At minimum the name should be - * defined. - * @param methodIn the operation method. - * @param values the values. - * @return a new Conversion. - * @throws InvalidOperation - */ -ConversionNNPtr Conversion::create(const util::PropertyMap &properties, - const OperationMethodNNPtr &methodIn, - const std::vector<GeneralParameterValueNNPtr> - &values) // throw InvalidOperation -{ - if (methodIn->parameters().size() != values.size()) { - throw InvalidOperation( - "Inconsistent number of parameters and parameter values"); - } - auto conv = Conversion::nn_make_shared<Conversion>(methodIn, values); - conv->assignSelf(conv); - conv->setProperties(properties); - return conv; -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a Conversion and its OperationMethod - * - * @param propertiesConversion See \ref general_properties of the conversion. - * At minimum the name should be defined. - * @param propertiesOperationMethod See \ref general_properties of the operation - * method. At minimum the name should be defined. - * @param parameters the operation parameters. - * @param values the operation values. Constraint: - * values.size() == parameters.size() - * @return a new Conversion. - * @throws InvalidOperation - */ -ConversionNNPtr Conversion::create( - const util::PropertyMap &propertiesConversion, - const util::PropertyMap &propertiesOperationMethod, - const std::vector<OperationParameterNNPtr> ¶meters, - const std::vector<ParameterValueNNPtr> &values) // throw InvalidOperation -{ - OperationMethodNNPtr op( - OperationMethod::create(propertiesOperationMethod, parameters)); - - if (parameters.size() != values.size()) { - throw InvalidOperation( - "Inconsistent number of parameters and parameter values"); - } - std::vector<GeneralParameterValueNNPtr> generalParameterValues; - generalParameterValues.reserve(values.size()); - for (size_t i = 0; i < values.size(); i++) { - generalParameterValues.push_back( - OperationParameterValue::create(parameters[i], values[i])); - } - return create(propertiesConversion, op, generalParameterValues); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -// --------------------------------------------------------------------------- - -static util::PropertyMap createMapNameEPSGCode(const std::string &name, - int code) { - return util::PropertyMap() - .set(common::IdentifiedObject::NAME_KEY, name) - .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) - .set(metadata::Identifier::CODE_KEY, code); -} - -// --------------------------------------------------------------------------- - -static util::PropertyMap createMapNameEPSGCode(const char *name, int code) { - return util::PropertyMap() - .set(common::IdentifiedObject::NAME_KEY, name) - .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) - .set(metadata::Identifier::CODE_KEY, code); -} - -// --------------------------------------------------------------------------- - -static util::PropertyMap createMethodMapNameEPSGCode(int code) { - const char *name = nullptr; - for (const auto &tuple : methodNameCodes) { - if (tuple.epsg_code == code) { - name = tuple.name; - break; - } - } - assert(name); - return createMapNameEPSGCode(name, code); -} - -// --------------------------------------------------------------------------- - -static util::PropertyMap -getUTMConversionProperty(const util::PropertyMap &properties, int zone, - bool north) { - if (!properties.get(common::IdentifiedObject::NAME_KEY)) { - std::string conversionName("UTM zone "); - conversionName += toString(zone); - conversionName += (north ? 'N' : 'S'); - - return createMapNameEPSGCode(conversionName, - (north ? 16000 : 17000) + zone); - } else { - return properties; - } -} - -// --------------------------------------------------------------------------- - -static util::PropertyMap -addDefaultNameIfNeeded(const util::PropertyMap &properties, - const std::string &defaultName) { - if (!properties.get(common::IdentifiedObject::NAME_KEY)) { - return util::PropertyMap(properties) - .set(common::IdentifiedObject::NAME_KEY, defaultName); - } else { - return properties; - } -} - -// --------------------------------------------------------------------------- - -static ConversionNNPtr -createConversion(const util::PropertyMap &properties, - const MethodMapping *mapping, - const std::vector<ParameterValueNNPtr> &values) { - - std::vector<OperationParameterNNPtr> parameters; - for (int i = 0; mapping->params[i] != nullptr; i++) { - const auto *param = mapping->params[i]; - auto paramProperties = util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, param->wkt2_name); - if (param->epsg_code != 0) { - paramProperties - .set(metadata::Identifier::CODESPACE_KEY, - metadata::Identifier::EPSG) - .set(metadata::Identifier::CODE_KEY, param->epsg_code); - } - auto parameter = OperationParameter::create(paramProperties); - parameters.push_back(parameter); - } - - auto methodProperties = util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, mapping->wkt2_name); - if (mapping->epsg_code != 0) { - methodProperties - .set(metadata::Identifier::CODESPACE_KEY, - metadata::Identifier::EPSG) - .set(metadata::Identifier::CODE_KEY, mapping->epsg_code); - } - return Conversion::create( - addDefaultNameIfNeeded(properties, mapping->wkt2_name), - methodProperties, parameters, values); -} -//! @endcond - -// --------------------------------------------------------------------------- - -ConversionNNPtr -Conversion::create(const util::PropertyMap &properties, int method_epsg_code, - const std::vector<ParameterValueNNPtr> &values) { - const MethodMapping *mapping = getMapping(method_epsg_code); - assert(mapping); - return createConversion(properties, mapping, values); -} - -// --------------------------------------------------------------------------- - -ConversionNNPtr -Conversion::create(const util::PropertyMap &properties, - const char *method_wkt2_name, - const std::vector<ParameterValueNNPtr> &values) { - const MethodMapping *mapping = getMapping(method_wkt2_name); - assert(mapping); - return createConversion(properties, mapping, values); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -struct VectorOfParameters : public std::vector<OperationParameterNNPtr> { - VectorOfParameters() : std::vector<OperationParameterNNPtr>() {} - explicit VectorOfParameters( - std::initializer_list<OperationParameterNNPtr> list) - : std::vector<OperationParameterNNPtr>(list) {} - VectorOfParameters(const VectorOfParameters &) = delete; - - ~VectorOfParameters(); -}; - -// This way, we disable inlining of destruction, and save a lot of space -VectorOfParameters::~VectorOfParameters() = default; - -struct VectorOfValues : public std::vector<ParameterValueNNPtr> { - VectorOfValues() : std::vector<ParameterValueNNPtr>() {} - explicit VectorOfValues(std::initializer_list<ParameterValueNNPtr> list) - : std::vector<ParameterValueNNPtr>(list) {} - - explicit VectorOfValues(std::initializer_list<common::Measure> list); - VectorOfValues(const VectorOfValues &) = delete; - VectorOfValues(VectorOfValues &&) = default; - - ~VectorOfValues(); -}; - -static std::vector<ParameterValueNNPtr> buildParameterValueFromMeasure( - const std::initializer_list<common::Measure> &list) { - std::vector<ParameterValueNNPtr> res; - for (const auto &v : list) { - res.emplace_back(ParameterValue::create(v)); - } - return res; -} - -VectorOfValues::VectorOfValues(std::initializer_list<common::Measure> list) - : std::vector<ParameterValueNNPtr>(buildParameterValueFromMeasure(list)) {} - -// This way, we disable inlining of destruction, and save a lot of space -VectorOfValues::~VectorOfValues() = default; - -PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1, - const common::Measure &m2, - const common::Measure &m3) { - return VectorOfValues{ParameterValue::create(m1), - ParameterValue::create(m2), - ParameterValue::create(m3)}; -} - -PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1, - const common::Measure &m2, - const common::Measure &m3, - const common::Measure &m4) { - return VectorOfValues{ - ParameterValue::create(m1), ParameterValue::create(m2), - ParameterValue::create(m3), ParameterValue::create(m4)}; -} - -PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1, - const common::Measure &m2, - const common::Measure &m3, - const common::Measure &m4, - const common::Measure &m5) { - return VectorOfValues{ - ParameterValue::create(m1), ParameterValue::create(m2), - ParameterValue::create(m3), ParameterValue::create(m4), - ParameterValue::create(m5), - }; -} - -PROJ_NO_INLINE static VectorOfValues -createParams(const common::Measure &m1, const common::Measure &m2, - const common::Measure &m3, const common::Measure &m4, - const common::Measure &m5, const common::Measure &m6) { - return VectorOfValues{ - ParameterValue::create(m1), ParameterValue::create(m2), - ParameterValue::create(m3), ParameterValue::create(m4), - ParameterValue::create(m5), ParameterValue::create(m6), - }; -} - -PROJ_NO_INLINE static VectorOfValues -createParams(const common::Measure &m1, const common::Measure &m2, - const common::Measure &m3, const common::Measure &m4, - const common::Measure &m5, const common::Measure &m6, - const common::Measure &m7) { - return VectorOfValues{ - ParameterValue::create(m1), ParameterValue::create(m2), - ParameterValue::create(m3), ParameterValue::create(m4), - ParameterValue::create(m5), ParameterValue::create(m6), - ParameterValue::create(m7), - }; -} - -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a [Universal Transverse Mercator] - *(https://proj.org/operations/projections/utm.html) conversion. - * - * UTM is a family of conversions, of EPSG codes from 16001 to 16060 for the - * northern hemisphere, and 17001 to 17060 for the southern hemisphere, - * based on the Transverse Mercator projection method. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param zone UTM zone number between 1 and 60. - * @param north true for UTM northern hemisphere, false for UTM southern - * hemisphere. - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createUTM(const util::PropertyMap &properties, - int zone, bool north) { - return create( - getUTMConversionProperty(properties, zone, north), - EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, - createParams(common::Angle(UTM_LATITUDE_OF_NATURAL_ORIGIN), - common::Angle(zone * 6.0 - 183.0), - common::Scale(UTM_SCALE_FACTOR), - common::Length(UTM_FALSE_EASTING), - common::Length(north ? UTM_NORTH_FALSE_NORTHING - : UTM_SOUTH_FALSE_NORTHING))); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Transverse Mercator] - *(https://proj.org/operations/projections/tmerc.html) projection method. - * - * This method is defined as [EPSG:9807] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9807) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude - * @param centerLong See \ref center_longitude - * @param scale See \ref scale - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createTransverseMercator( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Scale &scale, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, - createParams(centerLat, centerLong, scale, falseEasting, - falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Gauss Schreiber Transverse - *Mercator] - *(https://proj.org/operations/projections/gstmerc.html) projection method. - * - * This method is also known as Gauss-Laborde Reunion. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude - * @param centerLong See \ref center_longitude - * @param scale See \ref scale - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createGaussSchreiberTransverseMercator( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Scale &scale, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, - PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR, - createParams(centerLat, centerLong, scale, falseEasting, - falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Transverse Mercator South - *Orientated] - *(https://proj.org/operations/projections/tmerc.html) projection method. - * - * This method is defined as [EPSG:9808] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9808) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude - * @param centerLong See \ref center_longitude - * @param scale See \ref scale - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createTransverseMercatorSouthOriented( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Scale &scale, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, - EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED, - createParams(centerLat, centerLong, scale, falseEasting, - falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Two Point Equidistant] - *(https://proj.org/operations/projections/tpeqd.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeFirstPoint Latitude of first point. - * @param longitudeFirstPoint Longitude of first point. - * @param latitudeSecondPoint Latitude of second point. - * @param longitudeSeconPoint Longitude of second point. - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr -Conversion::createTwoPointEquidistant(const util::PropertyMap &properties, - const common::Angle &latitudeFirstPoint, - const common::Angle &longitudeFirstPoint, - const common::Angle &latitudeSecondPoint, - const common::Angle &longitudeSeconPoint, - const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, - createParams(latitudeFirstPoint, longitudeFirstPoint, - latitudeSecondPoint, longitudeSeconPoint, - falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the Tunisia Mapping Grid projection - * method. - * - * This method is defined as [EPSG:9816] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9816) - * - * \note There is currently no implementation of the method formulas in PROJ. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createTunisiaMappingGrid( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create( - properties, EPSG_CODE_METHOD_TUNISIA_MAPPING_GRID, - createParams(centerLat, centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Albers Conic Equal Area] - *(https://proj.org/operations/projections/aea.html) projection method. - * - * This method is defined as [EPSG:9822] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9822) - * - * @note the order of arguments is conformant with the corresponding EPSG - * mode and different than OGRSpatialReference::setACEA() of GDAL <= 2.3 - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeFalseOrigin See \ref latitude_false_origin - * @param longitudeFalseOrigin See \ref longitude_false_origin - * @param latitudeFirstParallel See \ref latitude_first_std_parallel - * @param latitudeSecondParallel See \ref latitude_second_std_parallel - * @param eastingFalseOrigin See \ref easting_false_origin - * @param northingFalseOrigin See \ref northing_false_origin - * @return a new Conversion. - */ -ConversionNNPtr -Conversion::createAlbersEqualArea(const util::PropertyMap &properties, - const common::Angle &latitudeFalseOrigin, - const common::Angle &longitudeFalseOrigin, - const common::Angle &latitudeFirstParallel, - const common::Angle &latitudeSecondParallel, - const common::Length &eastingFalseOrigin, - const common::Length &northingFalseOrigin) { - return create(properties, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA, - createParams(latitudeFalseOrigin, longitudeFalseOrigin, - latitudeFirstParallel, latitudeSecondParallel, - eastingFalseOrigin, northingFalseOrigin)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Lambert Conic Conformal 1SP] - *(https://proj.org/operations/projections/lcc.html) projection method. - * - * This method is defined as [EPSG:9801] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9801) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude - * @param centerLong See \ref center_longitude - * @param scale See \ref scale - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createLambertConicConformal_1SP( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Scale &scale, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, - createParams(centerLat, centerLong, scale, falseEasting, - falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Lambert Conic Conformal (2SP)] - *(https://proj.org/operations/projections/lcc.html) projection method. - * - * This method is defined as [EPSG:9802] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9802) - * - * @note the order of arguments is conformant with the corresponding EPSG - * mode and different than OGRSpatialReference::setLCC() of GDAL <= 2.3 - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeFalseOrigin See \ref latitude_false_origin - * @param longitudeFalseOrigin See \ref longitude_false_origin - * @param latitudeFirstParallel See \ref latitude_first_std_parallel - * @param latitudeSecondParallel See \ref latitude_second_std_parallel - * @param eastingFalseOrigin See \ref easting_false_origin - * @param northingFalseOrigin See \ref northing_false_origin - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createLambertConicConformal_2SP( - const util::PropertyMap &properties, - const common::Angle &latitudeFalseOrigin, - const common::Angle &longitudeFalseOrigin, - const common::Angle &latitudeFirstParallel, - const common::Angle &latitudeSecondParallel, - const common::Length &eastingFalseOrigin, - const common::Length &northingFalseOrigin) { - return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, - createParams(latitudeFalseOrigin, longitudeFalseOrigin, - latitudeFirstParallel, latitudeSecondParallel, - eastingFalseOrigin, northingFalseOrigin)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Lambert Conic Conformal (2SP - *Michigan)] - *(https://proj.org/operations/projections/lcc.html) projection method. - * - * This method is defined as [EPSG:1051] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1051) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeFalseOrigin See \ref latitude_false_origin - * @param longitudeFalseOrigin See \ref longitude_false_origin - * @param latitudeFirstParallel See \ref latitude_first_std_parallel - * @param latitudeSecondParallel See \ref latitude_second_std_parallel - * @param eastingFalseOrigin See \ref easting_false_origin - * @param northingFalseOrigin See \ref northing_false_origin - * @param ellipsoidScalingFactor Ellipsoid scaling factor. - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createLambertConicConformal_2SP_Michigan( - const util::PropertyMap &properties, - const common::Angle &latitudeFalseOrigin, - const common::Angle &longitudeFalseOrigin, - const common::Angle &latitudeFirstParallel, - const common::Angle &latitudeSecondParallel, - const common::Length &eastingFalseOrigin, - const common::Length &northingFalseOrigin, - const common::Scale &ellipsoidScalingFactor) { - return create(properties, - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, - createParams(latitudeFalseOrigin, longitudeFalseOrigin, - latitudeFirstParallel, latitudeSecondParallel, - eastingFalseOrigin, northingFalseOrigin, - ellipsoidScalingFactor)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Lambert Conic Conformal (2SP - *Belgium)] - *(https://proj.org/operations/projections/lcc.html) projection method. - * - * This method is defined as [EPSG:9803] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9803) - * - * \warning The formulas used currently in PROJ are, incorrectly, the ones of - * the regular LCC_2SP method. - * - * @note the order of arguments is conformant with the corresponding EPSG - * mode and different than OGRSpatialReference::setLCCB() of GDAL <= 2.3 - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeFalseOrigin See \ref latitude_false_origin - * @param longitudeFalseOrigin See \ref longitude_false_origin - * @param latitudeFirstParallel See \ref latitude_first_std_parallel - * @param latitudeSecondParallel See \ref latitude_second_std_parallel - * @param eastingFalseOrigin See \ref easting_false_origin - * @param northingFalseOrigin See \ref northing_false_origin - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createLambertConicConformal_2SP_Belgium( - const util::PropertyMap &properties, - const common::Angle &latitudeFalseOrigin, - const common::Angle &longitudeFalseOrigin, - const common::Angle &latitudeFirstParallel, - const common::Angle &latitudeSecondParallel, - const common::Length &eastingFalseOrigin, - const common::Length &northingFalseOrigin) { - - return create(properties, - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM, - createParams(latitudeFalseOrigin, longitudeFalseOrigin, - latitudeFirstParallel, latitudeSecondParallel, - eastingFalseOrigin, northingFalseOrigin)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Modified Azimuthal - *Equidistant] - *(https://proj.org/operations/projections/aeqd.html) projection method. - * - * This method is defined as [EPSG:9832] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9832) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeNatOrigin See \ref center_latitude - * @param longitudeNatOrigin See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createAzimuthalEquidistant( - const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, - const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, - createParams(latitudeNatOrigin, longitudeNatOrigin, - falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Guam Projection] - *(https://proj.org/operations/projections/aeqd.html) projection method. - * - * This method is defined as [EPSG:9831] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9831) - * - * @param properties See \ref general_properties of the conversion. If the name - *is - * not provided, it is automatically set. - * @param latitudeNatOrigin See \ref center_latitude - * @param longitudeNatOrigin See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createGuamProjection( - const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, - const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_GUAM_PROJECTION, - createParams(latitudeNatOrigin, longitudeNatOrigin, - falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Bonne] - *(https://proj.org/operations/projections/bonne.html) projection method. - * - * This method is defined as [EPSG:9827] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9827) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeNatOrigin See \ref center_latitude . PROJ calls its the - * standard parallel 1. - * @param longitudeNatOrigin See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createBonne(const util::PropertyMap &properties, - const common::Angle &latitudeNatOrigin, - const common::Angle &longitudeNatOrigin, - const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_BONNE, - createParams(latitudeNatOrigin, longitudeNatOrigin, - falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Lambert Cylindrical Equal Area - *(Spherical)] - *(https://proj.org/operations/projections/cea.html) projection method. - * - * This method is defined as [EPSG:9834] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9834) - * - * \warning The PROJ cea computation code would select the ellipsoidal form if - * a non-spherical ellipsoid is used for the base GeographicCRS. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeFirstParallel See \ref latitude_first_std_parallel. - * @param longitudeNatOrigin See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createLambertCylindricalEqualAreaSpherical( - const util::PropertyMap &properties, - const common::Angle &latitudeFirstParallel, - const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, - EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL, - createParams(latitudeFirstParallel, longitudeNatOrigin, - falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Lambert Cylindrical Equal Area - *(ellipsoidal form)] - *(https://proj.org/operations/projections/cea.html) projection method. - * - * This method is defined as [EPSG:9835] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9835) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeFirstParallel See \ref latitude_first_std_parallel. - * @param longitudeNatOrigin See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createLambertCylindricalEqualArea( - const util::PropertyMap &properties, - const common::Angle &latitudeFirstParallel, - const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, - createParams(latitudeFirstParallel, longitudeNatOrigin, - falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Cassini-Soldner] - * (https://proj.org/operations/projections/cass.html) projection method. - * - * This method is defined as [EPSG:9806] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9806) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createCassiniSoldner( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create( - properties, EPSG_CODE_METHOD_CASSINI_SOLDNER, - createParams(centerLat, centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Equidistant Conic] - *(https://proj.org/operations/projections/eqdc.html) projection method. - * - * There is no equivalent in EPSG. - * - * @note Although not found in EPSG, the order of arguments is conformant with - * the "spirit" of EPSG and different than OGRSpatialReference::setEC() of GDAL - *<= 2.3 * @param properties See \ref general_properties of the conversion. - *If the name - * is not provided, it is automatically set. - * - * @param centerLat See \ref center_latitude - * @param centerLong See \ref center_longitude - * @param latitudeFirstParallel See \ref latitude_first_std_parallel - * @param latitudeSecondParallel See \ref latitude_second_std_parallel - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createEquidistantConic( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Angle &latitudeFirstParallel, - const common::Angle &latitudeSecondParallel, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC, - createParams(centerLat, centerLong, latitudeFirstParallel, - latitudeSecondParallel, falseEasting, - falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Eckert I] - * (https://proj.org/operations/projections/eck1.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createEckertI(const util::PropertyMap &properties, - const common::Angle ¢erLong, - const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_I, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Eckert II] - * (https://proj.org/operations/projections/eck2.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createEckertII( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_II, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Eckert III] - * (https://proj.org/operations/projections/eck3.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createEckertIII( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_III, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Eckert IV] - * (https://proj.org/operations/projections/eck4.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createEckertIV( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_IV, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Eckert V] - * (https://proj.org/operations/projections/eck5.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createEckertV(const util::PropertyMap &properties, - const common::Angle ¢erLong, - const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_V, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Eckert VI] - * (https://proj.org/operations/projections/eck6.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createEckertVI( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_VI, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Equidistant Cylindrical] - *(https://proj.org/operations/projections/eqc.html) projection method. - * - * This is also known as the Equirectangular method, and in the particular case - * where the latitude of first parallel is 0. - * - * This method is defined as [EPSG:1028] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1028) - * - * @note This is the equivalent OGRSpatialReference::SetEquirectangular2( - * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL <= 2.3, - * where the lat_0 / center_latitude parameter is forced to 0. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeFirstParallel See \ref latitude_first_std_parallel. - * @param longitudeNatOrigin See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createEquidistantCylindrical( - const util::PropertyMap &properties, - const common::Angle &latitudeFirstParallel, - const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, - createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin, - falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Equidistant Cylindrical - *(Spherical)] - *(https://proj.org/operations/projections/eqc.html) projection method. - * - * This is also known as the Equirectangular method, and in the particular case - * where the latitude of first parallel is 0. - * - * This method is defined as [EPSG:1029] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1029) - * - * @note This is the equivalent OGRSpatialReference::SetEquirectangular2( - * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL <= 2.3, - * where the lat_0 / center_latitude parameter is forced to 0. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeFirstParallel See \ref latitude_first_std_parallel. - * @param longitudeNatOrigin See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createEquidistantCylindricalSpherical( - const util::PropertyMap &properties, - const common::Angle &latitudeFirstParallel, - const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, - EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, - createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin, - falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Gall (Stereographic)] - * (https://proj.org/operations/projections/gall.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createGall(const util::PropertyMap &properties, - const common::Angle ¢erLong, - const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Goode Homolosine] - * (https://proj.org/operations/projections/goode.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createGoodeHomolosine( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Interrupted Goode Homolosine] - * (https://proj.org/operations/projections/igh.html) projection method. - * - * There is no equivalent in EPSG. - * - * @note OGRSpatialReference::SetIGH() of GDAL <= 2.3 assumes the 3 - * projection - * parameters to be zero and this is the nominal case. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createInterruptedGoodeHomolosine( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, - PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Geostationary Satellite View] - * (https://proj.org/operations/projections/geos.html) projection method, - * with the sweep angle axis of the viewing instrument being x - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param height Height of the view point above the Earth. - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createGeostationarySatelliteSweepX( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &height, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create( - properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X, - createParams(centerLong, height, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Geostationary Satellite View] - * (https://proj.org/operations/projections/geos.html) projection method, - * with the sweep angle axis of the viewing instrument being y. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param height Height of the view point above the Earth. - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createGeostationarySatelliteSweepY( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &height, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create( - properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y, - createParams(centerLong, height, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Gnomonic] - *(https://proj.org/operations/projections/gnom.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createGnomonic( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create( - properties, PROJ_WKT2_NAME_METHOD_GNOMONIC, - createParams(centerLat, centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Hotine Oblique Mercator - *(Variant A)] - *(https://proj.org/operations/projections/omerc.html) projection method - * - * This is the variant with the no_uoff parameter, which corresponds to - * GDAL >=2.3 Hotine_Oblique_Mercator projection. - * In this variant, the false grid coordinates are - * defined at the intersection of the initial line and the aposphere (the - * equator on one of the intermediate surfaces inherent in the method), that is - * at the natural origin of the coordinate system). - * - * This method is defined as [EPSG:9812] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9812) - * - * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid = - *90deg, - * this maps to the [Swiss Oblique Mercator] - *(https://proj.org/operations/projections/somerc.html) formulas. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeProjectionCentre See \ref latitude_projection_centre - * @param longitudeProjectionCentre See \ref longitude_projection_centre - * @param azimuthInitialLine See \ref azimuth_initial_line - * @param angleFromRectifiedToSkrewGrid See - * \ref angle_from_recitfied_to_skrew_grid - * @param scale See \ref scale_factor_initial_line - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createHotineObliqueMercatorVariantA( - const util::PropertyMap &properties, - const common::Angle &latitudeProjectionCentre, - const common::Angle &longitudeProjectionCentre, - const common::Angle &azimuthInitialLine, - const common::Angle &angleFromRectifiedToSkrewGrid, - const common::Scale &scale, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create( - properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, - createParams(latitudeProjectionCentre, longitudeProjectionCentre, - azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale, - falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Hotine Oblique Mercator - *(Variant B)] - *(https://proj.org/operations/projections/omerc.html) projection method - * - * This is the variant without the no_uoff parameter, which corresponds to - * GDAL >=2.3 Hotine_Oblique_Mercator_Azimuth_Center projection. - * In this variant, the false grid coordinates are defined at the projection - *centre. - * - * This method is defined as [EPSG:9815] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9815) - * - * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid = - *90deg, - * this maps to the [Swiss Oblique Mercator] - *(https://proj.org/operations/projections/somerc.html) formulas. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeProjectionCentre See \ref latitude_projection_centre - * @param longitudeProjectionCentre See \ref longitude_projection_centre - * @param azimuthInitialLine See \ref azimuth_initial_line - * @param angleFromRectifiedToSkrewGrid See - * \ref angle_from_recitfied_to_skrew_grid - * @param scale See \ref scale_factor_initial_line - * @param eastingProjectionCentre See \ref easting_projection_centre - * @param northingProjectionCentre See \ref northing_projection_centre - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createHotineObliqueMercatorVariantB( - const util::PropertyMap &properties, - const common::Angle &latitudeProjectionCentre, - const common::Angle &longitudeProjectionCentre, - const common::Angle &azimuthInitialLine, - const common::Angle &angleFromRectifiedToSkrewGrid, - const common::Scale &scale, const common::Length &eastingProjectionCentre, - const common::Length &northingProjectionCentre) { - return create( - properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, - createParams(latitudeProjectionCentre, longitudeProjectionCentre, - azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale, - eastingProjectionCentre, northingProjectionCentre)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Hotine Oblique Mercator Two - *Point Natural Origin] - *(https://proj.org/operations/projections/omerc.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeProjectionCentre See \ref latitude_projection_centre - * @param latitudePoint1 Latitude of point 1. - * @param longitudePoint1 Latitude of point 1. - * @param latitudePoint2 Latitude of point 2. - * @param longitudePoint2 Longitude of point 2. - * @param scale See \ref scale_factor_initial_line - * @param eastingProjectionCentre See \ref easting_projection_centre - * @param northingProjectionCentre See \ref northing_projection_centre - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin( - const util::PropertyMap &properties, - const common::Angle &latitudeProjectionCentre, - const common::Angle &latitudePoint1, const common::Angle &longitudePoint1, - const common::Angle &latitudePoint2, const common::Angle &longitudePoint2, - const common::Scale &scale, const common::Length &eastingProjectionCentre, - const common::Length &northingProjectionCentre) { - return create( - properties, - PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, - { - ParameterValue::create(latitudeProjectionCentre), - ParameterValue::create(latitudePoint1), - ParameterValue::create(longitudePoint1), - ParameterValue::create(latitudePoint2), - ParameterValue::create(longitudePoint2), - ParameterValue::create(scale), - ParameterValue::create(eastingProjectionCentre), - ParameterValue::create(northingProjectionCentre), - }); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Laborde Oblique Mercator] - *(https://proj.org/operations/projections/labrd.html) projection method. - * - * This method is defined as [EPSG:9813] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9813) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeProjectionCentre See \ref latitude_projection_centre - * @param longitudeProjectionCentre See \ref longitude_projection_centre - * @param azimuthInitialLine See \ref azimuth_initial_line - * @param scale See \ref scale_factor_initial_line - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createLabordeObliqueMercator( - const util::PropertyMap &properties, - const common::Angle &latitudeProjectionCentre, - const common::Angle &longitudeProjectionCentre, - const common::Angle &azimuthInitialLine, const common::Scale &scale, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR, - createParams(latitudeProjectionCentre, - longitudeProjectionCentre, azimuthInitialLine, - scale, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [International Map of the World - *Polyconic] - *(https://proj.org/operations/projections/imw_p.html) projection method. - * - * There is no equivalent in EPSG. - * - * @note the order of arguments is conformant with the corresponding EPSG - * mode and different than OGRSpatialReference::SetIWMPolyconic() of GDAL <= - *2.3 - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param latitudeFirstParallel See \ref latitude_first_std_parallel - * @param latitudeSecondParallel See \ref latitude_second_std_parallel - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createInternationalMapWorldPolyconic( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Angle &latitudeFirstParallel, - const common::Angle &latitudeSecondParallel, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC, - createParams(centerLong, latitudeFirstParallel, - latitudeSecondParallel, falseEasting, - falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Krovak (north oriented)] - *(https://proj.org/operations/projections/krovak.html) projection method. - * - * This method is defined as [EPSG:1041] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1041) - * - * The coordinates are returned in the "GIS friendly" order: easting, northing. - * This method is similar to createKrovak(), except that the later returns - * projected values as southing, westing, where - * southing(Krovak) = -northing(Krovak_North) and - * westing(Krovak) = -easting(Krovak_North). - * - * @note The current PROJ implementation of Krovak hard-codes - * colatitudeConeAxis = 30deg17'17.30311" - * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for - * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221). - * It also hard-codes the parameters of the Bessel ellipsoid typically used for - * Krovak. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeProjectionCentre See \ref latitude_projection_centre - * @param longitudeOfOrigin See \ref longitude_of_origin - * @param colatitudeConeAxis See \ref colatitude_cone_axis - * @param latitudePseudoStandardParallel See \ref - *latitude_pseudo_standard_parallel - * @param scaleFactorPseudoStandardParallel See \ref - *scale_factor_pseudo_standard_parallel - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createKrovakNorthOriented( - const util::PropertyMap &properties, - const common::Angle &latitudeProjectionCentre, - const common::Angle &longitudeOfOrigin, - const common::Angle &colatitudeConeAxis, - const common::Angle &latitudePseudoStandardParallel, - const common::Scale &scaleFactorPseudoStandardParallel, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED, - createParams(latitudeProjectionCentre, longitudeOfOrigin, - colatitudeConeAxis, - latitudePseudoStandardParallel, - scaleFactorPseudoStandardParallel, falseEasting, - falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Krovak] - *(https://proj.org/operations/projections/krovak.html) projection method. - * - * This method is defined as [EPSG:9819] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9819) - * - * The coordinates are returned in the historical order: southing, westing - * This method is similar to createKrovakNorthOriented(), except that the later - *returns - * projected values as easting, northing, where - * easting(Krovak_North) = -westing(Krovak) and - * northing(Krovak_North) = -southing(Krovak). - * - * @note The current PROJ implementation of Krovak hard-codes - * colatitudeConeAxis = 30deg17'17.30311" - * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for - * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221). - * It also hard-codes the parameters of the Bessel ellipsoid typically used for - * Krovak. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeProjectionCentre See \ref latitude_projection_centre - * @param longitudeOfOrigin See \ref longitude_of_origin - * @param colatitudeConeAxis See \ref colatitude_cone_axis - * @param latitudePseudoStandardParallel See \ref - *latitude_pseudo_standard_parallel - * @param scaleFactorPseudoStandardParallel See \ref - *scale_factor_pseudo_standard_parallel - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr -Conversion::createKrovak(const util::PropertyMap &properties, - const common::Angle &latitudeProjectionCentre, - const common::Angle &longitudeOfOrigin, - const common::Angle &colatitudeConeAxis, - const common::Angle &latitudePseudoStandardParallel, - const common::Scale &scaleFactorPseudoStandardParallel, - const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_KROVAK, - createParams(latitudeProjectionCentre, longitudeOfOrigin, - colatitudeConeAxis, - latitudePseudoStandardParallel, - scaleFactorPseudoStandardParallel, falseEasting, - falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Lambert Azimuthal Equal Area] - *(https://proj.org/operations/projections/laea.html) projection method. - * - * This method is defined as [EPSG:9820] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9820) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeNatOrigin See \ref center_latitude - * @param longitudeNatOrigin See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createLambertAzimuthalEqualArea( - const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, - const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, - createParams(latitudeNatOrigin, longitudeNatOrigin, - falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Miller Cylindrical] - *(https://proj.org/operations/projections/mill.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createMillerCylindrical( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Mercator] - *(https://proj.org/operations/projections/merc.html) projection method. - * - * This is the variant, also known as Mercator (1SP), defined with the scale - * factor. Note that latitude of natural origin (centerLat) is a parameter, - * but unused in the transformation formulas. - * - * This method is defined as [EPSG:9804] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9804) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude . Should be 0. - * @param centerLong See \ref center_longitude - * @param scale See \ref scale - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createMercatorVariantA( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Scale &scale, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_A, - createParams(centerLat, centerLong, scale, falseEasting, - falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Mercator] - *(https://proj.org/operations/projections/merc.html) projection method. - * - * This is the variant, also known as Mercator (2SP), defined with the latitude - * of the first standard parallel (the second standard parallel is implicitly - * the opposite value). The latitude of natural origin is fixed to zero. - * - * This method is defined as [EPSG:9805] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9805) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeFirstParallel See \ref latitude_first_std_parallel - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createMercatorVariantB( - const util::PropertyMap &properties, - const common::Angle &latitudeFirstParallel, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_B, - createParams(latitudeFirstParallel, centerLong, falseEasting, - falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Popular Visualisation Pseudo - *Mercator] - *(https://proj.org/operations/projections/webmerc.html) projection method. - * - * Also known as WebMercator. Mostly/only used for Projected CRS EPSG:3857 - * (WGS 84 / Pseudo-Mercator) - * - * This method is defined as [EPSG:1024] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1024) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude . Usually 0 - * @param centerLong See \ref center_longitude . Usually 0 - * @param falseEasting See \ref false_easting . Usually 0 - * @param falseNorthing See \ref false_northing . Usually 0 - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createPopularVisualisationPseudoMercator( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create( - properties, EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, - createParams(centerLat, centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Mollweide] - * (https://proj.org/operations/projections/moll.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createMollweide( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_MOLLWEIDE, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [New Zealand Map Grid] - * (https://proj.org/operations/projections/nzmg.html) projection method. - * - * This method is defined as [EPSG:9811] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9811) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createNewZealandMappingGrid( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create( - properties, EPSG_CODE_METHOD_NZMG, - createParams(centerLat, centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Oblique Stereographic - *(Alternative)] - *(https://proj.org/operations/projections/sterea.html) projection method. - * - * This method is defined as [EPSG:9809] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9809) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude - * @param centerLong See \ref center_longitude - * @param scale See \ref scale - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createObliqueStereographic( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Scale &scale, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC, - createParams(centerLat, centerLong, scale, falseEasting, - falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Orthographic] - *(https://proj.org/operations/projections/ortho.html) projection method. - * - * This method is defined as [EPSG:9840] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9840) - * - * \note Before PROJ 7.2, only the spherical formulation was implemented. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createOrthographic( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create( - properties, EPSG_CODE_METHOD_ORTHOGRAPHIC, - createParams(centerLat, centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [American Polyconic] - *(https://proj.org/operations/projections/poly.html) projection method. - * - * This method is defined as [EPSG:9818] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9818) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createAmericanPolyconic( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create( - properties, EPSG_CODE_METHOD_AMERICAN_POLYCONIC, - createParams(centerLat, centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Polar Stereographic (Variant - *A)] - *(https://proj.org/operations/projections/stere.html) projection method. - * - * This method is defined as [EPSG:9810] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9810) - * - * This is the variant of polar stereographic defined with a scale factor. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude . Should be 90 deg ou -90 deg. - * @param centerLong See \ref center_longitude - * @param scale See \ref scale - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createPolarStereographicVariantA( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Scale &scale, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, - createParams(centerLat, centerLong, scale, falseEasting, - falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Polar Stereographic (Variant - *B)] - *(https://proj.org/operations/projections/stere.html) projection method. - * - * This method is defined as [EPSG:9829] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9829) - * - * This is the variant of polar stereographic defined with a latitude of - * standard parallel. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeStandardParallel See \ref latitude_std_parallel - * @param longitudeOfOrigin See \ref longitude_of_origin - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createPolarStereographicVariantB( - const util::PropertyMap &properties, - const common::Angle &latitudeStandardParallel, - const common::Angle &longitudeOfOrigin, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, - createParams(latitudeStandardParallel, longitudeOfOrigin, - falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Robinson] - * (https://proj.org/operations/projections/robin.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createRobinson( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_ROBINSON, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Sinusoidal] - * (https://proj.org/operations/projections/sinu.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createSinusoidal( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_SINUSOIDAL, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Stereographic] - *(https://proj.org/operations/projections/stere.html) projection method. - * - * There is no equivalent in EPSG. This method implements the original "Oblique - * Stereographic" method described in "Snyder's Map Projections - A Working - *manual", - * which is different from the "Oblique Stereographic (alternative") method - * implemented in createObliqueStereographic(). - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude - * @param centerLong See \ref center_longitude - * @param scale See \ref scale - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createStereographic( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Scale &scale, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC, - createParams(centerLat, centerLong, scale, falseEasting, - falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Van der Grinten] - * (https://proj.org/operations/projections/vandg.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createVanDerGrinten( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Wagner I] - * (https://proj.org/operations/projections/wag1.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createWagnerI(const util::PropertyMap &properties, - const common::Angle ¢erLong, - const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_I, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Wagner II] - * (https://proj.org/operations/projections/wag2.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createWagnerII( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_II, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Wagner III] - * (https://proj.org/operations/projections/wag3.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param latitudeTrueScale Latitude of true scale. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createWagnerIII( - const util::PropertyMap &properties, const common::Angle &latitudeTrueScale, - const common::Angle ¢erLong, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_III, - createParams(latitudeTrueScale, centerLong, falseEasting, - falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Wagner IV] - * (https://proj.org/operations/projections/wag4.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createWagnerIV( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_IV, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Wagner V] - * (https://proj.org/operations/projections/wag5.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createWagnerV(const util::PropertyMap &properties, - const common::Angle ¢erLong, - const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_V, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Wagner VI] - * (https://proj.org/operations/projections/wag6.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createWagnerVI( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VI, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Wagner VII] - * (https://proj.org/operations/projections/wag7.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createWagnerVII( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VII, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Quadrilateralized Spherical - *Cube] - *(https://proj.org/operations/projections/qsc.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLat See \ref center_latitude - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createQuadrilateralizedSphericalCube( - const util::PropertyMap &properties, const common::Angle ¢erLat, - const common::Angle ¢erLong, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create( - properties, PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE, - createParams(centerLat, centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Spherical Cross-Track Height] - *(https://proj.org/operations/projections/sch.html) projection method. - * - * There is no equivalent in EPSG. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param pegPointLat Peg point latitude. - * @param pegPointLong Peg point longitude. - * @param pegPointHeading Peg point heading. - * @param pegPointHeight Peg point height. - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createSphericalCrossTrackHeight( - const util::PropertyMap &properties, const common::Angle &pegPointLat, - const common::Angle &pegPointLong, const common::Angle &pegPointHeading, - const common::Length &pegPointHeight) { - return create(properties, - PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT, - createParams(pegPointLat, pegPointLong, pegPointHeading, - pegPointHeight)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Equal Earth] - * (https://proj.org/operations/projections/eqearth.html) projection method. - * - * This method is defined as [EPSG:1078] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1078) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param centerLong See \ref center_longitude - * @param falseEasting See \ref false_easting - * @param falseNorthing See \ref false_northing - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createEqualEarth( - const util::PropertyMap &properties, const common::Angle ¢erLong, - const common::Length &falseEasting, const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_EQUAL_EARTH, - createParams(centerLong, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the [Vertical Perspective] - * (https://proj.org/operations/projections/nsper.html) projection method. - * - * This method is defined as [EPSG:9838] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9838) - * - * The PROJ implementation of the EPSG Vertical Perspective has the current - * limitations with respect to the method described in EPSG: - * <ul> - * <li> it is a 2D-only method, ignoring the ellipsoidal height of the point to - * project.</li> - * <li> it has only a spherical development.</li> - * <li> the height of the topocentric origin is ignored, and thus assumed to be - * 0.</li> - * </ul> - * - * For completeness, PROJ adds the falseEasting and falseNorthing parameter, - * which are not described in EPSG. They should usually be set to 0. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param topoOriginLat Latitude of topocentric origin - * @param topoOriginLong Longitude of topocentric origin - * @param topoOriginHeight Ellipsoidal height of topocentric origin. Ignored by - * PROJ (that is assumed to be 0) - * @param viewPointHeight Viewpoint height with respect to the - * topocentric/mapping plane. In the case where topoOriginHeight = 0, this is - * the height above the ellipsoid surface at topoOriginLat, topoOriginLong. - * @param falseEasting See \ref false_easting . (not in EPSG) - * @param falseNorthing See \ref false_northing . (not in EPSG) - * @return a new Conversion. - * - * @since 6.3 - */ -ConversionNNPtr Conversion::createVerticalPerspective( - const util::PropertyMap &properties, const common::Angle &topoOriginLat, - const common::Angle &topoOriginLong, const common::Length &topoOriginHeight, - const common::Length &viewPointHeight, const common::Length &falseEasting, - const common::Length &falseNorthing) { - return create(properties, EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE, - createParams(topoOriginLat, topoOriginLong, topoOriginHeight, - viewPointHeight, falseEasting, falseNorthing)); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the Pole Rotation method, using - * the conventions of the GRIB 1 and GRIB 2 data formats. - * - * Those are mentioned in the Note 2 of - * https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_temp3-1.shtml - * - * Several conventions for the pole rotation method exists. - * The parameters provided in this method are remapped to the PROJ ob_tran - * operation with: - * <pre> - * +proj=ob_tran +o_proj=longlat +o_lon_p=-rotationAngle - * +o_lat_p=-southPoleLatInUnrotatedCRS - * +lon_0=southPoleLongInUnrotatedCRS - * </pre> - * - * Another implementation of that convention is also in the netcdf-java library: - * https://github.com/Unidata/netcdf-java/blob/3ce72c0cd167609ed8c69152bb4a004d1daa9273/cdm/core/src/main/java/ucar/unidata/geoloc/projection/RotatedLatLon.java - * - * The PROJ implementation of this method assumes a spherical ellipsoid. - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param southPoleLatInUnrotatedCRS Latitude of the point from the unrotated - * CRS, expressed in the unrotated CRS, that will become the south pole of the - * rotated CRS. - * @param southPoleLongInUnrotatedCRS Longitude of the point from the unrotated - * CRS, expressed in the unrotated CRS, that will become the south pole of the - * rotated CRS. - * @param axisRotation The angle of rotation about the new polar - * axis (measured clockwise when looking from the southern to the northern pole) - * of the coordinate system, assuming the new axis to have been obtained by - * first rotating the sphere through southPoleLongInUnrotatedCRS degrees about - * the geographic polar axis and then rotating through - * (90 + southPoleLatInUnrotatedCRS) degrees so that the southern pole moved - * along the (previously rotated) Greenwich meridian. - * @return a new Conversion. - * - * @since 7.0 - */ -ConversionNNPtr Conversion::createPoleRotationGRIBConvention( - const util::PropertyMap &properties, - const common::Angle &southPoleLatInUnrotatedCRS, - const common::Angle &southPoleLongInUnrotatedCRS, - const common::Angle &axisRotation) { - return create(properties, - PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION, - createParams(southPoleLatInUnrotatedCRS, - southPoleLongInUnrotatedCRS, axisRotation)); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -static OperationParameterNNPtr createOpParamNameEPSGCode(int code) { - const char *name = OperationParameter::getNameForEPSGCode(code); - assert(name); - return OperationParameter::create(createMapNameEPSGCode(name, code)); -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the Change of Vertical Unit - * method. - * - * This method is defined as [EPSG:1069] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1069) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param factor Conversion factor - * @return a new Conversion. - */ -ConversionNNPtr -Conversion::createChangeVerticalUnit(const util::PropertyMap &properties, - const common::Scale &factor) { - return create(properties, createMethodMapNameEPSGCode( - EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT), - VectorOfParameters{ - createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR), - }, - VectorOfValues{ - factor, - }); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the Height Depth Reversal - * method. - * - * This method is defined as [EPSG:1068] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1068) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @return a new Conversion. - * @since 6.3 - */ -ConversionNNPtr -Conversion::createHeightDepthReversal(const util::PropertyMap &properties) { - return create(properties, createMethodMapNameEPSGCode( - EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL), - {}, {}); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the Axis order reversal method - * - * This swaps the longitude, latitude axis. - * - * This method is defined as [EPSG:9843] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9843), - * or for 3D as [EPSG:9844] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9844) - * - * @param is3D Whether this should apply on 3D geographicCRS - * @return a new Conversion. - */ -ConversionNNPtr Conversion::createAxisOrderReversal(bool is3D) { - if (is3D) { - return create(createMapNameEPSGCode(AXIS_ORDER_CHANGE_3D_NAME, 15499), - createMethodMapNameEPSGCode( - EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D), - {}, {}); - } else { - return create(createMapNameEPSGCode(AXIS_ORDER_CHANGE_2D_NAME, 15498), - createMethodMapNameEPSGCode( - EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D), - {}, {}); - } -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a conversion based on the Geographic/Geocentric method. - * - * This method is defined as [EPSG:9602] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9602), - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @return a new Conversion. - */ -ConversionNNPtr -Conversion::createGeographicGeocentric(const util::PropertyMap &properties) { - return create(properties, createMethodMapNameEPSGCode( - EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC), - {}, {}); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -static const char *getCRSQualifierStr(const crs::CRSPtr &crs) { - auto geod = dynamic_cast<crs::GeodeticCRS *>(crs.get()); - if (geod) { - if (geod->isGeocentric()) { - return " (geocentric)"; - } - auto geog = dynamic_cast<crs::GeographicCRS *>(geod); - if (geog) { - if (geog->coordinateSystem()->axisList().size() == 2) { - return " (geog2D)"; - } else { - return " (geog3D)"; - } - } - } - return ""; -} - -// --------------------------------------------------------------------------- - -static std::string buildOpName(const char *opType, const crs::CRSPtr &source, - const crs::CRSPtr &target) { - std::string res(opType); - const auto &srcName = source->nameStr(); - const auto &targetName = target->nameStr(); - const char *srcQualifier = ""; - const char *targetQualifier = ""; - if (srcName == targetName) { - srcQualifier = getCRSQualifierStr(source); - targetQualifier = getCRSQualifierStr(target); - if (strcmp(srcQualifier, targetQualifier) == 0) { - srcQualifier = ""; - targetQualifier = ""; - } - } - res += " from "; - res += srcName; - res += srcQualifier; - res += " to "; - res += targetName; - res += targetQualifier; - return res; -} - -// --------------------------------------------------------------------------- - -ConversionNNPtr -Conversion::createGeographicGeocentric(const crs::CRSNNPtr &sourceCRS, - const crs::CRSNNPtr &targetCRS) { - auto properties = util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - buildOpName("Conversion", sourceCRS, targetCRS)); - auto conv = createGeographicGeocentric(properties); - conv->setCRSs(sourceCRS, targetCRS, nullptr); - return conv; -} -// --------------------------------------------------------------------------- - -static util::PropertyMap &addDomains(util::PropertyMap &map, - const common::ObjectUsage *obj) { - - auto ar = util::ArrayOfBaseObject::create(); - for (const auto &domain : obj->domains()) { - ar->add(domain); - } - if (!ar->empty()) { - map.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, ar); - } - return map; -} - -// --------------------------------------------------------------------------- - -static void addModifiedIdentifier(util::PropertyMap &map, - const common::IdentifiedObject *obj, - bool inverse, bool derivedFrom) { - // If original operation is AUTH:CODE, then assign INVERSE(AUTH):CODE - // as identifier. - - auto ar = util::ArrayOfBaseObject::create(); - for (const auto &idSrc : obj->identifiers()) { - auto authName = *(idSrc->codeSpace()); - const auto &srcCode = idSrc->code(); - if (derivedFrom) { - authName = concat("DERIVED_FROM(", authName, ")"); - } - if (inverse) { - if (starts_with(authName, "INVERSE(") && authName.back() == ')') { - authName = authName.substr(strlen("INVERSE(")); - authName.resize(authName.size() - 1); - } else { - authName = concat("INVERSE(", authName, ")"); - } - } - auto idsProp = util::PropertyMap().set( - metadata::Identifier::CODESPACE_KEY, authName); - ar->add(metadata::Identifier::create(srcCode, idsProp)); - } - if (!ar->empty()) { - map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar); - } -} - -// --------------------------------------------------------------------------- - -static util::PropertyMap -createPropertiesForInverse(const OperationMethodNNPtr &method) { - util::PropertyMap map; - - const std::string &forwardName = method->nameStr(); - if (!forwardName.empty()) { - if (starts_with(forwardName, INVERSE_OF)) { - map.set(common::IdentifiedObject::NAME_KEY, - forwardName.substr(INVERSE_OF.size())); - } else { - map.set(common::IdentifiedObject::NAME_KEY, - INVERSE_OF + forwardName); - } - } - - addModifiedIdentifier(map, method.get(), true, false); - - return map; -} - -// --------------------------------------------------------------------------- - -InverseConversion::InverseConversion(const ConversionNNPtr &forward) - : Conversion( - OperationMethod::create(createPropertiesForInverse(forward->method()), - forward->method()->parameters()), - forward->parameterValues()), - InverseCoordinateOperation(forward, true) { - setPropertiesFromForward(); -} - -// --------------------------------------------------------------------------- - -InverseConversion::~InverseConversion() = default; - -// --------------------------------------------------------------------------- - -ConversionNNPtr InverseConversion::inverseAsConversion() const { - return NN_NO_CHECK( - util::nn_dynamic_pointer_cast<Conversion>(forwardOperation_)); -} - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr -InverseConversion::create(const ConversionNNPtr &forward) { - auto conv = util::nn_make_shared<InverseConversion>(forward); - conv->assignSelf(conv); - return conv; -} - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr InverseConversion::_shallowClone() const { - auto op = InverseConversion::nn_make_shared<InverseConversion>( - inverseAsConversion()->shallowClone()); - op->assignSelf(op); - op->setCRSs(this, false); - return util::nn_static_pointer_cast<CoordinateOperation>(op); -} - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -static bool isAxisOrderReversal2D(int methodEPSGCode) { - return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D; -} - -static bool isAxisOrderReversal3D(int methodEPSGCode) { - return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D; -} - -bool isAxisOrderReversal(int methodEPSGCode) { - return isAxisOrderReversal2D(methodEPSGCode) || - isAxisOrderReversal3D(methodEPSGCode); -} -//! @endcond - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr Conversion::inverse() const { - const int methodEPSGCode = method()->getEPSGCode(); - - if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) { - const double convFactor = parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); - auto conv = createChangeVerticalUnit( - createPropertiesForInverse(this, false, false), - common::Scale(1.0 / convFactor)); - conv->setCRSs(this, true); - return conv; - } - - const bool l_isAxisOrderReversal2D = isAxisOrderReversal2D(methodEPSGCode); - const bool l_isAxisOrderReversal3D = isAxisOrderReversal3D(methodEPSGCode); - if (l_isAxisOrderReversal2D || l_isAxisOrderReversal3D) { - auto conv = createAxisOrderReversal(l_isAxisOrderReversal3D); - conv->setCRSs(this, true); - return conv; - } - - if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) { - - auto conv = createGeographicGeocentric( - createPropertiesForInverse(this, false, false)); - conv->setCRSs(this, true); - return conv; - } - - if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { - - auto conv = createHeightDepthReversal( - createPropertiesForInverse(this, false, false)); - conv->setCRSs(this, true); - return conv; - } - - return InverseConversion::create(NN_NO_CHECK( - util::nn_dynamic_pointer_cast<Conversion>(shared_from_this()))); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -static double msfn(double phi, double e2) { - const double sinphi = std::sin(phi); - const double cosphi = std::cos(phi); - return pj_msfn(sinphi, cosphi, e2); -} - -// --------------------------------------------------------------------------- - -static double tsfn(double phi, double ec) { - const double sinphi = std::sin(phi); - return pj_tsfn(phi, sinphi, ec); -} - -// --------------------------------------------------------------------------- - -// Function whose zeroes are the sin of the standard parallels of LCC_2SP -static double lcc_1sp_to_2sp_f(double sinphi, double K, double ec, double n) { - const double x = sinphi; - const double ecx = ec * x; - return (1 - x * x) / (1 - ecx * ecx) - - K * K * std::pow((1.0 - x) / (1.0 + x) * - std::pow((1.0 + ecx) / (1.0 - ecx), ec), - n); -} - -// --------------------------------------------------------------------------- - -// Find the sin of the standard parallels of LCC_2SP -static double find_zero_lcc_1sp_to_2sp_f(double sinphi0, bool bNorth, double K, - double ec) { - double a, b; - double f_a; - if (bNorth) { - // Look for zero above phi0 - a = sinphi0; - b = 1.0; // sin(North pole) - f_a = 1.0; // some positive value, but we only care about the sign - } else { - // Look for zero below phi0 - a = -1.0; // sin(South pole) - b = sinphi0; - f_a = -1.0; // minus infinity in fact, but we only care about the sign - } - // We use dichotomy search. lcc_1sp_to_2sp_f() is positive at sinphi_init, - // has a zero in ]-1,sinphi0[ and ]sinphi0,1[ ranges - for (int N = 0; N < 100; N++) { - double c = (a + b) / 2; - double f_c = lcc_1sp_to_2sp_f(c, K, ec, sinphi0); - if (f_c == 0.0 || (b - a) < 1e-18) { - return c; - } - if ((f_c > 0 && f_a > 0) || (f_c < 0 && f_a < 0)) { - a = c; - f_a = f_c; - } else { - b = c; - } - } - return (a + b) / 2; -} - -static inline double DegToRad(double x) { return x / 180.0 * M_PI; } -static inline double RadToDeg(double x) { return x / M_PI * 180.0; } - -//! @endcond - -// --------------------------------------------------------------------------- - -/** - * \brief Return an equivalent projection. - * - * Currently implemented: - * <ul> - * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP) to - * EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP)</li> - * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP) to - * EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP)</li> - * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP to - * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP</li> - * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP to - * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP</li> - * </ul> - * - * @param targetEPSGCode EPSG code of the target method. - * @return new conversion, or nullptr - */ -ConversionPtr Conversion::convertToOtherMethod(int targetEPSGCode) const { - const int current_epsg_code = method()->getEPSGCode(); - if (current_epsg_code == targetEPSGCode) { - return util::nn_dynamic_pointer_cast<Conversion>(shared_from_this()); - } - - auto geogCRS = dynamic_cast<crs::GeodeticCRS *>(sourceCRS().get()); - if (!geogCRS) { - return nullptr; - } - - const double e2 = geogCRS->ellipsoid()->squaredEccentricity(); - if (e2 < 0) { - return nullptr; - } - - if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_A && - targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && - parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) { - const double k0 = parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN); - if (!(k0 > 0 && k0 <= 1.0 + 1e-10)) - return nullptr; - const double dfStdP1Lat = - (k0 >= 1.0) - ? 0.0 - : std::acos(std::sqrt((1.0 - e2) / ((1.0 / (k0 * k0)) - e2))); - auto latitudeFirstParallel = common::Angle( - common::Angle(dfStdP1Lat, common::UnitOfMeasure::RADIAN) - .convertToUnit(common::UnitOfMeasure::DEGREE), - common::UnitOfMeasure::DEGREE); - auto conv = createMercatorVariantB( - util::PropertyMap(), latitudeFirstParallel, - common::Angle(parameterValueMeasure( - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), - common::Length( - parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), - common::Length( - parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING))); - conv->setCRSs(this, false); - return conv.as_nullable(); - } - - if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && - targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) { - const double phi1 = parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL); - if (!(std::fabs(phi1) < M_PI / 2)) - return nullptr; - const double k0 = msfn(phi1, e2); - auto conv = createMercatorVariantA( - util::PropertyMap(), - common::Angle(0.0, common::UnitOfMeasure::DEGREE), - common::Angle(parameterValueMeasure( - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), - common::Scale(k0, common::UnitOfMeasure::SCALE_UNITY), - common::Length( - parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), - common::Length( - parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING))); - conv->setCRSs(this, false); - return conv.as_nullable(); - } - - if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP && - targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { - // Notations m0, t0, n, m1, t1, F are those of the EPSG guidance - // "1.3.1.1 Lambert Conic Conformal (2SP)" and - // "1.3.1.2 Lambert Conic Conformal (1SP)" and - // or Snyder pages 106-109 - auto latitudeOfOrigin = common::Angle(parameterValueMeasure( - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN)); - const double phi0 = latitudeOfOrigin.getSIValue(); - const double k0 = parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN); - if (!(std::fabs(phi0) < M_PI / 2)) - return nullptr; - if (!(k0 > 0 && k0 <= 1.0 + 1e-10)) - return nullptr; - const double ec = std::sqrt(e2); - const double m0 = msfn(phi0, e2); - const double t0 = tsfn(phi0, ec); - const double n = sin(phi0); - if (std::fabs(n) < 1e-10) - return nullptr; - if (fabs(k0 - 1.0) <= 1e-10) { - auto conv = createLambertConicConformal_2SP( - util::PropertyMap(), latitudeOfOrigin, - common::Angle(parameterValueMeasure( - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), - latitudeOfOrigin, latitudeOfOrigin, - common::Length( - parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), - common::Length( - parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING))); - conv->setCRSs(this, false); - return conv.as_nullable(); - } else { - const double K = k0 * m0 / std::pow(t0, n); - const double phi1 = - std::asin(find_zero_lcc_1sp_to_2sp_f(n, true, K, ec)); - const double phi2 = - std::asin(find_zero_lcc_1sp_to_2sp_f(n, false, K, ec)); - double phi1Deg = RadToDeg(phi1); - double phi2Deg = RadToDeg(phi2); - - // Try to round to hundreth of degree if very close to it - if (std::fabs(phi1Deg * 1000 - std::floor(phi1Deg * 1000 + 0.5)) < - 1e-8) - phi1Deg = floor(phi1Deg * 1000 + 0.5) / 1000; - if (std::fabs(phi2Deg * 1000 - std::floor(phi2Deg * 1000 + 0.5)) < - 1e-8) - phi2Deg = std::floor(phi2Deg * 1000 + 0.5) / 1000; - - // The following improvement is too turn the LCC1SP equivalent of - // EPSG:2154 to the real LCC2SP - // If the computed latitude of origin is close to .0 or .5 degrees - // then check if rounding it to it will get a false northing - // close to an integer - const double FN = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING); - const double latitudeOfOriginDeg = - latitudeOfOrigin.convertToUnit(common::UnitOfMeasure::DEGREE); - if (std::fabs(latitudeOfOriginDeg * 2 - - std::floor(latitudeOfOriginDeg * 2 + 0.5)) < 0.2) { - const double dfRoundedLatOfOrig = - std::floor(latitudeOfOriginDeg * 2 + 0.5) / 2; - const double m1 = msfn(phi1, e2); - const double t1 = tsfn(phi1, ec); - const double F = m1 / (n * std::pow(t1, n)); - const double a = - geogCRS->ellipsoid()->semiMajorAxis().getSIValue(); - const double tRoundedLatOfOrig = - tsfn(DegToRad(dfRoundedLatOfOrig), ec); - const double FN_correction = - a * F * (std::pow(tRoundedLatOfOrig, n) - std::pow(t0, n)); - const double FN_corrected = FN - FN_correction; - const double FN_corrected_rounded = - std::floor(FN_corrected + 0.5); - if (std::fabs(FN_corrected - FN_corrected_rounded) < 1e-8) { - auto conv = createLambertConicConformal_2SP( - util::PropertyMap(), - common::Angle(dfRoundedLatOfOrig, - common::UnitOfMeasure::DEGREE), - common::Angle(parameterValueMeasure( - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), - common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE), - common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE), - common::Length(parameterValueMeasure( - EPSG_CODE_PARAMETER_FALSE_EASTING)), - common::Length(FN_corrected_rounded)); - conv->setCRSs(this, false); - return conv.as_nullable(); - } - } - - auto conv = createLambertConicConformal_2SP( - util::PropertyMap(), latitudeOfOrigin, - common::Angle(parameterValueMeasure( - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), - common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE), - common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE), - common::Length( - parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), - common::Length(FN)); - conv->setCRSs(this, false); - return conv.as_nullable(); - } - } - - if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP && - targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP) { - // Notations m0, t0, m1, t1, m2, t2 n, F are those of the EPSG guidance - // "1.3.1.1 Lambert Conic Conformal (2SP)" and - // "1.3.1.2 Lambert Conic Conformal (1SP)" and - // or Snyder pages 106-109 - const double phiF = - parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN) - .getSIValue(); - const double phi1 = - parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) - .getSIValue(); - const double phi2 = - parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) - .getSIValue(); - if (!(std::fabs(phiF) < M_PI / 2)) - return nullptr; - if (!(std::fabs(phi1) < M_PI / 2)) - return nullptr; - if (!(std::fabs(phi2) < M_PI / 2)) - return nullptr; - const double ec = std::sqrt(e2); - const double m1 = msfn(phi1, e2); - const double m2 = msfn(phi2, e2); - const double t1 = tsfn(phi1, ec); - const double t2 = tsfn(phi2, ec); - const double n_denom = std::log(t1) - std::log(t2); - const double n = (std::fabs(n_denom) < 1e-10) - ? std::sin(phi1) - : (std::log(m1) - std::log(m2)) / n_denom; - if (std::fabs(n) < 1e-10) - return nullptr; - const double F = m1 / (n * std::pow(t1, n)); - const double phi0 = std::asin(n); - const double m0 = msfn(phi0, e2); - const double t0 = tsfn(phi0, ec); - const double F0 = m0 / (n * std::pow(t0, n)); - const double k0 = F / F0; - const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue(); - const double tF = tsfn(phiF, ec); - const double FN_correction = - a * F * (std::pow(tF, n) - std::pow(t0, n)); - - double phi0Deg = RadToDeg(phi0); - // Try to round to thousandth of degree if very close to it - if (std::fabs(phi0Deg * 1000 - std::floor(phi0Deg * 1000 + 0.5)) < 1e-8) - phi0Deg = std::floor(phi0Deg * 1000 + 0.5) / 1000; - - auto conv = createLambertConicConformal_1SP( - util::PropertyMap(), - common::Angle(phi0Deg, common::UnitOfMeasure::DEGREE), - common::Angle(parameterValueMeasure( - EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN)), - common::Scale(k0), common::Length(parameterValueMeasure( - EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN)), - common::Length( - parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN) + - (std::fabs(FN_correction) > 1e-8 ? FN_correction : 0))); - conv->setCRSs(this, false); - return conv.as_nullable(); - } - - return nullptr; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -static void getESRIMethodNameAndParams(const Conversion *conv, - const std::string &methodName, - int methodEPSGCode, - const char *&esriMethodName, - const ESRIParamMapping *&esriParams) { - esriParams = nullptr; - esriMethodName = nullptr; - const auto *esriMapping = getESRIMapping(methodName, methodEPSGCode); - const auto l_targetCRS = conv->targetCRS(); - if (esriMapping) { - esriParams = esriMapping->params; - esriMethodName = esriMapping->esri_name; - if (esriMapping->epsg_code == - EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL || - esriMapping->epsg_code == - EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) { - if (l_targetCRS && - ci_find(l_targetCRS->nameStr(), "Plate Carree") != - std::string::npos && - conv->parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) { - esriParams = paramsESRI_Plate_Carree; - esriMethodName = "Plate_Carree"; - } else { - esriParams = paramsESRI_Equidistant_Cylindrical; - esriMethodName = "Equidistant_Cylindrical"; - } - } else if (esriMapping->epsg_code == - EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { - if (ci_find(conv->nameStr(), "Gauss Kruger") != std::string::npos || - (l_targetCRS && (ci_find(l_targetCRS->nameStr(), "Gauss") != - std::string::npos || - ci_find(l_targetCRS->nameStr(), "GK_") != - std::string::npos))) { - esriParams = paramsESRI_Gauss_Kruger; - esriMethodName = "Gauss_Kruger"; - } else { - esriParams = paramsESRI_Transverse_Mercator; - esriMethodName = "Transverse_Mercator"; - } - } else if (esriMapping->epsg_code == - EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) { - if (std::abs( - conv->parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) - - conv->parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) < - 1e-15) { - esriParams = - paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin; - esriMethodName = - "Hotine_Oblique_Mercator_Azimuth_Natural_Origin"; - } else { - esriParams = - paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin; - esriMethodName = "Rectified_Skew_Orthomorphic_Natural_Origin"; - } - } else if (esriMapping->epsg_code == - EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) { - if (std::abs( - conv->parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) - - conv->parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) < - 1e-15) { - esriParams = paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center; - esriMethodName = "Hotine_Oblique_Mercator_Azimuth_Center"; - } else { - esriParams = paramsESRI_Rectified_Skew_Orthomorphic_Center; - esriMethodName = "Rectified_Skew_Orthomorphic_Center"; - } - } else if (esriMapping->epsg_code == - EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) { - if (conv->parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL) > 0) { - esriMethodName = "Stereographic_North_Pole"; - } else { - esriMethodName = "Stereographic_South_Pole"; - } - } - } -} - -// --------------------------------------------------------------------------- - -const char *Conversion::getESRIMethodName() const { - const auto &l_method = method(); - const auto &methodName = l_method->nameStr(); - const auto methodEPSGCode = l_method->getEPSGCode(); - const ESRIParamMapping *esriParams = nullptr; - const char *esriMethodName = nullptr; - getESRIMethodNameAndParams(this, methodName, methodEPSGCode, esriMethodName, - esriParams); - return esriMethodName; -} - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -const char *Conversion::getWKT1GDALMethodName() const { - const auto &l_method = method(); - const auto methodEPSGCode = l_method->getEPSGCode(); - if (methodEPSGCode == - EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) { - return "Mercator_1SP"; - } - const MethodMapping *mapping = getMapping(l_method.get()); - return mapping ? mapping->wkt1_name : nullptr; -} - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -void Conversion::_exportToWKT(io::WKTFormatter *formatter) const { - const auto &l_method = method(); - const auto &methodName = l_method->nameStr(); - const auto methodEPSGCode = l_method->getEPSGCode(); - const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; - - if (!isWKT2 && formatter->useESRIDialect()) { - if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) { - auto eqConv = - convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B); - if (eqConv) { - eqConv->_exportToWKT(formatter); - return; - } - } - } - - if (isWKT2) { - formatter->startNode(formatter->useDerivingConversion() - ? io::WKTConstants::DERIVINGCONVERSION - : io::WKTConstants::CONVERSION, - !identifiers().empty()); - formatter->addQuotedString(nameStr()); - } else { - formatter->enter(); - formatter->pushOutputUnit(false); - formatter->pushOutputId(false); - } - -#ifdef DEBUG_CONVERSION_ID - if (sourceCRS() && targetCRS()) { - formatter->startNode("SOURCECRS_ID", false); - sourceCRS()->formatID(formatter); - formatter->endNode(); - formatter->startNode("TARGETCRS_ID", false); - targetCRS()->formatID(formatter); - formatter->endNode(); - } -#endif - - bool bAlreadyWritten = false; - if (!isWKT2 && formatter->useESRIDialect()) { - const ESRIParamMapping *esriParams = nullptr; - const char *esriMethodName = nullptr; - getESRIMethodNameAndParams(this, methodName, methodEPSGCode, - esriMethodName, esriParams); - if (esriMethodName && esriParams) { - formatter->startNode(io::WKTConstants::PROJECTION, false); - formatter->addQuotedString(esriMethodName); - formatter->endNode(); - - for (int i = 0; esriParams[i].esri_name != nullptr; i++) { - const auto &esriParam = esriParams[i]; - formatter->startNode(io::WKTConstants::PARAMETER, false); - formatter->addQuotedString(esriParam.esri_name); - if (esriParam.wkt2_name) { - const auto &pv = parameterValue(esriParam.wkt2_name, - esriParam.epsg_code); - if (pv && pv->type() == ParameterValue::Type::MEASURE) { - const auto &v = pv->value(); - // as we don't output the natural unit, output - // to the registered linear / angular unit. - const auto &unitType = v.unit().type(); - if (unitType == common::UnitOfMeasure::Type::LINEAR) { - formatter->add(v.convertToUnit( - *(formatter->axisLinearUnit()))); - } else if (unitType == - common::UnitOfMeasure::Type::ANGULAR) { - const auto &angUnit = - *(formatter->axisAngularUnit()); - double val = v.convertToUnit(angUnit); - if (angUnit == common::UnitOfMeasure::DEGREE) { - if (val > 180.0) { - val -= 360.0; - } else if (val < -180.0) { - val += 360.0; - } - } - formatter->add(val); - } else { - formatter->add(v.getSIValue()); - } - } else if (ci_find(esriParam.esri_name, "scale") != - std::string::npos) { - formatter->add(1.0); - } else { - formatter->add(0.0); - } - } else { - formatter->add(esriParam.fixed_value); - } - formatter->endNode(); - } - bAlreadyWritten = true; - } - } else if (!isWKT2) { - if (methodEPSGCode == - EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) { - const double latitudeOrigin = parameterValueNumeric( - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - common::UnitOfMeasure::DEGREE); - if (latitudeOrigin != 0) { - throw io::FormattingException( - std::string("Unsupported value for ") + - EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); - } - - bAlreadyWritten = true; - formatter->startNode(io::WKTConstants::PROJECTION, false); - formatter->addQuotedString("Mercator_1SP"); - formatter->endNode(); - - formatter->startNode(io::WKTConstants::PARAMETER, false); - formatter->addQuotedString("central_meridian"); - const double centralMeridian = parameterValueNumeric( - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - common::UnitOfMeasure::DEGREE); - formatter->add(centralMeridian); - formatter->endNode(); - - formatter->startNode(io::WKTConstants::PARAMETER, false); - formatter->addQuotedString("scale_factor"); - formatter->add(1.0); - formatter->endNode(); - - formatter->startNode(io::WKTConstants::PARAMETER, false); - formatter->addQuotedString("false_easting"); - const double falseEasting = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING); - formatter->add(falseEasting); - formatter->endNode(); - - formatter->startNode(io::WKTConstants::PARAMETER, false); - formatter->addQuotedString("false_northing"); - const double falseNorthing = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING); - formatter->add(falseNorthing); - formatter->endNode(); - } else if (starts_with(methodName, "PROJ ")) { - bAlreadyWritten = true; - formatter->startNode(io::WKTConstants::PROJECTION, false); - formatter->addQuotedString("custom_proj4"); - formatter->endNode(); - } - } - - if (!bAlreadyWritten) { - l_method->_exportToWKT(formatter); - - const MethodMapping *mapping = - !isWKT2 ? getMapping(l_method.get()) : nullptr; - for (const auto &genOpParamvalue : parameterValues()) { - - // EPSG has normally no Latitude of natural origin for Equidistant - // Cylindrical but PROJ can handle it, so output the parameter if - // not zero - if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL || - methodEPSGCode == - EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) { - auto opParamvalue = - dynamic_cast<const OperationParameterValue *>( - genOpParamvalue.get()); - if (opParamvalue && - opParamvalue->parameter()->getEPSGCode() == - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) { - const auto ¶mValue = opParamvalue->parameterValue(); - if (paramValue->type() == ParameterValue::Type::MEASURE) { - const auto &measure = paramValue->value(); - if (measure.getSIValue() == 0) { - continue; - } - } - } - } - // Same for false easting / false northing for Vertical Perspective - else if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE) { - auto opParamvalue = - dynamic_cast<const OperationParameterValue *>( - genOpParamvalue.get()); - if (opParamvalue) { - const auto paramEPSGCode = - opParamvalue->parameter()->getEPSGCode(); - if (paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_EASTING || - paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_NORTHING) { - const auto ¶mValue = opParamvalue->parameterValue(); - if (paramValue->type() == - ParameterValue::Type::MEASURE) { - const auto &measure = paramValue->value(); - if (measure.getSIValue() == 0) { - continue; - } - } - } - } - } - genOpParamvalue->_exportToWKT(formatter, mapping); - } - } - - if (isWKT2) { - if (formatter->outputId()) { - formatID(formatter); - } - formatter->endNode(); - } else { - formatter->popOutputUnit(); - formatter->popOutputId(); - formatter->leave(); - } -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -void Conversion::_exportToJSON( - io::JSONFormatter *formatter) const // throw(FormattingException) -{ - auto writer = formatter->writer(); - auto objectContext( - formatter->MakeObjectContext("Conversion", !identifiers().empty())); - - writer->AddObjKey("name"); - auto l_name = nameStr(); - if (l_name.empty()) { - writer->Add("unnamed"); - } else { - writer->Add(l_name); - } - - writer->AddObjKey("method"); - formatter->setOmitTypeInImmediateChild(); - formatter->setAllowIDInImmediateChild(); - method()->_exportToJSON(formatter); - - const auto &l_parameterValues = parameterValues(); - if (!l_parameterValues.empty()) { - writer->AddObjKey("parameters"); - { - auto parametersContext(writer->MakeArrayContext(false)); - for (const auto &genOpParamvalue : l_parameterValues) { - formatter->setAllowIDInImmediateChild(); - formatter->setOmitTypeInImmediateChild(); - genOpParamvalue->_exportToJSON(formatter); - } - } - } - - if (formatter->outputId()) { - formatID(formatter); - } -} - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static bool createPROJ4WebMercator(const Conversion *conv, - io::PROJStringFormatter *formatter) { - const double centralMeridian = conv->parameterValueNumeric( - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, - common::UnitOfMeasure::DEGREE); - - const double falseEasting = - conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING); - - const double falseNorthing = - conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING); - - auto sourceCRS = conv->sourceCRS(); - auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); - if (!geogCRS) { - return false; - } - - std::string units("m"); - auto targetCRS = conv->targetCRS(); - auto targetProjCRS = - dynamic_cast<const crs::ProjectedCRS *>(targetCRS.get()); - if (targetProjCRS) { - const auto &axisList = targetProjCRS->coordinateSystem()->axisList(); - const auto &unit = axisList[0]->unit(); - if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE, - util::IComparable::Criterion::EQUIVALENT)) { - auto projUnit = unit.exportToPROJString(); - if (!projUnit.empty()) { - units = projUnit; - } else { - return false; - } - } - } - - formatter->addStep("merc"); - const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue(); - formatter->addParam("a", a); - formatter->addParam("b", a); - formatter->addParam("lat_ts", 0.0); - formatter->addParam("lon_0", centralMeridian); - formatter->addParam("x_0", falseEasting); - formatter->addParam("y_0", falseNorthing); - formatter->addParam("k", 1.0); - formatter->addParam("units", units); - formatter->addParam("nadgrids", "@null"); - formatter->addParam("wktext"); - formatter->addParam("no_defs"); - return true; -} - -// --------------------------------------------------------------------------- - -static bool -createPROJExtensionFromCustomProj(const Conversion *conv, - io::PROJStringFormatter *formatter, - bool forExtensionNode) { - const auto &methodName = conv->method()->nameStr(); - assert(starts_with(methodName, "PROJ ")); - auto tokens = split(methodName, ' '); - - formatter->addStep(tokens[1]); - - if (forExtensionNode) { - auto sourceCRS = conv->sourceCRS(); - auto geogCRS = - dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); - if (!geogCRS) { - return false; - } - geogCRS->addDatumInfoToPROJString(formatter); - } - - for (size_t i = 2; i < tokens.size(); i++) { - auto kv = split(tokens[i], '='); - if (kv.size() == 2) { - formatter->addParam(kv[0], kv[1]); - } else { - formatter->addParam(tokens[i]); - } - } - - for (const auto &genOpParamvalue : conv->parameterValues()) { - auto opParamvalue = dynamic_cast<const OperationParameterValue *>( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶mName = opParamvalue->parameter()->nameStr(); - const auto ¶mValue = opParamvalue->parameterValue(); - if (paramValue->type() == ParameterValue::Type::MEASURE) { - const auto &measure = paramValue->value(); - const auto unitType = measure.unit().type(); - if (unitType == common::UnitOfMeasure::Type::LINEAR) { - formatter->addParam(paramName, measure.getSIValue()); - } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) { - formatter->addParam( - paramName, - measure.convertToUnit(common::UnitOfMeasure::DEGREE)); - } else { - formatter->addParam(paramName, measure.value()); - } - } - } - } - - if (forExtensionNode) { - formatter->addParam("wktext"); - formatter->addParam("no_defs"); - } - return true; -} -//! @endcond - -// --------------------------------------------------------------------------- - -bool Conversion::addWKTExtensionNode(io::WKTFormatter *formatter) const { - const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; - if (!isWKT2) { - const auto &l_method = method(); - const auto &methodName = l_method->nameStr(); - const int methodEPSGCode = l_method->getEPSGCode(); - if (l_method->getPrivate()->projMethodOverride_ == "tmerc approx" || - l_method->getPrivate()->projMethodOverride_ == "utm approx") { - auto projFormatter = io::PROJStringFormatter::create(); - projFormatter->setCRSExport(true); - projFormatter->setUseApproxTMerc(true); - formatter->startNode(io::WKTConstants::EXTENSION, false); - formatter->addQuotedString("PROJ4"); - _exportToPROJString(projFormatter.get()); - projFormatter->addParam("no_defs"); - formatter->addQuotedString(projFormatter->toString()); - formatter->endNode(); - return true; - } else if (methodEPSGCode == - EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR || - nameStr() == "Popular Visualisation Mercator") { - - auto projFormatter = io::PROJStringFormatter::create(); - projFormatter->setCRSExport(true); - if (createPROJ4WebMercator(this, projFormatter.get())) { - formatter->startNode(io::WKTConstants::EXTENSION, false); - formatter->addQuotedString("PROJ4"); - formatter->addQuotedString(projFormatter->toString()); - formatter->endNode(); - return true; - } - } else if (starts_with(methodName, "PROJ ")) { - auto projFormatter = io::PROJStringFormatter::create(); - projFormatter->setCRSExport(true); - if (createPROJExtensionFromCustomProj(this, projFormatter.get(), - true)) { - formatter->startNode(io::WKTConstants::EXTENSION, false); - formatter->addQuotedString("PROJ4"); - formatter->addQuotedString(projFormatter->toString()); - formatter->endNode(); - return true; - } - } else if (methodName == - PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) { - auto projFormatter = io::PROJStringFormatter::create(); - projFormatter->setCRSExport(true); - formatter->startNode(io::WKTConstants::EXTENSION, false); - formatter->addQuotedString("PROJ4"); - _exportToPROJString(projFormatter.get()); - projFormatter->addParam("no_defs"); - formatter->addQuotedString(projFormatter->toString()); - formatter->endNode(); - return true; - } - } - return false; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -void Conversion::_exportToPROJString( - io::PROJStringFormatter *formatter) const // throw(FormattingException) -{ - const auto &l_method = method(); - const auto &methodName = l_method->nameStr(); - const int methodEPSGCode = l_method->getEPSGCode(); - const bool isZUnitConversion = - methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT; - const bool isAffineParametric = - methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION; - const bool isGeographicGeocentric = - methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC; - const bool isHeightDepthReversal = - methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL; - const bool applySourceCRSModifiers = - !isZUnitConversion && !isAffineParametric && - !isAxisOrderReversal(methodEPSGCode) && !isGeographicGeocentric && - !isHeightDepthReversal; - bool applyTargetCRSModifiers = applySourceCRSModifiers; - - if (formatter->getCRSExport()) { - if (methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TOPOCENTRIC || - methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) { - throw io::FormattingException("Transformation cannot be exported " - "as a PROJ.4 string (but can be part " - "of a PROJ pipeline)"); - } - } - - auto l_sourceCRS = sourceCRS(); - crs::GeographicCRSPtr srcGeogCRS; - if (!formatter->getCRSExport() && l_sourceCRS && applySourceCRSModifiers) { - - crs::CRSPtr horiz = l_sourceCRS; - const auto compound = - dynamic_cast<const crs::CompoundCRS *>(l_sourceCRS.get()); - if (compound) { - const auto &components = compound->componentReferenceSystems(); - if (!components.empty()) { - horiz = components.front().as_nullable(); - } - } - - srcGeogCRS = std::dynamic_pointer_cast<crs::GeographicCRS>(horiz); - if (srcGeogCRS) { - formatter->setOmitProjLongLatIfPossible(true); - formatter->startInversion(); - srcGeogCRS->_exportToPROJString(formatter); - formatter->stopInversion(); - formatter->setOmitProjLongLatIfPossible(false); - } - - auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(horiz.get()); - if (projCRS) { - formatter->startInversion(); - formatter->pushOmitZUnitConversion(); - projCRS->addUnitConvertAndAxisSwap(formatter, false); - formatter->popOmitZUnitConversion(); - formatter->stopInversion(); - } - } - - const auto &convName = nameStr(); - bool bConversionDone = false; - bool bEllipsoidParametersDone = false; - bool useApprox = false; - if (methodEPSGCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { - // Check for UTM - int zone = 0; - bool north = true; - useApprox = - formatter->getUseApproxTMerc() || - l_method->getPrivate()->projMethodOverride_ == "tmerc approx" || - l_method->getPrivate()->projMethodOverride_ == "utm approx"; - if (isUTM(zone, north)) { - bConversionDone = true; - formatter->addStep("utm"); - if (useApprox) { - formatter->addParam("approx"); - } - formatter->addParam("zone", zone); - if (!north) { - formatter->addParam("south"); - } - } - } else if (methodEPSGCode == - EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) { - const double azimuth = - parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, - common::UnitOfMeasure::DEGREE); - const double angleRectifiedToSkewGrid = parameterValueNumeric( - EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, - common::UnitOfMeasure::DEGREE); - // Map to Swiss Oblique Mercator / somerc - if (std::fabs(azimuth - 90) < 1e-4 && - std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) { - bConversionDone = true; - formatter->addStep("somerc"); - formatter->addParam( - "lat_0", parameterValueNumeric( - EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, - common::UnitOfMeasure::DEGREE)); - formatter->addParam( - "lon_0", parameterValueNumeric( - EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, - common::UnitOfMeasure::DEGREE)); - formatter->addParam( - "k_0", parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE)); - formatter->addParam("x_0", parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_FALSE_EASTING)); - formatter->addParam("y_0", parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_FALSE_NORTHING)); - } - } else if (methodEPSGCode == - EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) { - const double azimuth = - parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, - common::UnitOfMeasure::DEGREE); - const double angleRectifiedToSkewGrid = parameterValueNumeric( - EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, - common::UnitOfMeasure::DEGREE); - // Map to Swiss Oblique Mercator / somerc - if (std::fabs(azimuth - 90) < 1e-4 && - std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) { - bConversionDone = true; - formatter->addStep("somerc"); - formatter->addParam( - "lat_0", parameterValueNumeric( - EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, - common::UnitOfMeasure::DEGREE)); - formatter->addParam( - "lon_0", parameterValueNumeric( - EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, - common::UnitOfMeasure::DEGREE)); - formatter->addParam( - "k_0", parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE)); - formatter->addParam( - "x_0", parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE)); - formatter->addParam( - "y_0", parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE)); - } - } else if (methodEPSGCode == EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED) { - double colatitude = - parameterValueNumeric(EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, - common::UnitOfMeasure::DEGREE); - double latitudePseudoStandardParallel = parameterValueNumeric( - EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, - common::UnitOfMeasure::DEGREE); - // 30deg 17' 17.30311'' = 30.28813975277777776 - // 30deg 17' 17.303'' = 30.288139722222223 as used in GDAL WKT1 - if (std::fabs(colatitude - 30.2881397) > 1e-7) { - throw io::FormattingException( - std::string("Unsupported value for ") + - EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS); - } - if (std::fabs(latitudePseudoStandardParallel - 78.5) > 1e-8) { - throw io::FormattingException( - std::string("Unsupported value for ") + - EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL); - } - } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) { - double latitudeOrigin = parameterValueNumeric( - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - common::UnitOfMeasure::DEGREE); - if (latitudeOrigin != 0) { - throw io::FormattingException( - std::string("Unsupported value for ") + - EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); - } - } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B) { - const auto &scaleFactor = parameterValueMeasure(WKT1_SCALE_FACTOR, 0); - if (scaleFactor.unit().type() != common::UnitOfMeasure::Type::UNKNOWN && - std::fabs(scaleFactor.getSIValue() - 1.0) > 1e-10) { - throw io::FormattingException( - "Unexpected presence of scale factor in Mercator (variant B)"); - } - double latitudeOrigin = parameterValueNumeric( - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, - common::UnitOfMeasure::DEGREE); - if (latitudeOrigin != 0) { - throw io::FormattingException( - std::string("Unsupported value for ") + - EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); - } - } else if (methodEPSGCode == - EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) { - // We map TMSO to tmerc with axis=wsu. This only works if false easting - // and northings are zero, which is the case in practice for South - // African and Namibian EPSG CRS - const auto falseEasting = parameterValueNumeric( - EPSG_CODE_PARAMETER_FALSE_EASTING, common::UnitOfMeasure::METRE); - if (falseEasting != 0) { - throw io::FormattingException( - std::string("Unsupported value for ") + - EPSG_NAME_PARAMETER_FALSE_EASTING); - } - const auto falseNorthing = parameterValueNumeric( - EPSG_CODE_PARAMETER_FALSE_NORTHING, common::UnitOfMeasure::METRE); - if (falseNorthing != 0) { - throw io::FormattingException( - std::string("Unsupported value for ") + - EPSG_NAME_PARAMETER_FALSE_NORTHING); - } - // PROJ.4 specific hack for webmercator - } else if (formatter->getCRSExport() && - methodEPSGCode == - EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) { - if (!createPROJ4WebMercator(this, formatter)) { - throw io::FormattingException( - std::string("Cannot export ") + - EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR + - " as PROJ.4 string outside of a ProjectedCRS context"); - } - bConversionDone = true; - bEllipsoidParametersDone = true; - applyTargetCRSModifiers = false; - } else if (ci_equal(convName, "Popular Visualisation Mercator")) { - if (formatter->getCRSExport()) { - if (!createPROJ4WebMercator(this, formatter)) { - throw io::FormattingException(concat( - "Cannot export ", convName, - " as PROJ.4 string outside of a ProjectedCRS context")); - } - applyTargetCRSModifiers = false; - } else { - formatter->addStep("webmerc"); - if (l_sourceCRS) { - datum::Ellipsoid::WGS84->_exportToPROJString(formatter); - } - } - bConversionDone = true; - bEllipsoidParametersDone = true; - } else if (starts_with(methodName, "PROJ ")) { - bConversionDone = true; - createPROJExtensionFromCustomProj(this, formatter, false); - } else if (ci_equal(methodName, - PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION)) { - double southPoleLat = parameterValueNumeric( - PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION, - common::UnitOfMeasure::DEGREE); - double southPoleLon = parameterValueNumeric( - PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION, - common::UnitOfMeasure::DEGREE); - double rotation = parameterValueNumeric( - PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION, - common::UnitOfMeasure::DEGREE); - formatter->addStep("ob_tran"); - formatter->addParam("o_proj", "longlat"); - formatter->addParam("o_lon_p", -rotation); - formatter->addParam("o_lat_p", -southPoleLat); - formatter->addParam("lon_0", southPoleLon); - bConversionDone = true; - } else if (ci_equal(methodName, "Adams_Square_II")) { - // Look for ESRI method and parameter names (to be opposed - // to the OGC WKT2 names we use elsewhere, because there's no mapping - // of those parameters to OGC WKT2) - // We also reject non-default values for a number of parameters, - // because they are not implemented on PROJ side. The subset we - // support can handle ESRI:54098 WGS_1984_Adams_Square_II, but not - // ESRI:54099 WGS_1984_Spilhaus_Ocean_Map_in_Square - const double falseEasting = parameterValueNumeric( - "False_Easting", common::UnitOfMeasure::METRE); - const double falseNorthing = parameterValueNumeric( - "False_Northing", common::UnitOfMeasure::METRE); - const double scaleFactor = - parameterValue("Scale_Factor", 0) - ? parameterValueNumeric("Scale_Factor", - common::UnitOfMeasure::SCALE_UNITY) - : 1.0; - const double azimuth = - parameterValueNumeric("Azimuth", common::UnitOfMeasure::DEGREE); - const double longitudeOfCenter = parameterValueNumeric( - "Longitude_Of_Center", common::UnitOfMeasure::DEGREE); - const double latitudeOfCenter = parameterValueNumeric( - "Latitude_Of_Center", common::UnitOfMeasure::DEGREE); - const double XYPlaneRotation = parameterValueNumeric( - "XY_Plane_Rotation", common::UnitOfMeasure::DEGREE); - if (scaleFactor != 1.0 || azimuth != 0.0 || latitudeOfCenter != 0.0 || - XYPlaneRotation != 0.0) { - throw io::FormattingException("Unsupported value for one or " - "several parameters of " - "Adams_Square_II"); - } - formatter->addStep("adams_ws2"); - formatter->addParam("lon_0", longitudeOfCenter); - formatter->addParam("x_0", falseEasting); - formatter->addParam("y_0", falseNorthing); - bConversionDone = true; - } else if (formatter->convention() == - io::PROJStringFormatter::Convention::PROJ_5 && - isZUnitConversion) { - double convFactor = parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); - auto uom = common::UnitOfMeasure(std::string(), convFactor, - common::UnitOfMeasure::Type::LINEAR) - .exportToPROJString(); - auto reverse_uom = - convFactor == 0.0 - ? std::string() - : common::UnitOfMeasure(std::string(), 1.0 / convFactor, - common::UnitOfMeasure::Type::LINEAR) - .exportToPROJString(); - if (uom == "m") { - // do nothing - } else if (!uom.empty()) { - formatter->addStep("unitconvert"); - formatter->addParam("z_in", uom); - formatter->addParam("z_out", "m"); - } else if (!reverse_uom.empty()) { - formatter->addStep("unitconvert"); - formatter->addParam("z_in", "m"); - formatter->addParam("z_out", reverse_uom); - } else { - formatter->addStep("affine"); - formatter->addParam("s33", convFactor); - } - bConversionDone = true; - bEllipsoidParametersDone = true; - } else if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) { - if (!srcGeogCRS) { - throw io::FormattingException( - "Export of Geographic/Topocentric conversion to a PROJ string " - "requires an input geographic CRS"); - } - - formatter->addStep("cart"); - srcGeogCRS->ellipsoid()->_exportToPROJString(formatter); - - formatter->addStep("topocentric"); - const auto latOrigin = parameterValueNumeric( - EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, - common::UnitOfMeasure::DEGREE); - const auto lonOrigin = parameterValueNumeric( - EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, - common::UnitOfMeasure::DEGREE); - const auto heightOrigin = parameterValueNumeric( - EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, - common::UnitOfMeasure::METRE); - formatter->addParam("lat_0", latOrigin); - formatter->addParam("lon_0", lonOrigin); - formatter->addParam("h_0", heightOrigin); - bConversionDone = true; - } - - auto l_targetCRS = targetCRS(); - - bool bAxisSpecFound = false; - if (!bConversionDone) { - const MethodMapping *mapping = getMapping(l_method.get()); - if (mapping && mapping->proj_name_main) { - formatter->addStep(mapping->proj_name_main); - if (useApprox) { - formatter->addParam("approx"); - } - if (mapping->proj_name_aux) { - bool addAux = true; - if (internal::starts_with(mapping->proj_name_aux, "axis=")) { - if (mapping->epsg_code == EPSG_CODE_METHOD_KROVAK) { - auto projCRS = dynamic_cast<const crs::ProjectedCRS *>( - l_targetCRS.get()); - if (projCRS) { - const auto &axisList = - projCRS->coordinateSystem()->axisList(); - if (axisList[0]->direction() == - cs::AxisDirection::WEST && - axisList[1]->direction() == - cs::AxisDirection::SOUTH) { - formatter->addParam("czech"); - addAux = false; - } - } - } - bAxisSpecFound = true; - } - - // No need to add explicit f=0 if the ellipsoid is a sphere - if (strcmp(mapping->proj_name_aux, "f=0") == 0) { - crs::CRS *horiz = l_sourceCRS.get(); - const auto compound = - dynamic_cast<const crs::CompoundCRS *>(horiz); - if (compound) { - const auto &components = - compound->componentReferenceSystems(); - if (!components.empty()) { - horiz = components.front().get(); - } - } - - auto geogCRS = - dynamic_cast<const crs::GeographicCRS *>(horiz); - if (geogCRS && geogCRS->ellipsoid()->isSphere()) { - addAux = false; - } - } - - if (addAux) { - auto kv = split(mapping->proj_name_aux, '='); - if (kv.size() == 2) { - formatter->addParam(kv[0], kv[1]); - } else { - formatter->addParam(mapping->proj_name_aux); - } - } - } - - if (mapping->epsg_code == - EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) { - double latitudeStdParallel = parameterValueNumeric( - EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, - common::UnitOfMeasure::DEGREE); - formatter->addParam("lat_0", - (latitudeStdParallel >= 0) ? 90.0 : -90.0); - } - - for (int i = 0; mapping->params[i] != nullptr; i++) { - const auto *param = mapping->params[i]; - if (!param->proj_name) { - continue; - } - const auto &value = - parameterValueMeasure(param->wkt2_name, param->epsg_code); - double valueConverted = 0; - if (value == nullMeasure) { - // Deal with missing values. In an ideal world, this would - // not happen - if (param->epsg_code == - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) { - valueConverted = 1.0; - } - } else if (param->unit_type == - common::UnitOfMeasure::Type::ANGULAR) { - valueConverted = - value.convertToUnit(common::UnitOfMeasure::DEGREE); - } else { - valueConverted = value.getSIValue(); - } - - if (mapping->epsg_code == - EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP && - strcmp(param->proj_name, "lat_1") == 0) { - formatter->addParam(param->proj_name, valueConverted); - formatter->addParam("lat_0", valueConverted); - } else { - formatter->addParam(param->proj_name, valueConverted); - } - } - - } else { - if (!exportToPROJStringGeneric(formatter)) { - throw io::FormattingException( - concat("Unsupported conversion method: ", methodName)); - } - } - } - - if (l_targetCRS && applyTargetCRSModifiers) { - crs::CRS *horiz = l_targetCRS.get(); - const auto compound = dynamic_cast<const crs::CompoundCRS *>(horiz); - if (compound) { - const auto &components = compound->componentReferenceSystems(); - if (!components.empty()) { - horiz = components.front().get(); - } - } - - if (!bEllipsoidParametersDone) { - auto targetGeodCRS = horiz->extractGeodeticCRS(); - auto targetGeogCRS = - std::dynamic_pointer_cast<crs::GeographicCRS>(targetGeodCRS); - if (targetGeogCRS) { - if (formatter->getCRSExport()) { - targetGeogCRS->addDatumInfoToPROJString(formatter); - } else { - targetGeogCRS->ellipsoid()->_exportToPROJString(formatter); - targetGeogCRS->primeMeridian()->_exportToPROJString( - formatter); - } - } else if (targetGeodCRS) { - targetGeodCRS->ellipsoid()->_exportToPROJString(formatter); - } - } - - auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(horiz); - if (projCRS) { - formatter->pushOmitZUnitConversion(); - projCRS->addUnitConvertAndAxisSwap(formatter, bAxisSpecFound); - formatter->popOmitZUnitConversion(); - } - - auto derivedGeographicCRS = - dynamic_cast<const crs::DerivedGeographicCRS *>(horiz); - if (!formatter->getCRSExport() && derivedGeographicCRS) { - formatter->setOmitProjLongLatIfPossible(true); - derivedGeographicCRS->addAngularUnitConvertAndAxisSwap(formatter); - formatter->setOmitProjLongLatIfPossible(false); - } - } -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Return whether a conversion is a [Universal Transverse Mercator] - * (https://proj.org/operations/projections/utm.html) conversion. - * - * @param[out] zone UTM zone number between 1 and 60. - * @param[out] north true for UTM northern hemisphere, false for UTM southern - * hemisphere. - * @return true if it is a UTM conversion. - */ -bool Conversion::isUTM(int &zone, bool &north) const { - zone = 0; - north = true; - - if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { - // Check for UTM - - bool bLatitudeNatOriginUTM = false; - bool bScaleFactorUTM = false; - bool bFalseEastingUTM = false; - bool bFalseNorthingUTM = false; - for (const auto &genOpParamvalue : parameterValues()) { - auto opParamvalue = dynamic_cast<const OperationParameterValue *>( - genOpParamvalue.get()); - if (opParamvalue) { - const auto epsg_code = opParamvalue->parameter()->getEPSGCode(); - const auto &l_parameterValue = opParamvalue->parameterValue(); - if (l_parameterValue->type() == ParameterValue::Type::MEASURE) { - const auto &measure = l_parameterValue->value(); - if (epsg_code == - EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN && - std::fabs(measure.value() - - UTM_LATITUDE_OF_NATURAL_ORIGIN) < 1e-10) { - bLatitudeNatOriginUTM = true; - } else if ( - (epsg_code == - EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN || - epsg_code == - EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN) && - measure.unit()._isEquivalentTo( - common::UnitOfMeasure::DEGREE, - util::IComparable::Criterion::EQUIVALENT)) { - double dfZone = (measure.value() + 183.0) / 6.0; - if (dfZone > 0.9 && dfZone < 60.1 && - std::abs(dfZone - std::round(dfZone)) < 1e-10) { - zone = static_cast<int>(std::lround(dfZone)); - } - } else if ( - epsg_code == - EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN && - measure.unit()._isEquivalentTo( - common::UnitOfMeasure::SCALE_UNITY, - util::IComparable::Criterion::EQUIVALENT) && - std::fabs(measure.value() - UTM_SCALE_FACTOR) < 1e-10) { - bScaleFactorUTM = true; - } else if (epsg_code == EPSG_CODE_PARAMETER_FALSE_EASTING && - measure.value() == UTM_FALSE_EASTING && - measure.unit()._isEquivalentTo( - common::UnitOfMeasure::METRE, - util::IComparable::Criterion::EQUIVALENT)) { - bFalseEastingUTM = true; - } else if (epsg_code == - EPSG_CODE_PARAMETER_FALSE_NORTHING && - measure.unit()._isEquivalentTo( - common::UnitOfMeasure::METRE, - util::IComparable::Criterion::EQUIVALENT)) { - if (std::fabs(measure.value() - - UTM_NORTH_FALSE_NORTHING) < 1e-10) { - bFalseNorthingUTM = true; - north = true; - } else if (std::fabs(measure.value() - - UTM_SOUTH_FALSE_NORTHING) < - 1e-10) { - bFalseNorthingUTM = true; - north = false; - } - } - } - } - } - if (bLatitudeNatOriginUTM && zone > 0 && bScaleFactorUTM && - bFalseEastingUTM && bFalseNorthingUTM) { - return true; - } - } - return false; -} - -// --------------------------------------------------------------------------- - -/** \brief Return a Conversion object where some parameters are better - * identified. - * - * @return a new Conversion. - */ -ConversionNNPtr Conversion::identify() const { - auto newConversion = Conversion::nn_make_shared<Conversion>(*this); - newConversion->assignSelf(newConversion); - - if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { - // Check for UTM - int zone = 0; - bool north = true; - if (isUTM(zone, north)) { - newConversion->setProperties( - getUTMConversionProperty(util::PropertyMap(), zone, north)); - } - } - - return newConversion; -} - -//! @cond Doxygen_Suppress -// --------------------------------------------------------------------------- - -InvalidOperation::InvalidOperation(const char *message) : Exception(message) {} - -// --------------------------------------------------------------------------- - -InvalidOperation::InvalidOperation(const std::string &message) - : Exception(message) {} - -// --------------------------------------------------------------------------- - -InvalidOperation::InvalidOperation(const InvalidOperation &) = default; - -// --------------------------------------------------------------------------- - -InvalidOperation::~InvalidOperation() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct Transformation::Private { - - TransformationPtr forwardOperation_{}; - - static TransformationNNPtr registerInv(const Transformation *thisIn, - TransformationNNPtr invTransform); -}; -//! @endcond - -// --------------------------------------------------------------------------- - -Transformation::Transformation( - const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, - const crs::CRSPtr &interpolationCRSIn, const OperationMethodNNPtr &methodIn, - const std::vector<GeneralParameterValueNNPtr> &values, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) - : SingleOperation(methodIn), d(internal::make_unique<Private>()) { - setParameterValues(values); - setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn); - setAccuracies(accuracies); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -Transformation::~Transformation() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -Transformation::Transformation(const Transformation &other) - : CoordinateOperation(other), SingleOperation(other), - d(internal::make_unique<Private>(*other.d)) {} - -// --------------------------------------------------------------------------- - -/** \brief Return the source crs::CRS of the transformation. - * - * @return the source CRS. - */ -const crs::CRSNNPtr &Transformation::sourceCRS() PROJ_PURE_DEFN { - return CoordinateOperation::getPrivate()->strongRef_->sourceCRS_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the target crs::CRS of the transformation. - * - * @return the target CRS. - */ -const crs::CRSNNPtr &Transformation::targetCRS() PROJ_PURE_DEFN { - return CoordinateOperation::getPrivate()->strongRef_->targetCRS_; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -TransformationNNPtr Transformation::shallowClone() const { - auto transf = Transformation::nn_make_shared<Transformation>(*this); - transf->assignSelf(transf); - transf->setCRSs(this, false); - if (transf->d->forwardOperation_) { - transf->d->forwardOperation_ = - transf->d->forwardOperation_->shallowClone().as_nullable(); - } - return transf; -} - -CoordinateOperationNNPtr Transformation::_shallowClone() const { - return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone()); -} - -// --------------------------------------------------------------------------- - -TransformationNNPtr -Transformation::promoteTo3D(const std::string &, - const io::DatabaseContextPtr &dbContext) const { - auto transf = shallowClone(); - transf->setCRSs(sourceCRS()->promoteTo3D(std::string(), dbContext), - targetCRS()->promoteTo3D(std::string(), dbContext), - interpolationCRS()); - return transf; -} - -// --------------------------------------------------------------------------- - -TransformationNNPtr -Transformation::demoteTo2D(const std::string &, - const io::DatabaseContextPtr &dbContext) const { - auto transf = shallowClone(); - transf->setCRSs(sourceCRS()->demoteTo2D(std::string(), dbContext), - targetCRS()->demoteTo2D(std::string(), dbContext), - interpolationCRS()); - return transf; -} - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -/** \brief Return the TOWGS84 parameters of the transformation. - * - * If this transformation uses Coordinate Frame Rotation, Position Vector - * transformation or Geocentric translations, a vector of 7 double values - * using the Position Vector convention (EPSG:9606) is returned. Those values - * can be used as the value of the WKT1 TOWGS84 parameter or - * PROJ +towgs84 parameter. - * - * @return a vector of 7 values if valid, otherwise a io::FormattingException - * is thrown. - * @throws io::FormattingException - */ -std::vector<double> -Transformation::getTOWGS84Parameters() const // throw(io::FormattingException) -{ - // GDAL WKT1 assumes EPSG:9606 / Position Vector convention - - bool sevenParamsTransform = false; - bool threeParamsTransform = false; - bool invertRotSigns = false; - const auto &l_method = method(); - const auto &methodName = l_method->nameStr(); - const int methodEPSGCode = l_method->getEPSGCode(); - const auto paramCount = parameterValues().size(); - if ((paramCount == 7 && - ci_find(methodName, "Coordinate Frame") != std::string::npos) || - methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || - methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || - methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) { - sevenParamsTransform = true; - invertRotSigns = true; - } else if ((paramCount == 7 && - ci_find(methodName, "Position Vector") != std::string::npos) || - methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || - methodEPSGCode == - EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || - methodEPSGCode == - EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) { - sevenParamsTransform = true; - invertRotSigns = false; - } else if ((paramCount == 3 && - ci_find(methodName, "Geocentric translations") != - std::string::npos) || - methodEPSGCode == - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || - methodEPSGCode == - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || - methodEPSGCode == - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) { - threeParamsTransform = true; - } - - if (threeParamsTransform || sevenParamsTransform) { - std::vector<double> params(7, 0.0); - bool foundX = false; - bool foundY = false; - bool foundZ = false; - bool foundRotX = false; - bool foundRotY = false; - bool foundRotZ = false; - bool foundScale = false; - const double rotSign = invertRotSigns ? -1.0 : 1.0; - - const auto fixNegativeZero = [](double x) { - if (x == 0.0) - return 0.0; - return x; - }; - - for (const auto &genOpParamvalue : parameterValues()) { - auto opParamvalue = dynamic_cast<const OperationParameterValue *>( - genOpParamvalue.get()); - if (opParamvalue) { - const auto ¶meter = opParamvalue->parameter(); - const auto epsg_code = parameter->getEPSGCode(); - const auto &l_parameterValue = opParamvalue->parameterValue(); - if (l_parameterValue->type() == ParameterValue::Type::MEASURE) { - const auto &measure = l_parameterValue->value(); - if (epsg_code == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) { - params[0] = measure.getSIValue(); - foundX = true; - } else if (epsg_code == - EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) { - params[1] = measure.getSIValue(); - foundY = true; - } else if (epsg_code == - EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) { - params[2] = measure.getSIValue(); - foundZ = true; - } else if (epsg_code == - EPSG_CODE_PARAMETER_X_AXIS_ROTATION) { - params[3] = fixNegativeZero( - rotSign * - measure.convertToUnit( - common::UnitOfMeasure::ARC_SECOND)); - foundRotX = true; - } else if (epsg_code == - EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) { - params[4] = fixNegativeZero( - rotSign * - measure.convertToUnit( - common::UnitOfMeasure::ARC_SECOND)); - foundRotY = true; - } else if (epsg_code == - EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) { - params[5] = fixNegativeZero( - rotSign * - measure.convertToUnit( - common::UnitOfMeasure::ARC_SECOND)); - foundRotZ = true; - } else if (epsg_code == - EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) { - params[6] = measure.convertToUnit( - common::UnitOfMeasure::PARTS_PER_MILLION); - foundScale = true; - } - } - } - } - if (foundX && foundY && foundZ && - (threeParamsTransform || - (foundRotX && foundRotY && foundRotZ && foundScale))) { - return params; - } else { - throw io::FormattingException( - "Missing required parameter values in transformation"); - } - } - -#if 0 - if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS || - methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) { - auto offsetLat = - parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); - auto offsetLong = - parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); - - auto offsetHeight = - parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); - - if (offsetLat.getSIValue() == 0.0 && offsetLong.getSIValue() == 0.0 && - offsetHeight.getSIValue() == 0.0) { - std::vector<double> params(7, 0.0); - return params; - } - } -#endif - - throw io::FormattingException( - "Transformation cannot be formatted as WKT1 TOWGS84 parameters"); -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation from a vector of GeneralParameterValue. - * - * @param properties See \ref general_properties. At minimum the name should be - * defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param interpolationCRSIn Interpolation CRS (might be null) - * @param methodIn Operation method. - * @param values Vector of GeneralOperationParameterNNPtr. - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - * @throws InvalidOperation - */ -TransformationNNPtr Transformation::create( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn, - const OperationMethodNNPtr &methodIn, - const std::vector<GeneralParameterValueNNPtr> &values, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - if (methodIn->parameters().size() != values.size()) { - throw InvalidOperation( - "Inconsistent number of parameters and parameter values"); - } - auto transf = Transformation::nn_make_shared<Transformation>( - sourceCRSIn, targetCRSIn, interpolationCRSIn, methodIn, values, - accuracies); - transf->assignSelf(transf); - transf->setProperties(properties); - std::string name; - if (properties.getStringValue(common::IdentifiedObject::NAME_KEY, name) && - ci_find(name, "ballpark") != std::string::npos) { - transf->setHasBallparkTransformation(true); - } - return transf; -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation ands its OperationMethod. - * - * @param propertiesTransformation The \ref general_properties of the - * Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param interpolationCRSIn Interpolation CRS (might be null) - * @param propertiesOperationMethod The \ref general_properties of the - * OperationMethod. - * At minimum the name should be defined. - * @param parameters Vector of parameters of the operation method. - * @param values Vector of ParameterValueNNPtr. Constraint: - * values.size() == parameters.size() - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - * @throws InvalidOperation - */ -TransformationNNPtr -Transformation::create(const util::PropertyMap &propertiesTransformation, - const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, - const crs::CRSPtr &interpolationCRSIn, - const util::PropertyMap &propertiesOperationMethod, - const std::vector<OperationParameterNNPtr> ¶meters, - const std::vector<ParameterValueNNPtr> &values, - const std::vector<metadata::PositionalAccuracyNNPtr> - &accuracies) // throw InvalidOperation -{ - OperationMethodNNPtr op( - OperationMethod::create(propertiesOperationMethod, parameters)); - - if (parameters.size() != values.size()) { - throw InvalidOperation( - "Inconsistent number of parameters and parameter values"); - } - std::vector<GeneralParameterValueNNPtr> generalParameterValues; - generalParameterValues.reserve(values.size()); - for (size_t i = 0; i < values.size(); i++) { - generalParameterValues.push_back( - OperationParameterValue::create(parameters[i], values[i])); - } - return create(propertiesTransformation, sourceCRSIn, targetCRSIn, - interpolationCRSIn, op, generalParameterValues, accuracies); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -// --------------------------------------------------------------------------- - -static TransformationNNPtr createSevenParamsTransform( - const util::PropertyMap &properties, - const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, double translationXMetre, - double translationYMetre, double translationZMetre, - double rotationXArcSecond, double rotationYArcSecond, - double rotationZArcSecond, double scaleDifferencePPM, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - return Transformation::create( - properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties, - VectorOfParameters{ - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE), - }, - createParams(common::Length(translationXMetre), - common::Length(translationYMetre), - common::Length(translationZMetre), - common::Angle(rotationXArcSecond, - common::UnitOfMeasure::ARC_SECOND), - common::Angle(rotationYArcSecond, - common::UnitOfMeasure::ARC_SECOND), - common::Angle(rotationZArcSecond, - common::UnitOfMeasure::ARC_SECOND), - common::Scale(scaleDifferencePPM, - common::UnitOfMeasure::PARTS_PER_MILLION)), - accuracies); -} - -// --------------------------------------------------------------------------- - -static void getTransformationType(const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, - bool &isGeocentric, bool &isGeog2D, - bool &isGeog3D) { - auto sourceCRSGeod = - dynamic_cast<const crs::GeodeticCRS *>(sourceCRSIn.get()); - auto targetCRSGeod = - dynamic_cast<const crs::GeodeticCRS *>(targetCRSIn.get()); - isGeocentric = sourceCRSGeod && sourceCRSGeod->isGeocentric() && - targetCRSGeod && targetCRSGeod->isGeocentric(); - if (isGeocentric) { - isGeog2D = false; - isGeog3D = false; - return; - } - isGeocentric = false; - - auto sourceCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(sourceCRSIn.get()); - auto targetCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(targetCRSIn.get()); - if (!sourceCRSGeog || !targetCRSGeog) { - throw InvalidOperation("Inconsistent CRS type"); - } - const auto nSrcAxisCount = - sourceCRSGeog->coordinateSystem()->axisList().size(); - const auto nTargetAxisCount = - targetCRSGeog->coordinateSystem()->axisList().size(); - isGeog2D = nSrcAxisCount == 2 && nTargetAxisCount == 2; - isGeog3D = !isGeog2D && nSrcAxisCount >= 2 && nTargetAxisCount >= 2; -} - -// --------------------------------------------------------------------------- - -static int -useOperationMethodEPSGCodeIfPresent(const util::PropertyMap &properties, - int nDefaultOperationMethodEPSGCode) { - const auto *operationMethodEPSGCode = - properties.get("OPERATION_METHOD_EPSG_CODE"); - if (operationMethodEPSGCode) { - const auto boxedValue = dynamic_cast<const util::BoxedValue *>( - (*operationMethodEPSGCode).get()); - if (boxedValue && - boxedValue->type() == util::BoxedValue::Type::INTEGER) { - return boxedValue->integerValue(); - } - } - return nDefaultOperationMethodEPSGCode; -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation with Geocentric Translations method. - * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param translationXMetre Value of the Translation_X parameter (in metre). - * @param translationYMetre Value of the Translation_Y parameter (in metre). - * @param translationZMetre Value of the Translation_Z parameter (in metre). - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - */ -TransformationNNPtr Transformation::createGeocentricTranslations( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, double translationXMetre, - double translationYMetre, double translationZMetre, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - bool isGeocentric; - bool isGeog2D; - bool isGeog3D; - getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, - isGeog3D); - return create( - properties, sourceCRSIn, targetCRSIn, nullptr, - createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( - properties, - isGeocentric - ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC - : isGeog2D - ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D - : EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D)), - VectorOfParameters{ - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), - }, - createParams(common::Length(translationXMetre), - common::Length(translationYMetre), - common::Length(translationZMetre)), - accuracies); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation with Position vector transformation - * method. - * - * This is similar to createCoordinateFrameRotation(), except that the sign of - * the rotation terms is inverted. - * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param translationXMetre Value of the Translation_X parameter (in metre). - * @param translationYMetre Value of the Translation_Y parameter (in metre). - * @param translationZMetre Value of the Translation_Z parameter (in metre). - * @param rotationXArcSecond Value of the Rotation_X parameter (in - * arc-second). - * @param rotationYArcSecond Value of the Rotation_Y parameter (in - * arc-second). - * @param rotationZArcSecond Value of the Rotation_Z parameter (in - * arc-second). - * @param scaleDifferencePPM Value of the Scale_Difference parameter (in - * parts-per-million). - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - */ -TransformationNNPtr Transformation::createPositionVector( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, double translationXMetre, - double translationYMetre, double translationZMetre, - double rotationXArcSecond, double rotationYArcSecond, - double rotationZArcSecond, double scaleDifferencePPM, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - - bool isGeocentric; - bool isGeog2D; - bool isGeog3D; - getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, - isGeog3D); - return createSevenParamsTransform( - properties, - createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( - properties, - isGeocentric - ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC - : isGeog2D ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D - : EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D)), - sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, - translationZMetre, rotationXArcSecond, rotationYArcSecond, - rotationZArcSecond, scaleDifferencePPM, accuracies); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation with Coordinate Frame Rotation method. - * - * This is similar to createPositionVector(), except that the sign of - * the rotation terms is inverted. - * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param translationXMetre Value of the Translation_X parameter (in metre). - * @param translationYMetre Value of the Translation_Y parameter (in metre). - * @param translationZMetre Value of the Translation_Z parameter (in metre). - * @param rotationXArcSecond Value of the Rotation_X parameter (in - * arc-second). - * @param rotationYArcSecond Value of the Rotation_Y parameter (in - * arc-second). - * @param rotationZArcSecond Value of the Rotation_Z parameter (in - * arc-second). - * @param scaleDifferencePPM Value of the Scale_Difference parameter (in - * parts-per-million). - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - */ -TransformationNNPtr Transformation::createCoordinateFrameRotation( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, double translationXMetre, - double translationYMetre, double translationZMetre, - double rotationXArcSecond, double rotationYArcSecond, - double rotationZArcSecond, double scaleDifferencePPM, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - bool isGeocentric; - bool isGeog2D; - bool isGeog3D; - getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, - isGeog3D); - return createSevenParamsTransform( - properties, - createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( - properties, - isGeocentric - ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC - : isGeog2D ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D - : EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D)), - sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, - translationZMetre, rotationXArcSecond, rotationYArcSecond, - rotationZArcSecond, scaleDifferencePPM, accuracies); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static TransformationNNPtr createFifteenParamsTransform( - const util::PropertyMap &properties, - const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, double translationXMetre, - double translationYMetre, double translationZMetre, - double rotationXArcSecond, double rotationYArcSecond, - double rotationZArcSecond, double scaleDifferencePPM, - double rateTranslationX, double rateTranslationY, double rateTranslationZ, - double rateRotationX, double rateRotationY, double rateRotationZ, - double rateScaleDifference, double referenceEpochYear, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - return Transformation::create( - properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties, - VectorOfParameters{ - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE), - - createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION), - createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION), - createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION), - createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE), - - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_REFERENCE_EPOCH), - }, - VectorOfValues{ - common::Length(translationXMetre), - common::Length(translationYMetre), - common::Length(translationZMetre), - common::Angle(rotationXArcSecond, - common::UnitOfMeasure::ARC_SECOND), - common::Angle(rotationYArcSecond, - common::UnitOfMeasure::ARC_SECOND), - common::Angle(rotationZArcSecond, - common::UnitOfMeasure::ARC_SECOND), - common::Scale(scaleDifferencePPM, - common::UnitOfMeasure::PARTS_PER_MILLION), - common::Measure(rateTranslationX, - common::UnitOfMeasure::METRE_PER_YEAR), - common::Measure(rateTranslationY, - common::UnitOfMeasure::METRE_PER_YEAR), - common::Measure(rateTranslationZ, - common::UnitOfMeasure::METRE_PER_YEAR), - common::Measure(rateRotationX, - common::UnitOfMeasure::ARC_SECOND_PER_YEAR), - common::Measure(rateRotationY, - common::UnitOfMeasure::ARC_SECOND_PER_YEAR), - common::Measure(rateRotationZ, - common::UnitOfMeasure::ARC_SECOND_PER_YEAR), - common::Measure(rateScaleDifference, - common::UnitOfMeasure::PPM_PER_YEAR), - common::Measure(referenceEpochYear, common::UnitOfMeasure::YEAR), - }, - accuracies); -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation with Time Dependent position vector - * transformation method. - * - * This is similar to createTimeDependentCoordinateFrameRotation(), except that - * the sign of - * the rotation terms is inverted. - * - * This method is defined as [EPSG:1053] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1053) - * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param translationXMetre Value of the Translation_X parameter (in metre). - * @param translationYMetre Value of the Translation_Y parameter (in metre). - * @param translationZMetre Value of the Translation_Z parameter (in metre). - * @param rotationXArcSecond Value of the Rotation_X parameter (in - * arc-second). - * @param rotationYArcSecond Value of the Rotation_Y parameter (in - * arc-second). - * @param rotationZArcSecond Value of the Rotation_Z parameter (in - * arc-second). - * @param scaleDifferencePPM Value of the Scale_Difference parameter (in - * parts-per-million). - * @param rateTranslationX Value of the rate of change of X-axis translation (in - * metre/year) - * @param rateTranslationY Value of the rate of change of Y-axis translation (in - * metre/year) - * @param rateTranslationZ Value of the rate of change of Z-axis translation (in - * metre/year) - * @param rateRotationX Value of the rate of change of X-axis rotation (in - * arc-second/year) - * @param rateRotationY Value of the rate of change of Y-axis rotation (in - * arc-second/year) - * @param rateRotationZ Value of the rate of change of Z-axis rotation (in - * arc-second/year) - * @param rateScaleDifference Value of the rate of change of scale difference - * (in PPM/year) - * @param referenceEpochYear Parameter reference epoch (in decimal year) - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - */ -TransformationNNPtr Transformation::createTimeDependentPositionVector( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, double translationXMetre, - double translationYMetre, double translationZMetre, - double rotationXArcSecond, double rotationYArcSecond, - double rotationZArcSecond, double scaleDifferencePPM, - double rateTranslationX, double rateTranslationY, double rateTranslationZ, - double rateRotationX, double rateRotationY, double rateRotationZ, - double rateScaleDifference, double referenceEpochYear, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - bool isGeocentric; - bool isGeog2D; - bool isGeog3D; - getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, - isGeog3D); - return createFifteenParamsTransform( - properties, - createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( - properties, - isGeocentric - ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC - : isGeog2D - ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D - : EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D)), - sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, - translationZMetre, rotationXArcSecond, rotationYArcSecond, - rotationZArcSecond, scaleDifferencePPM, rateTranslationX, - rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY, - rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation with Time Dependent Position coordinate - * frame rotation transformation method. - * - * This is similar to createTimeDependentPositionVector(), except that the sign - * of - * the rotation terms is inverted. - * - * This method is defined as [EPSG:1056] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1056) - * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param translationXMetre Value of the Translation_X parameter (in metre). - * @param translationYMetre Value of the Translation_Y parameter (in metre). - * @param translationZMetre Value of the Translation_Z parameter (in metre). - * @param rotationXArcSecond Value of the Rotation_X parameter (in - * arc-second). - * @param rotationYArcSecond Value of the Rotation_Y parameter (in - * arc-second). - * @param rotationZArcSecond Value of the Rotation_Z parameter (in - * arc-second). - * @param scaleDifferencePPM Value of the Scale_Difference parameter (in - * parts-per-million). - * @param rateTranslationX Value of the rate of change of X-axis translation (in - * metre/year) - * @param rateTranslationY Value of the rate of change of Y-axis translation (in - * metre/year) - * @param rateTranslationZ Value of the rate of change of Z-axis translation (in - * metre/year) - * @param rateRotationX Value of the rate of change of X-axis rotation (in - * arc-second/year) - * @param rateRotationY Value of the rate of change of Y-axis rotation (in - * arc-second/year) - * @param rateRotationZ Value of the rate of change of Z-axis rotation (in - * arc-second/year) - * @param rateScaleDifference Value of the rate of change of scale difference - * (in PPM/year) - * @param referenceEpochYear Parameter reference epoch (in decimal year) - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - * @throws InvalidOperation - */ -TransformationNNPtr Transformation::createTimeDependentCoordinateFrameRotation( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, double translationXMetre, - double translationYMetre, double translationZMetre, - double rotationXArcSecond, double rotationYArcSecond, - double rotationZArcSecond, double scaleDifferencePPM, - double rateTranslationX, double rateTranslationY, double rateTranslationZ, - double rateRotationX, double rateRotationY, double rateRotationZ, - double rateScaleDifference, double referenceEpochYear, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - - bool isGeocentric; - bool isGeog2D; - bool isGeog3D; - getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, - isGeog3D); - return createFifteenParamsTransform( - properties, - createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( - properties, - isGeocentric - ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC - : isGeog2D - ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D - : EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D)), - sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, - translationZMetre, rotationXArcSecond, rotationYArcSecond, - rotationZArcSecond, scaleDifferencePPM, rateTranslationX, - rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY, - rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static TransformationNNPtr _createMolodensky( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, int methodEPSGCode, - double translationXMetre, double translationYMetre, - double translationZMetre, double semiMajorAxisDifferenceMetre, - double flattingDifference, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - return Transformation::create( - properties, sourceCRSIn, targetCRSIn, nullptr, - createMethodMapNameEPSGCode(methodEPSGCode), - VectorOfParameters{ - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), - createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE), - createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE), - }, - createParams( - common::Length(translationXMetre), - common::Length(translationYMetre), - common::Length(translationZMetre), - common::Length(semiMajorAxisDifferenceMetre), - common::Measure(flattingDifference, common::UnitOfMeasure::NONE)), - accuracies); -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation with Molodensky method. - * - * @see createAbridgedMolodensky() for a related method. - * - * This method is defined as [EPSG:9604] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9604) - * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param translationXMetre Value of the Translation_X parameter (in metre). - * @param translationYMetre Value of the Translation_Y parameter (in metre). - * @param translationZMetre Value of the Translation_Z parameter (in metre). - * @param semiMajorAxisDifferenceMetre The difference between the semi-major - * axis values of the ellipsoids used in the target and source CRS (in metre). - * @param flattingDifference The difference between the flattening values of - * the ellipsoids used in the target and source CRS. - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - * @throws InvalidOperation - */ -TransformationNNPtr Transformation::createMolodensky( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, double translationXMetre, - double translationYMetre, double translationZMetre, - double semiMajorAxisDifferenceMetre, double flattingDifference, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - return _createMolodensky( - properties, sourceCRSIn, targetCRSIn, EPSG_CODE_METHOD_MOLODENSKY, - translationXMetre, translationYMetre, translationZMetre, - semiMajorAxisDifferenceMetre, flattingDifference, accuracies); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation with Abridged Molodensky method. - * - * @see createdMolodensky() for a related method. - * - * This method is defined as [EPSG:9605] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9605) - * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param translationXMetre Value of the Translation_X parameter (in metre). - * @param translationYMetre Value of the Translation_Y parameter (in metre). - * @param translationZMetre Value of the Translation_Z parameter (in metre). - * @param semiMajorAxisDifferenceMetre The difference between the semi-major - * axis values of the ellipsoids used in the target and source CRS (in metre). - * @param flattingDifference The difference between the flattening values of - * the ellipsoids used in the target and source CRS. - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - * @throws InvalidOperation - */ -TransformationNNPtr Transformation::createAbridgedMolodensky( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, double translationXMetre, - double translationYMetre, double translationZMetre, - double semiMajorAxisDifferenceMetre, double flattingDifference, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - return _createMolodensky(properties, sourceCRSIn, targetCRSIn, - EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY, - translationXMetre, translationYMetre, - translationZMetre, semiMajorAxisDifferenceMetre, - flattingDifference, accuracies); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation from TOWGS84 parameters. - * - * This is a helper of createPositionVector() with the source CRS being the - * GeographicCRS of sourceCRSIn, and the target CRS being EPSG:4326 - * - * @param sourceCRSIn Source CRS. - * @param TOWGS84Parameters The vector of 3 double values (Translation_X,_Y,_Z) - * or 7 double values (Translation_X,_Y,_Z, Rotation_X,_Y,_Z, Scale_Difference) - * passed to createPositionVector() - * @return new Transformation. - * @throws InvalidOperation - */ -TransformationNNPtr Transformation::createTOWGS84( - const crs::CRSNNPtr &sourceCRSIn, - const std::vector<double> &TOWGS84Parameters) // throw InvalidOperation -{ - if (TOWGS84Parameters.size() != 3 && TOWGS84Parameters.size() != 7) { - throw InvalidOperation( - "Invalid number of elements in TOWGS84Parameters"); - } - - crs::CRSPtr transformSourceCRS = sourceCRSIn->extractGeodeticCRS(); - if (!transformSourceCRS) { - throw InvalidOperation( - "Cannot find GeodeticCRS in sourceCRS of TOWGS84 transformation"); - } - - util::PropertyMap properties; - properties.set(common::IdentifiedObject::NAME_KEY, - concat("Transformation from ", transformSourceCRS->nameStr(), - " to WGS84")); - - auto targetCRS = - dynamic_cast<const crs::GeographicCRS *>(transformSourceCRS.get()) - ? util::nn_static_pointer_cast<crs::CRS>( - crs::GeographicCRS::EPSG_4326) - : util::nn_static_pointer_cast<crs::CRS>( - crs::GeodeticCRS::EPSG_4978); - - if (TOWGS84Parameters.size() == 3) { - return createGeocentricTranslations( - properties, NN_NO_CHECK(transformSourceCRS), targetCRS, - TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2], - {}); - } - - return createPositionVector(properties, NN_NO_CHECK(transformSourceCRS), - targetCRS, TOWGS84Parameters[0], - TOWGS84Parameters[1], TOWGS84Parameters[2], - TOWGS84Parameters[3], TOWGS84Parameters[4], - TOWGS84Parameters[5], TOWGS84Parameters[6], {}); -} - -// --------------------------------------------------------------------------- -/** \brief Instantiate a transformation with NTv2 method. - * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param filename NTv2 filename. - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - */ -TransformationNNPtr Transformation::createNTv2( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, const std::string &filename, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - - return create(properties, sourceCRSIn, targetCRSIn, nullptr, - createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV2), - VectorOfParameters{createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}, - VectorOfValues{ParameterValue::createFilename(filename)}, - accuracies); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static TransformationNNPtr _createGravityRelatedHeightToGeographic3D( - const util::PropertyMap &properties, bool inverse, - const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, - const crs::CRSPtr &interpolationCRSIn, const std::string &filename, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - - return Transformation::create( - properties, sourceCRSIn, targetCRSIn, interpolationCRSIn, - util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - inverse ? INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D - : PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D), - VectorOfParameters{createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)}, - VectorOfValues{ParameterValue::createFilename(filename)}, accuracies); -} -//! @endcond - -// --------------------------------------------------------------------------- -/** \brief Instantiate a transformation from GravityRelatedHeight to - * Geographic3D - * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param interpolationCRSIn Interpolation CRS. (might be null) - * @param filename GRID filename. - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - */ -TransformationNNPtr Transformation::createGravityRelatedHeightToGeographic3D( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn, - const std::string &filename, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - - return _createGravityRelatedHeightToGeographic3D( - properties, false, sourceCRSIn, targetCRSIn, interpolationCRSIn, - filename, accuracies); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation with method VERTCON - * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param filename GRID filename. - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - */ -TransformationNNPtr Transformation::createVERTCON( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, const std::string &filename, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - - return create(properties, sourceCRSIn, targetCRSIn, nullptr, - createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTCON), - VectorOfParameters{createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)}, - VectorOfValues{ParameterValue::createFilename(filename)}, - accuracies); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static inline std::vector<metadata::PositionalAccuracyNNPtr> -buildAccuracyZero() { - return std::vector<metadata::PositionalAccuracyNNPtr>{ - metadata::PositionalAccuracy::create("0")}; -} - -// --------------------------------------------------------------------------- - -//! @endcond - -/** \brief Instantiate a transformation with method Longitude rotation - * - * This method is defined as [EPSG:9601] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9601) - * * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param offset Longitude offset to add. - * @return new Transformation. - */ -TransformationNNPtr Transformation::createLongitudeRotation( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, const common::Angle &offset) { - - return create( - properties, sourceCRSIn, targetCRSIn, nullptr, - createMethodMapNameEPSGCode(EPSG_CODE_METHOD_LONGITUDE_ROTATION), - VectorOfParameters{ - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)}, - VectorOfValues{ParameterValue::create(offset)}, buildAccuracyZero()); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -bool Transformation::isLongitudeRotation() const { - return method()->getEPSGCode() == EPSG_CODE_METHOD_LONGITUDE_ROTATION; -} - -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation with method Geographic 2D offsets - * - * This method is defined as [EPSG:9619] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9619) - * * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param offsetLat Latitude offset to add. - * @param offsetLon Longitude offset to add. - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - */ -TransformationNNPtr Transformation::createGeographic2DOffsets( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, - const common::Angle &offsetLon, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - return create( - properties, sourceCRSIn, targetCRSIn, nullptr, - createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS), - VectorOfParameters{ - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)}, - VectorOfValues{offsetLat, offsetLon}, accuracies); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation with method Geographic 3D offsets - * - * This method is defined as [EPSG:9660] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9660) - * * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param offsetLat Latitude offset to add. - * @param offsetLon Longitude offset to add. - * @param offsetHeight Height offset to add. - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - */ -TransformationNNPtr Transformation::createGeographic3DOffsets( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, - const common::Angle &offsetLon, const common::Length &offsetHeight, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - return create( - properties, sourceCRSIn, targetCRSIn, nullptr, - createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS), - VectorOfParameters{ - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_VERTICAL_OFFSET)}, - VectorOfValues{offsetLat, offsetLon, offsetHeight}, accuracies); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation with method Geographic 2D with - * height - * offsets - * - * This method is defined as [EPSG:9618] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9618) - * * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param offsetLat Latitude offset to add. - * @param offsetLon Longitude offset to add. - * @param offsetHeight Geoid undulation to add. - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - */ -TransformationNNPtr Transformation::createGeographic2DWithHeightOffsets( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, - const common::Angle &offsetLon, const common::Length &offsetHeight, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - return create( - properties, sourceCRSIn, targetCRSIn, nullptr, - createMethodMapNameEPSGCode( - EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS), - VectorOfParameters{ - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET), - createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_GEOID_UNDULATION)}, - VectorOfValues{offsetLat, offsetLon, offsetHeight}, accuracies); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation with method Vertical Offset. - * - * This method is defined as [EPSG:9616] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9616) - * * - * @param properties See \ref general_properties of the Transformation. - * At minimum the name should be defined. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param offsetHeight Geoid undulation to add. - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - */ -TransformationNNPtr Transformation::createVerticalOffset( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, const common::Length &offsetHeight, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - return create(properties, sourceCRSIn, targetCRSIn, nullptr, - createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTICAL_OFFSET), - VectorOfParameters{createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_VERTICAL_OFFSET)}, - VectorOfValues{offsetHeight}, accuracies); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a transformation based on the Change of Vertical Unit - * method. - * - * This method is defined as [EPSG:1069] - * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1069) - * - * @param properties See \ref general_properties of the conversion. If the name - * is not provided, it is automatically set. - * @param sourceCRSIn Source CRS. - * @param targetCRSIn Target CRS. - * @param factor Conversion factor - * @param accuracies Vector of positional accuracy (might be empty). - * @return a new Transformation. - */ -TransformationNNPtr Transformation::createChangeVerticalUnit( - const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, - const crs::CRSNNPtr &targetCRSIn, const common::Scale &factor, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - return create( - properties, sourceCRSIn, targetCRSIn, nullptr, - createMethodMapNameEPSGCode(EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT), - VectorOfParameters{ - createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR), - }, - VectorOfValues{ - factor, - }, - accuracies); -} - -// --------------------------------------------------------------------------- - -static util::PropertyMap -createPropertiesForInverse(const CoordinateOperation *op, bool derivedFrom, - bool approximateInversion) { - assert(op); - util::PropertyMap map; - - // The domain(s) are unchanged by the inverse operation - addDomains(map, op); - - const std::string &forwardName = op->nameStr(); - - // Forge a name for the inverse, either from the forward name, or - // from the source and target CRS names - const char *opType; - if (starts_with(forwardName, BALLPARK_GEOCENTRIC_TRANSLATION)) { - opType = BALLPARK_GEOCENTRIC_TRANSLATION; - } else if (starts_with(forwardName, BALLPARK_GEOGRAPHIC_OFFSET)) { - opType = BALLPARK_GEOGRAPHIC_OFFSET; - } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) { - opType = NULL_GEOGRAPHIC_OFFSET; - } else if (starts_with(forwardName, NULL_GEOCENTRIC_TRANSLATION)) { - opType = NULL_GEOCENTRIC_TRANSLATION; - } else if (dynamic_cast<const Transformation *>(op) || - starts_with(forwardName, "Transformation from ")) { - opType = "Transformation"; - } else if (dynamic_cast<const Conversion *>(op)) { - opType = "Conversion"; - } else { - opType = "Operation"; - } - - auto sourceCRS = op->sourceCRS(); - auto targetCRS = op->targetCRS(); - std::string name; - if (!forwardName.empty()) { - if (dynamic_cast<const Transformation *>(op) == nullptr && - dynamic_cast<const ConcatenatedOperation *>(op) == nullptr && - (starts_with(forwardName, INVERSE_OF) || - forwardName.find(" + ") != std::string::npos)) { - std::vector<std::string> tokens; - std::string curToken; - bool inString = false; - for (size_t i = 0; i < forwardName.size(); ++i) { - if (inString) { - curToken += forwardName[i]; - if (forwardName[i] == '\'') { - inString = false; - } - } else if (i + 3 < forwardName.size() && - memcmp(&forwardName[i], " + ", 3) == 0) { - tokens.push_back(curToken); - curToken.clear(); - i += 2; - } else if (forwardName[i] == '\'') { - inString = true; - curToken += forwardName[i]; - } else { - curToken += forwardName[i]; - } - } - if (!curToken.empty()) { - tokens.push_back(curToken); - } - for (size_t i = tokens.size(); i > 0;) { - i--; - if (!name.empty()) { - name += " + "; - } - if (starts_with(tokens[i], INVERSE_OF)) { - name += tokens[i].substr(INVERSE_OF.size()); - } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME || - tokens[i] == AXIS_ORDER_CHANGE_3D_NAME) { - name += tokens[i]; - } else { - name += INVERSE_OF + tokens[i]; - } - } - } else if (!sourceCRS || !targetCRS || - forwardName != buildOpName(opType, sourceCRS, targetCRS)) { - if (forwardName.find(" + ") != std::string::npos) { - name = INVERSE_OF + '\'' + forwardName + '\''; - } else { - name = INVERSE_OF + forwardName; - } - } - } - if (name.empty() && sourceCRS && targetCRS) { - name = buildOpName(opType, targetCRS, sourceCRS); - } - if (approximateInversion) { - name += " (approx. inversion)"; - } - - if (!name.empty()) { - map.set(common::IdentifiedObject::NAME_KEY, name); - } - - const std::string &remarks = op->remarks(); - if (!remarks.empty()) { - map.set(common::IdentifiedObject::REMARKS_KEY, remarks); - } - - addModifiedIdentifier(map, op, true, derivedFrom); - - const auto so = dynamic_cast<const SingleOperation *>(op); - if (so) { - const int soMethodEPSGCode = so->method()->getEPSGCode(); - if (soMethodEPSGCode > 0) { - map.set("OPERATION_METHOD_EPSG_CODE", soMethodEPSGCode); - } - } - - return map; -} - -// --------------------------------------------------------------------------- - -static bool isTimeDependent(const std::string &methodName) { - return ci_find(methodName, "Time dependent") != std::string::npos || - ci_find(methodName, "Time-dependent") != std::string::npos; -} - -// --------------------------------------------------------------------------- - -// to avoid -0... -static double negate(double val) { - if (val != 0) { - return -val; - } - return 0.0; -} - -// --------------------------------------------------------------------------- - -static CoordinateOperationPtr -createApproximateInverseIfPossible(const Transformation *op) { - bool sevenParamsTransform = false; - bool fifteenParamsTransform = false; - const auto &method = op->method(); - const auto &methodName = method->nameStr(); - const int methodEPSGCode = method->getEPSGCode(); - const auto paramCount = op->parameterValues().size(); - const bool isPositionVector = - ci_find(methodName, "Position Vector") != std::string::npos; - const bool isCoordinateFrame = - ci_find(methodName, "Coordinate Frame") != std::string::npos; - - // See end of "2.4.3.3 Helmert 7-parameter transformations" - // in EPSG 7-2 guidance - // For practical purposes, the inverse of 7- or 15-parameters Helmert - // can be obtained by using the forward method with all parameters - // negated - // (except reference epoch!) - // So for WKT export use that. But for PROJ string, we use the +inv flag - // so as to get "perfect" round-tripability. - if ((paramCount == 7 && isCoordinateFrame && - !isTimeDependent(methodName)) || - methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || - methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || - methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) { - sevenParamsTransform = true; - } else if ( - (paramCount == 15 && isCoordinateFrame && - isTimeDependent(methodName)) || - methodEPSGCode == - EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC || - methodEPSGCode == - EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D || - methodEPSGCode == - EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) { - fifteenParamsTransform = true; - } else if ((paramCount == 7 && isPositionVector && - !isTimeDependent(methodName)) || - methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || - methodEPSGCode == - EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || - methodEPSGCode == - EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) { - sevenParamsTransform = true; - } else if ( - (paramCount == 15 && isPositionVector && isTimeDependent(methodName)) || - methodEPSGCode == - EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC || - methodEPSGCode == - EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D || - methodEPSGCode == - EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) { - fifteenParamsTransform = true; - } - if (sevenParamsTransform || fifteenParamsTransform) { - double neg_x = negate(op->parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION)); - double neg_y = negate(op->parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION)); - double neg_z = negate(op->parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION)); - double neg_rx = negate( - op->parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND)); - double neg_ry = negate( - op->parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND)); - double neg_rz = negate( - op->parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND)); - double neg_scaleDiff = negate(op->parameterValueNumeric( - EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, - common::UnitOfMeasure::PARTS_PER_MILLION)); - auto methodProperties = util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, methodName); - int method_epsg_code = method->getEPSGCode(); - if (method_epsg_code) { - methodProperties - .set(metadata::Identifier::CODESPACE_KEY, - metadata::Identifier::EPSG) - .set(metadata::Identifier::CODE_KEY, method_epsg_code); - } - if (fifteenParamsTransform) { - double neg_rate_x = negate(op->parameterValueNumeric( - EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION, - common::UnitOfMeasure::METRE_PER_YEAR)); - double neg_rate_y = negate(op->parameterValueNumeric( - EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION, - common::UnitOfMeasure::METRE_PER_YEAR)); - double neg_rate_z = negate(op->parameterValueNumeric( - EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION, - common::UnitOfMeasure::METRE_PER_YEAR)); - double neg_rate_rx = negate(op->parameterValueNumeric( - EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND_PER_YEAR)); - double neg_rate_ry = negate(op->parameterValueNumeric( - EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND_PER_YEAR)); - double neg_rate_rz = negate(op->parameterValueNumeric( - EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND_PER_YEAR)); - double neg_rate_scaleDiff = negate(op->parameterValueNumeric( - EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE, - common::UnitOfMeasure::PPM_PER_YEAR)); - double referenceEpochYear = - op->parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH, - common::UnitOfMeasure::YEAR); - return util::nn_static_pointer_cast<CoordinateOperation>( - createFifteenParamsTransform( - createPropertiesForInverse(op, false, true), - methodProperties, op->targetCRS(), op->sourceCRS(), - neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz, - neg_scaleDiff, neg_rate_x, neg_rate_y, neg_rate_z, - neg_rate_rx, neg_rate_ry, neg_rate_rz, - neg_rate_scaleDiff, referenceEpochYear, - op->coordinateOperationAccuracies())) - .as_nullable(); - } else { - return util::nn_static_pointer_cast<CoordinateOperation>( - createSevenParamsTransform( - createPropertiesForInverse(op, false, true), - methodProperties, op->targetCRS(), op->sourceCRS(), - neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz, - neg_scaleDiff, op->coordinateOperationAccuracies())) - .as_nullable(); - } - } - - return nullptr; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -TransformationNNPtr -Transformation::Private::registerInv(const Transformation *thisIn, - TransformationNNPtr invTransform) { - invTransform->d->forwardOperation_ = thisIn->shallowClone().as_nullable(); - invTransform->setHasBallparkTransformation( - thisIn->hasBallparkTransformation()); - return invTransform; -} -//! @endcond - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr Transformation::inverse() const { - return inverseAsTransformation(); -} - -// --------------------------------------------------------------------------- - -TransformationNNPtr Transformation::inverseAsTransformation() const { - - if (d->forwardOperation_) { - return NN_NO_CHECK(d->forwardOperation_); - } - const auto &l_method = method(); - const auto &methodName = l_method->nameStr(); - const int methodEPSGCode = l_method->getEPSGCode(); - const auto &l_sourceCRS = sourceCRS(); - const auto &l_targetCRS = targetCRS(); - - // For geocentric translation, the inverse is exactly the negation of - // the parameters. - if (ci_find(methodName, "Geocentric translations") != std::string::npos || - methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || - methodEPSGCode == - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || - methodEPSGCode == - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) { - double x = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); - double y = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); - double z = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); - auto properties = createPropertiesForInverse(this, false, false); - return Private::registerInv( - this, create(properties, l_targetCRS, l_sourceCRS, nullptr, - createMethodMapNameEPSGCode( - useOperationMethodEPSGCodeIfPresent( - properties, methodEPSGCode)), - VectorOfParameters{ - createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), - createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), - createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), - }, - createParams(common::Length(negate(x)), - common::Length(negate(y)), - common::Length(negate(z))), - coordinateOperationAccuracies())); - } - - if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY || - methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { - double x = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); - double y = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); - double z = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); - double da = parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE); - double df = parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE); - - if (methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { - return Private::registerInv( - this, - createAbridgedMolodensky( - createPropertiesForInverse(this, false, false), l_targetCRS, - l_sourceCRS, negate(x), negate(y), negate(z), negate(da), - negate(df), coordinateOperationAccuracies())); - } else { - return Private::registerInv( - this, - createMolodensky(createPropertiesForInverse(this, false, false), - l_targetCRS, l_sourceCRS, negate(x), negate(y), - negate(z), negate(da), negate(df), - coordinateOperationAccuracies())); - } - } - - if (isLongitudeRotation()) { - auto offset = - parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); - const common::Angle newOffset(negate(offset.value()), offset.unit()); - return Private::registerInv( - this, createLongitudeRotation( - createPropertiesForInverse(this, false, false), - l_targetCRS, l_sourceCRS, newOffset)); - } - - if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) { - auto offsetLat = - parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); - const common::Angle newOffsetLat(negate(offsetLat.value()), - offsetLat.unit()); - - auto offsetLong = - parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); - const common::Angle newOffsetLong(negate(offsetLong.value()), - offsetLong.unit()); - - return Private::registerInv( - this, createGeographic2DOffsets( - createPropertiesForInverse(this, false, false), - l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong, - coordinateOperationAccuracies())); - } - - if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) { - auto offsetLat = - parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); - const common::Angle newOffsetLat(negate(offsetLat.value()), - offsetLat.unit()); - - auto offsetLong = - parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); - const common::Angle newOffsetLong(negate(offsetLong.value()), - offsetLong.unit()); - - auto offsetHeight = - parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); - const common::Length newOffsetHeight(negate(offsetHeight.value()), - offsetHeight.unit()); - - return Private::registerInv( - this, createGeographic3DOffsets( - createPropertiesForInverse(this, false, false), - l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong, - newOffsetHeight, coordinateOperationAccuracies())); - } - - if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) { - auto offsetLat = - parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); - const common::Angle newOffsetLat(negate(offsetLat.value()), - offsetLat.unit()); - - auto offsetLong = - parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); - const common::Angle newOffsetLong(negate(offsetLong.value()), - offsetLong.unit()); - - auto offsetHeight = - parameterValueMeasure(EPSG_CODE_PARAMETER_GEOID_UNDULATION); - const common::Length newOffsetHeight(negate(offsetHeight.value()), - offsetHeight.unit()); - - return Private::registerInv( - this, createGeographic2DWithHeightOffsets( - createPropertiesForInverse(this, false, false), - l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong, - newOffsetHeight, coordinateOperationAccuracies())); - } - - if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) { - - auto offsetHeight = - parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); - const common::Length newOffsetHeight(negate(offsetHeight.value()), - offsetHeight.unit()); - - return Private::registerInv( - this, - createVerticalOffset(createPropertiesForInverse(this, false, false), - l_targetCRS, l_sourceCRS, newOffsetHeight, - coordinateOperationAccuracies())); - } - - if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) { - const double convFactor = parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); - return Private::registerInv( - this, createChangeVerticalUnit( - createPropertiesForInverse(this, false, false), - l_targetCRS, l_sourceCRS, common::Scale(1.0 / convFactor), - coordinateOperationAccuracies())); - } - -#ifdef notdef - // We don't need that currently, but we might... - if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { - return Private::registerInv( - this, - createHeightDepthReversal( - createPropertiesForInverse(this, false, false), l_targetCRS, - l_sourceCRS, coordinateOperationAccuracies())); - } -#endif - - return InverseTransformation::create(NN_NO_CHECK( - util::nn_dynamic_pointer_cast<Transformation>(shared_from_this()))); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -// --------------------------------------------------------------------------- - -InverseTransformation::InverseTransformation(const TransformationNNPtr &forward) - : Transformation( - forward->targetCRS(), forward->sourceCRS(), - forward->interpolationCRS(), - OperationMethod::create(createPropertiesForInverse(forward->method()), - forward->method()->parameters()), - forward->parameterValues(), forward->coordinateOperationAccuracies()), - InverseCoordinateOperation(forward, true) { - setPropertiesFromForward(); -} - -// --------------------------------------------------------------------------- - -InverseTransformation::~InverseTransformation() = default; - -// --------------------------------------------------------------------------- - -TransformationNNPtr -InverseTransformation::create(const TransformationNNPtr &forward) { - auto conv = util::nn_make_shared<InverseTransformation>(forward); - conv->assignSelf(conv); - return conv; -} - -// --------------------------------------------------------------------------- - -TransformationNNPtr InverseTransformation::inverseAsTransformation() const { - return NN_NO_CHECK( - util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_)); -} - -// --------------------------------------------------------------------------- - -void InverseTransformation::_exportToWKT(io::WKTFormatter *formatter) const { - - auto approxInverse = createApproximateInverseIfPossible( - util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_).get()); - if (approxInverse) { - approxInverse->_exportToWKT(formatter); - } else { - Transformation::_exportToWKT(formatter); - } -} - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr InverseTransformation::_shallowClone() const { - auto op = InverseTransformation::nn_make_shared<InverseTransformation>( - inverseAsTransformation()->shallowClone()); - op->assignSelf(op); - op->setCRSs(this, false); - return util::nn_static_pointer_cast<CoordinateOperation>(op); -} - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -void Transformation::_exportToWKT(io::WKTFormatter *formatter) const { - exportTransformationToWKT(formatter); -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -void Transformation::_exportToJSON( - io::JSONFormatter *formatter) const // throw(FormattingException) -{ - auto writer = formatter->writer(); - auto objectContext(formatter->MakeObjectContext( - formatter->abridgedTransformation() ? "AbridgedTransformation" - : "Transformation", - !identifiers().empty())); - - writer->AddObjKey("name"); - auto l_name = nameStr(); - if (l_name.empty()) { - writer->Add("unnamed"); - } else { - writer->Add(l_name); - } - - if (!formatter->abridgedTransformation()) { - writer->AddObjKey("source_crs"); - formatter->setAllowIDInImmediateChild(); - sourceCRS()->_exportToJSON(formatter); - - writer->AddObjKey("target_crs"); - formatter->setAllowIDInImmediateChild(); - targetCRS()->_exportToJSON(formatter); - - const auto &l_interpolationCRS = interpolationCRS(); - if (l_interpolationCRS) { - writer->AddObjKey("interpolation_crs"); - formatter->setAllowIDInImmediateChild(); - l_interpolationCRS->_exportToJSON(formatter); - } - } - - writer->AddObjKey("method"); - formatter->setOmitTypeInImmediateChild(); - formatter->setAllowIDInImmediateChild(); - method()->_exportToJSON(formatter); - - writer->AddObjKey("parameters"); - { - auto parametersContext(writer->MakeArrayContext(false)); - for (const auto &genOpParamvalue : parameterValues()) { - formatter->setAllowIDInImmediateChild(); - formatter->setOmitTypeInImmediateChild(); - genOpParamvalue->_exportToJSON(formatter); - } - } - - if (!formatter->abridgedTransformation()) { - if (!coordinateOperationAccuracies().empty()) { - writer->AddObjKey("accuracy"); - writer->Add(coordinateOperationAccuracies()[0]->value()); - } - } - - if (formatter->abridgedTransformation()) { - if (formatter->outputId()) { - formatID(formatter); - } - } else { - ObjectUsage::baseExportToJSON(formatter); - } -} - -//! @endcond - -// --------------------------------------------------------------------------- - -static void exportSourceCRSAndTargetCRSToWKT(const CoordinateOperation *co, - io::WKTFormatter *formatter) { - auto l_sourceCRS = co->sourceCRS(); - assert(l_sourceCRS); - auto l_targetCRS = co->targetCRS(); - assert(l_targetCRS); - const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; - const bool canExportCRSId = - (isWKT2 && formatter->use2019Keywords() && - !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId())); - - const bool hasDomains = !co->domains().empty(); - if (hasDomains) { - formatter->pushDisableUsage(); - } - - formatter->startNode(io::WKTConstants::SOURCECRS, false); - if (canExportCRSId && !l_sourceCRS->identifiers().empty()) { - // fake that top node has no id, so that the sourceCRS id is - // considered - formatter->pushHasId(false); - l_sourceCRS->_exportToWKT(formatter); - formatter->popHasId(); - } else { - l_sourceCRS->_exportToWKT(formatter); - } - formatter->endNode(); - - formatter->startNode(io::WKTConstants::TARGETCRS, false); - if (canExportCRSId && !l_targetCRS->identifiers().empty()) { - // fake that top node has no id, so that the targetCRS id is - // considered - formatter->pushHasId(false); - l_targetCRS->_exportToWKT(formatter); - formatter->popHasId(); - } else { - l_targetCRS->_exportToWKT(formatter); - } - formatter->endNode(); - - if (hasDomains) { - formatter->popDisableUsage(); - } -} - -// --------------------------------------------------------------------------- - -void SingleOperation::exportTransformationToWKT( - io::WKTFormatter *formatter) const { - const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; - if (!isWKT2) { - throw io::FormattingException( - "Transformation can only be exported to WKT2"); - } - - if (formatter->abridgedTransformation()) { - formatter->startNode(io::WKTConstants::ABRIDGEDTRANSFORMATION, - !identifiers().empty()); - } else { - formatter->startNode(io::WKTConstants::COORDINATEOPERATION, - !identifiers().empty()); - } - - formatter->addQuotedString(nameStr()); - - if (formatter->use2019Keywords()) { - const auto &version = operationVersion(); - if (version.has_value()) { - formatter->startNode(io::WKTConstants::VERSION, false); - formatter->addQuotedString(*version); - formatter->endNode(); - } - } - - if (!formatter->abridgedTransformation()) { - exportSourceCRSAndTargetCRSToWKT(this, formatter); - } - - method()->_exportToWKT(formatter); - - for (const auto ¶mValue : parameterValues()) { - paramValue->_exportToWKT(formatter, nullptr); - } - - if (!formatter->abridgedTransformation()) { - if (interpolationCRS()) { - formatter->startNode(io::WKTConstants::INTERPOLATIONCRS, false); - interpolationCRS()->_exportToWKT(formatter); - formatter->endNode(); - } - - if (!coordinateOperationAccuracies().empty()) { - formatter->startNode(io::WKTConstants::OPERATIONACCURACY, false); - formatter->add(coordinateOperationAccuracies()[0]->value()); - formatter->endNode(); - } - } - - ObjectUsage::baseExportToWKT(formatter); - formatter->endNode(); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static const std::string nullString; - -static const std::string &_getNTv2Filename(const Transformation *op, - bool allowInverse) { - - const auto &l_method = op->method(); - if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV2 || - (allowInverse && - ci_equal(l_method->nameStr(), INVERSE_OF + EPSG_NAME_METHOD_NTV2))) { - const auto &fileParameter = op->parameterValue( - EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, - EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); - if (fileParameter && - fileParameter->type() == ParameterValue::Type::FILENAME) { - return fileParameter->valueFile(); - } - } - return nullString; -} -//! @endcond - -// --------------------------------------------------------------------------- -//! @cond Doxygen_Suppress -const std::string &Transformation::getNTv2Filename() const { - - return _getNTv2Filename(this, false); -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static const std::string &_getNTv1Filename(const Transformation *op, - bool allowInverse) { - - const auto &l_method = op->method(); - const auto &methodName = l_method->nameStr(); - if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV1 || - (allowInverse && - ci_equal(methodName, INVERSE_OF + EPSG_NAME_METHOD_NTV1))) { - const auto &fileParameter = op->parameterValue( - EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, - EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); - if (fileParameter && - fileParameter->type() == ParameterValue::Type::FILENAME) { - return fileParameter->valueFile(); - } - } - return nullString; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static const std::string &_getCTABLE2Filename(const Transformation *op, - bool allowInverse) { - const auto &l_method = op->method(); - const auto &methodName = l_method->nameStr(); - if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_CTABLE2) || - (allowInverse && - ci_equal(methodName, INVERSE_OF + PROJ_WKT2_NAME_METHOD_CTABLE2))) { - const auto &fileParameter = op->parameterValue( - EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, - EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); - if (fileParameter && - fileParameter->type() == ParameterValue::Type::FILENAME) { - return fileParameter->valueFile(); - } - } - return nullString; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static const std::string & -_getHorizontalShiftGTIFFFilename(const Transformation *op, bool allowInverse) { - const auto &l_method = op->method(); - const auto &methodName = l_method->nameStr(); - if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF) || - (allowInverse && - ci_equal(methodName, - INVERSE_OF + PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF))) { - const auto &fileParameter = op->parameterValue( - EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, - EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); - if (fileParameter && - fileParameter->type() == ParameterValue::Type::FILENAME) { - return fileParameter->valueFile(); - } - } - return nullString; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static const std::string & -_getGeocentricTranslationFilename(const Transformation *op, bool allowInverse) { - - const auto &l_method = op->method(); - const auto &methodName = l_method->nameStr(); - if (l_method->getEPSGCode() == - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN || - (allowInverse && - ci_equal( - methodName, - INVERSE_OF + - EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN))) { - const auto &fileParameter = - op->parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, - EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE); - if (fileParameter && - fileParameter->type() == ParameterValue::Type::FILENAME) { - return fileParameter->valueFile(); - } - } - return nullString; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static const std::string & -_getHeightToGeographic3DFilename(const Transformation *op, bool allowInverse) { - - const auto &methodName = op->method()->nameStr(); - - if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D) || - (allowInverse && - ci_equal(methodName, - INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D))) { - const auto &fileParameter = - op->parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, - EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); - if (fileParameter && - fileParameter->type() == ParameterValue::Type::FILENAME) { - return fileParameter->valueFile(); - } - } - return nullString; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static bool -isGeographic3DToGravityRelatedHeight(const OperationMethodNNPtr &method, - bool allowInverse) { - const auto &methodName = method->nameStr(); - static const char *const methodCodes[] = { - "1025", // Geographic3D to GravityRelatedHeight (EGM2008) - "1030", // Geographic3D to GravityRelatedHeight (NZgeoid) - "1045", // Geographic3D to GravityRelatedHeight (OSGM02-Ire) - "1047", // Geographic3D to GravityRelatedHeight (Gravsoft) - "1048", // Geographic3D to GravityRelatedHeight (Ausgeoid v2) - "1050", // Geographic3D to GravityRelatedHeight (CI) - "1059", // Geographic3D to GravityRelatedHeight (PNG) - "1060", // Geographic3D to GravityRelatedHeight (CGG2013) - "1072", // Geographic3D to GravityRelatedHeight (OSGM15-Ire) - "1073", // Geographic3D to GravityRelatedHeight (IGN2009) - "1081", // Geographic3D to GravityRelatedHeight (BEV AT) - "1083", // Geog3D to Geog2D+Vertical (AUSGeoid v2) - "9661", // Geographic3D to GravityRelatedHeight (EGM) - "9662", // Geographic3D to GravityRelatedHeight (Ausgeoid98) - "9663", // Geographic3D to GravityRelatedHeight (OSGM-GB) - "9664", // Geographic3D to GravityRelatedHeight (IGN1997) - "9665", // Geographic3D to GravityRelatedHeight (US .gtx) - "9635", // Geog3D to Geog2D+GravityRelatedHeight (US .gtx) - }; - - if (ci_find(methodName, "Geographic3D to GravityRelatedHeight") == 0) { - return true; - } - if (allowInverse && - ci_find(methodName, - INVERSE_OF + "Geographic3D to GravityRelatedHeight") == 0) { - return true; - } - - for (const auto &code : methodCodes) { - for (const auto &idSrc : method->identifiers()) { - const auto &srcAuthName = *(idSrc->codeSpace()); - const auto &srcCode = idSrc->code(); - if (ci_equal(srcAuthName, "EPSG") && srcCode == code) { - return true; - } - if (allowInverse && ci_equal(srcAuthName, "INVERSE(EPSG)") && - srcCode == code) { - return true; - } - } - } - return false; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -const std::string &Transformation::getHeightToGeographic3DFilename() const { - - const std::string &ret = _getHeightToGeographic3DFilename(this, false); - if (!ret.empty()) - return ret; - if (isGeographic3DToGravityRelatedHeight(method(), false)) { - const auto &fileParameter = - parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, - EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); - if (fileParameter && - fileParameter->type() == ParameterValue::Type::FILENAME) { - return fileParameter->valueFile(); - } - } - return nullString; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static util::PropertyMap -createSimilarPropertiesMethod(common::IdentifiedObjectNNPtr obj) { - util::PropertyMap map; - - const std::string &forwardName = obj->nameStr(); - if (!forwardName.empty()) { - map.set(common::IdentifiedObject::NAME_KEY, forwardName); - } - - { - auto ar = util::ArrayOfBaseObject::create(); - for (const auto &idSrc : obj->identifiers()) { - const auto &srcAuthName = *(idSrc->codeSpace()); - const auto &srcCode = idSrc->code(); - auto idsProp = util::PropertyMap().set( - metadata::Identifier::CODESPACE_KEY, srcAuthName); - ar->add(metadata::Identifier::create(srcCode, idsProp)); - } - if (!ar->empty()) { - map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar); - } - } - - return map; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static util::PropertyMap -createSimilarPropertiesTransformation(TransformationNNPtr obj) { - util::PropertyMap map; - - // The domain(s) are unchanged - addDomains(map, obj.get()); - - const std::string &forwardName = obj->nameStr(); - if (!forwardName.empty()) { - map.set(common::IdentifiedObject::NAME_KEY, forwardName); - } - - const std::string &remarks = obj->remarks(); - if (!remarks.empty()) { - map.set(common::IdentifiedObject::REMARKS_KEY, remarks); - } - - addModifiedIdentifier(map, obj.get(), false, true); - - return map; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static TransformationNNPtr -createNTv1(const util::PropertyMap &properties, - const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, - const std::string &filename, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - return Transformation::create( - properties, sourceCRSIn, targetCRSIn, nullptr, - createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV1), - {OperationParameter::create( - util::PropertyMap() - .set(common::IdentifiedObject::NAME_KEY, - EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE) - .set(metadata::Identifier::CODESPACE_KEY, - metadata::Identifier::EPSG) - .set(metadata::Identifier::CODE_KEY, - EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE))}, - {ParameterValue::createFilename(filename)}, accuracies); -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Return an equivalent transformation to the current one, but using - * PROJ alternative grid names. - */ -TransformationNNPtr Transformation::substitutePROJAlternativeGridNames( - io::DatabaseContextNNPtr databaseContext) const { - auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>( - shared_from_this().as_nullable())); - - const auto &l_method = method(); - const int methodEPSGCode = l_method->getEPSGCode(); - - std::string projFilename; - std::string projGridFormat; - bool inverseDirection = false; - - const auto &NTv1Filename = _getNTv1Filename(this, false); - const auto &NTv2Filename = _getNTv2Filename(this, false); - std::string lasFilename; - if (methodEPSGCode == EPSG_CODE_METHOD_NADCON) { - const auto &latitudeFileParameter = - parameterValue(EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE, - EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE); - const auto &longitudeFileParameter = - parameterValue(EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE, - EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE); - if (latitudeFileParameter && - latitudeFileParameter->type() == ParameterValue::Type::FILENAME && - longitudeFileParameter && - longitudeFileParameter->type() == ParameterValue::Type::FILENAME) { - lasFilename = latitudeFileParameter->valueFile(); - } - } - const auto &horizontalGridName = - !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty() - ? NTv2Filename - : lasFilename; - - if (!horizontalGridName.empty() && - databaseContext->lookForGridAlternative(horizontalGridName, - projFilename, projGridFormat, - inverseDirection)) { - - if (horizontalGridName == projFilename) { - if (inverseDirection) { - throw util::UnsupportedOperationException( - "Inverse direction for " + projFilename + " not supported"); - } - return self; - } - - const auto &l_sourceCRS = sourceCRS(); - const auto &l_targetCRS = targetCRS(); - const auto &l_accuracies = coordinateOperationAccuracies(); - if (projGridFormat == "GTiff") { - auto parameters = - std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}; - auto methodProperties = util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF); - auto values = std::vector<ParameterValueNNPtr>{ - ParameterValue::createFilename(projFilename)}; - if (inverseDirection) { - return create(createPropertiesForInverse( - self.as_nullable().get(), true, false), - l_targetCRS, l_sourceCRS, nullptr, - methodProperties, parameters, values, - l_accuracies) - ->inverseAsTransformation(); - - } else { - return create(createSimilarPropertiesTransformation(self), - l_sourceCRS, l_targetCRS, nullptr, - methodProperties, parameters, values, - l_accuracies); - } - } else if (projGridFormat == "NTv1") { - if (inverseDirection) { - return createNTv1(createPropertiesForInverse( - self.as_nullable().get(), true, false), - l_targetCRS, l_sourceCRS, projFilename, - l_accuracies) - ->inverseAsTransformation(); - } else { - return createNTv1(createSimilarPropertiesTransformation(self), - l_sourceCRS, l_targetCRS, projFilename, - l_accuracies); - } - } else if (projGridFormat == "NTv2") { - if (inverseDirection) { - return createNTv2(createPropertiesForInverse( - self.as_nullable().get(), true, false), - l_targetCRS, l_sourceCRS, projFilename, - l_accuracies) - ->inverseAsTransformation(); - } else { - return createNTv2(createSimilarPropertiesTransformation(self), - l_sourceCRS, l_targetCRS, projFilename, - l_accuracies); - } - } else if (projGridFormat == "CTable2") { - auto parameters = - std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}; - auto methodProperties = - util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, - PROJ_WKT2_NAME_METHOD_CTABLE2); - auto values = std::vector<ParameterValueNNPtr>{ - ParameterValue::createFilename(projFilename)}; - if (inverseDirection) { - return create(createPropertiesForInverse( - self.as_nullable().get(), true, false), - l_targetCRS, l_sourceCRS, nullptr, - methodProperties, parameters, values, - l_accuracies) - ->inverseAsTransformation(); - - } else { - return create(createSimilarPropertiesTransformation(self), - l_sourceCRS, l_targetCRS, nullptr, - methodProperties, parameters, values, - l_accuracies); - } - } - } - - if (isGeographic3DToGravityRelatedHeight(method(), false)) { - const auto &fileParameter = - parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, - EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); - if (fileParameter && - fileParameter->type() == ParameterValue::Type::FILENAME) { - auto filename = fileParameter->valueFile(); - if (databaseContext->lookForGridAlternative( - filename, projFilename, projGridFormat, inverseDirection)) { - - if (inverseDirection) { - throw util::UnsupportedOperationException( - "Inverse direction for " - "Geographic3DToGravityRelatedHeight not supported"); - } - - if (filename == projFilename) { - return self; - } - - auto parameters = std::vector<OperationParameterNNPtr>{ - createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)}; -#ifdef disabled_for_now - if (inverseDirection) { - return create(createPropertiesForInverse( - self.as_nullable().get(), true, false), - targetCRS(), sourceCRS(), nullptr, - createSimilarPropertiesMethod(method()), - parameters, {ParameterValue::createFilename( - projFilename)}, - coordinateOperationAccuracies()) - ->inverseAsTransformation(); - } else -#endif - { - return create( - createSimilarPropertiesTransformation(self), - sourceCRS(), targetCRS(), nullptr, - createSimilarPropertiesMethod(method()), parameters, - {ParameterValue::createFilename(projFilename)}, - coordinateOperationAccuracies()); - } - } - } - } - - const auto &geocentricTranslationFilename = - _getGeocentricTranslationFilename(this, false); - if (!geocentricTranslationFilename.empty()) { - if (databaseContext->lookForGridAlternative( - geocentricTranslationFilename, projFilename, projGridFormat, - inverseDirection)) { - - if (inverseDirection) { - throw util::UnsupportedOperationException( - "Inverse direction for " - "GeocentricTranslation not supported"); - } - - if (geocentricTranslationFilename == projFilename) { - return self; - } - - auto parameters = - std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE)}; - return create(createSimilarPropertiesTransformation(self), - sourceCRS(), targetCRS(), interpolationCRS(), - createSimilarPropertiesMethod(method()), parameters, - {ParameterValue::createFilename(projFilename)}, - coordinateOperationAccuracies()); - } - } - - if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON || - methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD || - methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT || - methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX) { - auto fileParameter = - parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, - EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE); - if (fileParameter && - fileParameter->type() == ParameterValue::Type::FILENAME) { - - auto filename = fileParameter->valueFile(); - if (databaseContext->lookForGridAlternative( - filename, projFilename, projGridFormat, inverseDirection)) { - - if (filename == projFilename) { - if (inverseDirection) { - throw util::UnsupportedOperationException( - "Inverse direction for " + projFilename + - " not supported"); - } - return self; - } - - auto parameters = std::vector<OperationParameterNNPtr>{ - createOpParamNameEPSGCode( - EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)}; - if (inverseDirection) { - return create(createPropertiesForInverse( - self.as_nullable().get(), true, false), - targetCRS(), sourceCRS(), nullptr, - createSimilarPropertiesMethod(method()), - parameters, {ParameterValue::createFilename( - projFilename)}, - coordinateOperationAccuracies()) - ->inverseAsTransformation(); - } else { - return create( - createSimilarPropertiesTransformation(self), - sourceCRS(), targetCRS(), nullptr, - createSimilarPropertiesMethod(method()), parameters, - {ParameterValue::createFilename(projFilename)}, - coordinateOperationAccuracies()); - } - } - } - } - - return self; -} -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -static void ThrowExceptionNotGeodeticGeographic(const char *trfrm_name) { - throw io::FormattingException(concat("Can apply ", std::string(trfrm_name), - " only to GeodeticCRS / " - "GeographicCRS")); -} - -// --------------------------------------------------------------------------- - -// If crs is a geographic CRS, or a compound CRS of a geographic CRS, -// or a compoundCRS of a bound CRS of a geographic CRS, return that -// geographic CRS -static crs::GeographicCRSPtr -extractGeographicCRSIfGeographicCRSOrEquivalent(const crs::CRSNNPtr &crs) { - auto geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(crs); - if (!geogCRS) { - auto compoundCRS = util::nn_dynamic_pointer_cast<crs::CompoundCRS>(crs); - if (compoundCRS) { - const auto &components = compoundCRS->componentReferenceSystems(); - if (!components.empty()) { - geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>( - components[0]); - if (!geogCRS) { - auto boundCRS = - util::nn_dynamic_pointer_cast<crs::BoundCRS>( - components[0]); - if (boundCRS) { - geogCRS = - util::nn_dynamic_pointer_cast<crs::GeographicCRS>( - boundCRS->baseCRS()); - } - } - } - } else { - auto boundCRS = util::nn_dynamic_pointer_cast<crs::BoundCRS>(crs); - if (boundCRS) { - geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>( - boundCRS->baseCRS()); - } - } - } - return geogCRS; -} - -// --------------------------------------------------------------------------- - -static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter, - const crs::CRSNNPtr &crs, bool addPushV3, - const char *trfrm_name) { - auto sourceCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs); - if (sourceCRSGeog) { - formatter->startInversion(); - sourceCRSGeog->_exportToPROJString(formatter); - formatter->stopInversion(); - if (util::isOfExactType<crs::DerivedGeographicCRS>( - *(sourceCRSGeog.get()))) { - // The export of a DerivedGeographicCRS in non-CRS mode adds - // unit conversion and axis swapping. We must compensate for that - formatter->startInversion(); - sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - formatter->stopInversion(); - } - - if (addPushV3) { - formatter->addStep("push"); - formatter->addParam("v_3"); - } - - formatter->addStep("cart"); - sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); - } else { - auto sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get()); - if (!sourceCRSGeod) { - ThrowExceptionNotGeodeticGeographic(trfrm_name); - } - formatter->startInversion(); - sourceCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter); - formatter->stopInversion(); - } -} -// --------------------------------------------------------------------------- - -static void setupPROJGeodeticTargetCRS(io::PROJStringFormatter *formatter, - const crs::CRSNNPtr &crs, bool addPopV3, - const char *trfrm_name) { - auto targetCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs); - if (targetCRSGeog) { - formatter->addStep("cart"); - formatter->setCurrentStepInverted(true); - targetCRSGeog->ellipsoid()->_exportToPROJString(formatter); - - if (addPopV3) { - formatter->addStep("pop"); - formatter->addParam("v_3"); - } - if (util::isOfExactType<crs::DerivedGeographicCRS>( - *(targetCRSGeog.get()))) { - // The export of a DerivedGeographicCRS in non-CRS mode adds - // unit conversion and axis swapping. We must compensate for that - targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - } - targetCRSGeog->_exportToPROJString(formatter); - } else { - auto targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get()); - if (!targetCRSGeod) { - ThrowExceptionNotGeodeticGeographic(trfrm_name); - } - targetCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter); - } -} - -//! @endcond -// --------------------------------------------------------------------------- - -void Transformation::_exportToPROJString( - io::PROJStringFormatter *formatter) const // throw(FormattingException) -{ - if (formatter->convention() == - io::PROJStringFormatter::Convention::PROJ_4) { - throw io::FormattingException( - "Transformation cannot be exported as a PROJ.4 string"); - } - - formatter->setCoordinateOperationOptimizations(true); - - bool positionVectorConvention = true; - bool sevenParamsTransform = false; - bool threeParamsTransform = false; - bool fifteenParamsTransform = false; - const auto &l_method = method(); - const int methodEPSGCode = l_method->getEPSGCode(); - const auto &methodName = l_method->nameStr(); - const auto paramCount = parameterValues().size(); - const bool l_isTimeDependent = isTimeDependent(methodName); - const bool isPositionVector = - ci_find(methodName, "Position Vector") != std::string::npos || - ci_find(methodName, "PV") != std::string::npos; - const bool isCoordinateFrame = - ci_find(methodName, "Coordinate Frame") != std::string::npos || - ci_find(methodName, "CF") != std::string::npos; - if ((paramCount == 7 && isCoordinateFrame && !l_isTimeDependent) || - methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || - methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || - methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) { - positionVectorConvention = false; - sevenParamsTransform = true; - } else if ( - (paramCount == 15 && isCoordinateFrame && l_isTimeDependent) || - methodEPSGCode == - EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC || - methodEPSGCode == - EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D || - methodEPSGCode == - EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) { - positionVectorConvention = false; - fifteenParamsTransform = true; - } else if ((paramCount == 7 && isPositionVector && !l_isTimeDependent) || - methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || - methodEPSGCode == - EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || - methodEPSGCode == - EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) { - sevenParamsTransform = true; - } else if ( - (paramCount == 15 && isPositionVector && l_isTimeDependent) || - methodEPSGCode == - EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC || - methodEPSGCode == - EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D || - methodEPSGCode == - EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) { - fifteenParamsTransform = true; - } else if ((paramCount == 3 && - ci_find(methodName, "Geocentric translations") != - std::string::npos) || - methodEPSGCode == - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || - methodEPSGCode == - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || - methodEPSGCode == - EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) { - threeParamsTransform = true; - } - if (threeParamsTransform || sevenParamsTransform || - fifteenParamsTransform) { - double x = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); - double y = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); - double z = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); - - auto sourceCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); - auto targetCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); - const bool addPushPopV3 = - !CoordinateOperation::getPrivate()->use3DHelmert_ && - ((sourceCRSGeog && - sourceCRSGeog->coordinateSystem()->axisList().size() == 2) || - (targetCRSGeog && - targetCRSGeog->coordinateSystem()->axisList().size() == 2)); - - setupPROJGeodeticSourceCRS(formatter, sourceCRS(), addPushPopV3, - "Helmert"); - - formatter->addStep("helmert"); - formatter->addParam("x", x); - formatter->addParam("y", y); - formatter->addParam("z", z); - if (sevenParamsTransform || fifteenParamsTransform) { - double rx = - parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND); - double ry = - parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND); - double rz = - parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND); - double scaleDiff = - parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, - common::UnitOfMeasure::PARTS_PER_MILLION); - formatter->addParam("rx", rx); - formatter->addParam("ry", ry); - formatter->addParam("rz", rz); - formatter->addParam("s", scaleDiff); - if (fifteenParamsTransform) { - double rate_x = parameterValueNumeric( - EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION, - common::UnitOfMeasure::METRE_PER_YEAR); - double rate_y = parameterValueNumeric( - EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION, - common::UnitOfMeasure::METRE_PER_YEAR); - double rate_z = parameterValueNumeric( - EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION, - common::UnitOfMeasure::METRE_PER_YEAR); - double rate_rx = parameterValueNumeric( - EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND_PER_YEAR); - double rate_ry = parameterValueNumeric( - EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND_PER_YEAR); - double rate_rz = parameterValueNumeric( - EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND_PER_YEAR); - double rate_scaleDiff = parameterValueNumeric( - EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE, - common::UnitOfMeasure::PPM_PER_YEAR); - double referenceEpochYear = - parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH, - common::UnitOfMeasure::YEAR); - formatter->addParam("dx", rate_x); - formatter->addParam("dy", rate_y); - formatter->addParam("dz", rate_z); - formatter->addParam("drx", rate_rx); - formatter->addParam("dry", rate_ry); - formatter->addParam("drz", rate_rz); - formatter->addParam("ds", rate_scaleDiff); - formatter->addParam("t_epoch", referenceEpochYear); - } - if (positionVectorConvention) { - formatter->addParam("convention", "position_vector"); - } else { - formatter->addParam("convention", "coordinate_frame"); - } - } - - setupPROJGeodeticTargetCRS(formatter, targetCRS(), addPushPopV3, - "Helmert"); - - return; - } - - if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC || - methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC || - methodEPSGCode == - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D || - methodEPSGCode == - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D || - methodEPSGCode == - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D || - methodEPSGCode == - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D) { - - positionVectorConvention = - isPositionVector || - methodEPSGCode == - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC || - methodEPSGCode == - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D || - methodEPSGCode == - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D; - - double x = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); - double y = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); - double z = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); - double rx = parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND); - double ry = parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND); - double rz = parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, - common::UnitOfMeasure::ARC_SECOND); - double scaleDiff = - parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, - common::UnitOfMeasure::PARTS_PER_MILLION); - - double px = parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT); - double py = parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT); - double pz = parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT); - - bool addPushPopV3 = - (methodEPSGCode == - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D || - methodEPSGCode == - EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D); - - setupPROJGeodeticSourceCRS(formatter, sourceCRS(), addPushPopV3, - "Molodensky-Badekas"); - - formatter->addStep("molobadekas"); - formatter->addParam("x", x); - formatter->addParam("y", y); - formatter->addParam("z", z); - formatter->addParam("rx", rx); - formatter->addParam("ry", ry); - formatter->addParam("rz", rz); - formatter->addParam("s", scaleDiff); - formatter->addParam("px", px); - formatter->addParam("py", py); - formatter->addParam("pz", pz); - if (positionVectorConvention) { - formatter->addParam("convention", "position_vector"); - } else { - formatter->addParam("convention", "coordinate_frame"); - } - - setupPROJGeodeticTargetCRS(formatter, targetCRS(), addPushPopV3, - "Molodensky-Badekas"); - - return; - } - - if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY || - methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { - double x = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); - double y = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); - double z = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); - double da = parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE); - double df = parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE); - - auto sourceCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); - if (!sourceCRSGeog) { - throw io::FormattingException( - "Can apply Molodensky only to GeographicCRS"); - } - - auto targetCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); - if (!targetCRSGeog) { - throw io::FormattingException( - "Can apply Molodensky only to GeographicCRS"); - } - - formatter->startInversion(); - sourceCRSGeog->_exportToPROJString(formatter); - formatter->stopInversion(); - - formatter->addStep("molodensky"); - sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); - formatter->addParam("dx", x); - formatter->addParam("dy", y); - formatter->addParam("dz", z); - formatter->addParam("da", da); - formatter->addParam("df", df); - - if (ci_find(methodName, "Abridged") != std::string::npos || - methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { - formatter->addParam("abridged"); - } - - targetCRSGeog->_exportToPROJString(formatter); - - return; - } - - if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) { - double offsetLat = - parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, - common::UnitOfMeasure::ARC_SECOND); - double offsetLong = - parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, - common::UnitOfMeasure::ARC_SECOND); - - auto sourceCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); - if (!sourceCRSGeog) { - throw io::FormattingException( - "Can apply Geographic 2D offsets only to GeographicCRS"); - } - - auto targetCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); - if (!targetCRSGeog) { - throw io::FormattingException( - "Can apply Geographic 2D offsets only to GeographicCRS"); - } - - formatter->startInversion(); - sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - formatter->stopInversion(); - - if (offsetLat != 0.0 || offsetLong != 0.0) { - formatter->addStep("geogoffset"); - formatter->addParam("dlat", offsetLat); - formatter->addParam("dlon", offsetLong); - } - - targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - - return; - } - - if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) { - double offsetLat = - parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, - common::UnitOfMeasure::ARC_SECOND); - double offsetLong = - parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, - common::UnitOfMeasure::ARC_SECOND); - double offsetHeight = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); - - auto sourceCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); - if (!sourceCRSGeog) { - throw io::FormattingException( - "Can apply Geographic 3D offsets only to GeographicCRS"); - } - - auto targetCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); - if (!targetCRSGeog) { - throw io::FormattingException( - "Can apply Geographic 3D offsets only to GeographicCRS"); - } - - formatter->startInversion(); - sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - formatter->stopInversion(); - - if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) { - formatter->addStep("geogoffset"); - formatter->addParam("dlat", offsetLat); - formatter->addParam("dlon", offsetLong); - formatter->addParam("dh", offsetHeight); - } - - targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - - return; - } - - if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) { - double offsetLat = - parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, - common::UnitOfMeasure::ARC_SECOND); - double offsetLong = - parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, - common::UnitOfMeasure::ARC_SECOND); - double offsetHeight = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_UNDULATION); - - auto sourceCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); - if (!sourceCRSGeog) { - auto sourceCRSCompound = - dynamic_cast<const crs::CompoundCRS *>(sourceCRS().get()); - if (sourceCRSCompound) { - sourceCRSGeog = sourceCRSCompound->extractGeographicCRS().get(); - } - if (!sourceCRSGeog) { - throw io::FormattingException("Can apply Geographic 2D with " - "height offsets only to " - "GeographicCRS / CompoundCRS"); - } - } - - auto targetCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); - if (!targetCRSGeog) { - auto targetCRSCompound = - dynamic_cast<const crs::CompoundCRS *>(targetCRS().get()); - if (targetCRSCompound) { - targetCRSGeog = targetCRSCompound->extractGeographicCRS().get(); - } - if (!targetCRSGeog) { - throw io::FormattingException("Can apply Geographic 2D with " - "height offsets only to " - "GeographicCRS / CompoundCRS"); - } - } - - formatter->startInversion(); - sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - formatter->stopInversion(); - - if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) { - formatter->addStep("geogoffset"); - formatter->addParam("dlat", offsetLat); - formatter->addParam("dlon", offsetLong); - formatter->addParam("dh", offsetHeight); - } - - targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - - return; - } - - if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) { - - auto sourceCRSVert = - dynamic_cast<const crs::VerticalCRS *>(sourceCRS().get()); - if (!sourceCRSVert) { - throw io::FormattingException( - "Can apply Vertical offset only to VerticalCRS"); - } - - auto targetCRSVert = - dynamic_cast<const crs::VerticalCRS *>(targetCRS().get()); - if (!targetCRSVert) { - throw io::FormattingException( - "Can apply Vertical offset only to VerticalCRS"); - } - - auto offsetHeight = - parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); - - formatter->startInversion(); - sourceCRSVert->addLinearUnitConvert(formatter); - formatter->stopInversion(); - - formatter->addStep("geogoffset"); - formatter->addParam("dh", offsetHeight); - - targetCRSVert->addLinearUnitConvert(formatter); - - return; - } - - // Substitute grid names with PROJ friendly names. - if (formatter->databaseContext()) { - auto alternate = substitutePROJAlternativeGridNames( - NN_NO_CHECK(formatter->databaseContext())); - auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>( - shared_from_this().as_nullable())); - - if (alternate != self) { - alternate->_exportToPROJString(formatter); - return; - } - } - - const bool isMethodInverseOf = starts_with(methodName, INVERSE_OF); - - const auto &NTv1Filename = _getNTv1Filename(this, true); - const auto &NTv2Filename = _getNTv2Filename(this, true); - const auto &CTABLE2Filename = _getCTABLE2Filename(this, true); - const auto &HorizontalShiftGTIFFFilename = - _getHorizontalShiftGTIFFFilename(this, true); - const auto &hGridShiftFilename = - !HorizontalShiftGTIFFFilename.empty() - ? HorizontalShiftGTIFFFilename - : !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty() - ? NTv2Filename - : CTABLE2Filename; - if (!hGridShiftFilename.empty()) { - auto sourceCRSGeog = - extractGeographicCRSIfGeographicCRSOrEquivalent(sourceCRS()); - if (!sourceCRSGeog) { - throw io::FormattingException( - concat("Can apply ", methodName, " only to GeographicCRS")); - } - - auto targetCRSGeog = - extractGeographicCRSIfGeographicCRSOrEquivalent(targetCRS()); - if (!targetCRSGeog) { - throw io::FormattingException( - concat("Can apply ", methodName, " only to GeographicCRS")); - } - - formatter->startInversion(); - sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - formatter->stopInversion(); - - if (isMethodInverseOf) { - formatter->startInversion(); - } - formatter->addStep("hgridshift"); - formatter->addParam("grids", hGridShiftFilename); - if (isMethodInverseOf) { - formatter->stopInversion(); - } - - targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - - return; - } - - const auto &geocentricTranslationFilename = - _getGeocentricTranslationFilename(this, true); - if (!geocentricTranslationFilename.empty()) { - auto sourceCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); - if (!sourceCRSGeog) { - throw io::FormattingException( - concat("Can apply ", methodName, " only to GeographicCRS")); - } - - auto targetCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); - if (!targetCRSGeog) { - throw io::FormattingException( - concat("Can apply ", methodName, " only to GeographicCRS")); - } - - const auto &interpCRS = interpolationCRS(); - if (!interpCRS) { - throw io::FormattingException( - "InterpolationCRS required " - "for" - " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN); - } - const bool interpIsSrc = interpCRS->_isEquivalentTo( - sourceCRS().get(), util::IComparable::Criterion::EQUIVALENT); - const bool interpIsTarget = interpCRS->_isEquivalentTo( - targetCRS().get(), util::IComparable::Criterion::EQUIVALENT); - if (!interpIsSrc && !interpIsTarget) { - throw io::FormattingException( - "For" - " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN - ", interpolation CRS should be the source or target CRS"); - } - - formatter->startInversion(); - sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - formatter->stopInversion(); - - if (isMethodInverseOf) { - formatter->startInversion(); - } - - formatter->addStep("push"); - formatter->addParam("v_3"); - - formatter->addStep("cart"); - sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); - - formatter->addStep("xyzgridshift"); - formatter->addParam("grids", geocentricTranslationFilename); - formatter->addParam("grid_ref", - interpIsTarget ? "output_crs" : "input_crs"); - (interpIsTarget ? targetCRSGeog : sourceCRSGeog) - ->ellipsoid() - ->_exportToPROJString(formatter); - - formatter->startInversion(); - formatter->addStep("cart"); - targetCRSGeog->ellipsoid()->_exportToPROJString(formatter); - formatter->stopInversion(); - - formatter->addStep("pop"); - formatter->addParam("v_3"); - - if (isMethodInverseOf) { - formatter->stopInversion(); - } - - targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - - return; - } - - const auto &heightFilename = _getHeightToGeographic3DFilename(this, true); - if (!heightFilename.empty()) { - auto targetCRSGeog = - extractGeographicCRSIfGeographicCRSOrEquivalent(targetCRS()); - if (!targetCRSGeog) { - throw io::FormattingException( - concat("Can apply ", methodName, " only to GeographicCRS")); - } - - if (!formatter->omitHorizontalConversionInVertTransformation()) { - formatter->startInversion(); - formatter->pushOmitZUnitConversion(); - targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - formatter->popOmitZUnitConversion(); - formatter->stopInversion(); - } - - if (isMethodInverseOf) { - formatter->startInversion(); - } - formatter->addStep("vgridshift"); - formatter->addParam("grids", heightFilename); - formatter->addParam("multiplier", 1.0); - if (isMethodInverseOf) { - formatter->stopInversion(); - } - - if (!formatter->omitHorizontalConversionInVertTransformation()) { - formatter->pushOmitZUnitConversion(); - targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - formatter->popOmitZUnitConversion(); - } - - return; - } - - if (isGeographic3DToGravityRelatedHeight(method(), true)) { - auto fileParameter = - parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, - EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); - if (fileParameter && - fileParameter->type() == ParameterValue::Type::FILENAME) { - auto filename = fileParameter->valueFile(); - - auto sourceCRSGeog = - extractGeographicCRSIfGeographicCRSOrEquivalent(sourceCRS()); - if (!sourceCRSGeog) { - throw io::FormattingException( - concat("Can apply ", methodName, " only to GeographicCRS")); - } - - if (!formatter->omitHorizontalConversionInVertTransformation()) { - formatter->startInversion(); - formatter->pushOmitZUnitConversion(); - sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - formatter->popOmitZUnitConversion(); - formatter->stopInversion(); - } - - bool doInversion = isMethodInverseOf; - // The EPSG Geog3DToHeight is the reverse convention of PROJ ! - doInversion = !doInversion; - if (doInversion) { - formatter->startInversion(); - } - formatter->addStep("vgridshift"); - formatter->addParam("grids", filename); - formatter->addParam("multiplier", 1.0); - if (doInversion) { - formatter->stopInversion(); - } - - if (!formatter->omitHorizontalConversionInVertTransformation()) { - formatter->pushOmitZUnitConversion(); - sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - formatter->popOmitZUnitConversion(); - } - - return; - } - } - - if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON) { - auto fileParameter = - parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, - EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE); - if (fileParameter && - fileParameter->type() == ParameterValue::Type::FILENAME) { - formatter->addStep("vgridshift"); - formatter->addParam("grids", fileParameter->valueFile()); - if (fileParameter->valueFile().find(".tif") != std::string::npos) { - formatter->addParam("multiplier", 1.0); - } else { - // The vertcon grids go from NGVD 29 to NAVD 88, with units - // in millimeter (see - // https://github.com/OSGeo/proj.4/issues/1071), for gtx files - formatter->addParam("multiplier", 0.001); - } - return; - } - } - - if (methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD || - methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT || - methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX) { - auto fileParameter = - parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, - EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE); - if (fileParameter && - fileParameter->type() == ParameterValue::Type::FILENAME) { - formatter->addStep("vgridshift"); - formatter->addParam("grids", fileParameter->valueFile()); - formatter->addParam("multiplier", 1.0); - return; - } - } - - if (isLongitudeRotation()) { - double offsetDeg = - parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, - common::UnitOfMeasure::DEGREE); - - auto sourceCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); - if (!sourceCRSGeog) { - throw io::FormattingException( - concat("Can apply ", methodName, " only to GeographicCRS")); - } - - auto targetCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); - if (!targetCRSGeog) { - throw io::FormattingException( - concat("Can apply ", methodName + " only to GeographicCRS")); - } - - if (!sourceCRSGeog->ellipsoid()->_isEquivalentTo( - targetCRSGeog->ellipsoid().get(), - util::IComparable::Criterion::EQUIVALENT)) { - // This is arguable if we should check this... - throw io::FormattingException("Can apply Longitude rotation " - "only to SRS with same " - "ellipsoid"); - } - - formatter->startInversion(); - sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - formatter->stopInversion(); - - bool done = false; - if (offsetDeg != 0.0) { - // Optimization: as we are doing nominally a +step=inv, - // if the negation of the offset value is a well-known name, - // then use forward case with this name. - auto projPMName = datum::PrimeMeridian::getPROJStringWellKnownName( - common::Angle(-offsetDeg)); - if (!projPMName.empty()) { - done = true; - formatter->addStep("longlat"); - sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); - formatter->addParam("pm", projPMName); - } - } - if (!done) { - // To actually add the offset, we must use the reverse longlat - // operation. - formatter->startInversion(); - formatter->addStep("longlat"); - sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); - datum::PrimeMeridian::create(util::PropertyMap(), - common::Angle(offsetDeg)) - ->_exportToPROJString(formatter); - formatter->stopInversion(); - } - - targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); - - return; - } - - if (exportToPROJStringGeneric(formatter)) { - return; - } - - throw io::FormattingException("Unimplemented"); -} - -// --------------------------------------------------------------------------- - -bool SingleOperation::exportToPROJStringGeneric( - io::PROJStringFormatter *formatter) const { - const int methodEPSGCode = method()->getEPSGCode(); - - if (methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION) { - const double A0 = parameterValueMeasure(EPSG_CODE_PARAMETER_A0).value(); - const double A1 = parameterValueMeasure(EPSG_CODE_PARAMETER_A1).value(); - const double A2 = parameterValueMeasure(EPSG_CODE_PARAMETER_A2).value(); - const double B0 = parameterValueMeasure(EPSG_CODE_PARAMETER_B0).value(); - const double B1 = parameterValueMeasure(EPSG_CODE_PARAMETER_B1).value(); - const double B2 = parameterValueMeasure(EPSG_CODE_PARAMETER_B2).value(); - - // Do not mess with axis unit and order for that transformation - - formatter->addStep("affine"); - formatter->addParam("xoff", A0); - formatter->addParam("s11", A1); - formatter->addParam("s12", A2); - formatter->addParam("yoff", B0); - formatter->addParam("s21", B1); - formatter->addParam("s22", B2); - - return true; - } - - if (isAxisOrderReversal(methodEPSGCode)) { - formatter->addStep("axisswap"); - formatter->addParam("order", "2,1"); - auto sourceCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); - auto targetCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); - if (sourceCRSGeog && targetCRSGeog) { - const auto &unitSrc = - sourceCRSGeog->coordinateSystem()->axisList()[0]->unit(); - const auto &unitDst = - targetCRSGeog->coordinateSystem()->axisList()[0]->unit(); - if (!unitSrc._isEquivalentTo( - unitDst, util::IComparable::Criterion::EQUIVALENT)) { - formatter->addStep("unitconvert"); - auto projUnit = unitSrc.exportToPROJString(); - if (projUnit.empty()) { - formatter->addParam("xy_in", unitSrc.conversionToSI()); - } else { - formatter->addParam("xy_in", projUnit); - } - projUnit = unitDst.exportToPROJString(); - if (projUnit.empty()) { - formatter->addParam("xy_out", unitDst.conversionToSI()); - } else { - formatter->addParam("xy_out", projUnit); - } - } - } - return true; - } - - if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) { - - auto sourceCRSGeod = - dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get()); - auto targetCRSGeod = - dynamic_cast<const crs::GeodeticCRS *>(targetCRS().get()); - if (sourceCRSGeod && targetCRSGeod) { - auto sourceCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(sourceCRSGeod); - auto targetCRSGeog = - dynamic_cast<const crs::GeographicCRS *>(targetCRSGeod); - bool isSrcGeocentric = sourceCRSGeod->isGeocentric(); - bool isSrcGeographic = sourceCRSGeog != nullptr; - bool isTargetGeocentric = targetCRSGeod->isGeocentric(); - bool isTargetGeographic = targetCRSGeog != nullptr; - if ((isSrcGeocentric && isTargetGeographic) || - (isSrcGeographic && isTargetGeocentric)) { - - formatter->startInversion(); - sourceCRSGeod->_exportToPROJString(formatter); - formatter->stopInversion(); - - targetCRSGeod->_exportToPROJString(formatter); - - return true; - } - } - - throw io::FormattingException("Invalid nature of source and/or " - "targetCRS for Geographic/Geocentric " - "conversion"); - } - - if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) { - double convFactor = parameterValueNumericAsSI( - EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); - auto uom = common::UnitOfMeasure(std::string(), convFactor, - common::UnitOfMeasure::Type::LINEAR) - .exportToPROJString(); - auto reverse_uom = - common::UnitOfMeasure(std::string(), 1.0 / convFactor, - common::UnitOfMeasure::Type::LINEAR) - .exportToPROJString(); - if (uom == "m") { - // do nothing - } else if (!uom.empty()) { - formatter->addStep("unitconvert"); - formatter->addParam("z_in", uom); - formatter->addParam("z_out", "m"); - } else if (!reverse_uom.empty()) { - formatter->addStep("unitconvert"); - formatter->addParam("z_in", "m"); - formatter->addParam("z_out", reverse_uom); - } else { - formatter->addStep("affine"); - formatter->addParam("s33", convFactor); - } - return true; - } - - if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { - formatter->addStep("axisswap"); - formatter->addParam("order", "1,2,-3"); - return true; - } - - return false; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -PointMotionOperation::~PointMotionOperation() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct ConcatenatedOperation::Private { - std::vector<CoordinateOperationNNPtr> operations_{}; - bool computedName_ = false; - - explicit Private(const std::vector<CoordinateOperationNNPtr> &operationsIn) - : operations_(operationsIn) {} - Private(const Private &) = default; -}; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -ConcatenatedOperation::~ConcatenatedOperation() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -ConcatenatedOperation::ConcatenatedOperation(const ConcatenatedOperation &other) - : CoordinateOperation(other), - d(internal::make_unique<Private>(*(other.d))) {} -//! @endcond - -// --------------------------------------------------------------------------- - -ConcatenatedOperation::ConcatenatedOperation( - const std::vector<CoordinateOperationNNPtr> &operationsIn) - : CoordinateOperation(), d(internal::make_unique<Private>(operationsIn)) {} - -// --------------------------------------------------------------------------- - -/** \brief Return the operation steps of the concatenated operation. - * - * @return the operation steps. - */ -const std::vector<CoordinateOperationNNPtr> & -ConcatenatedOperation::operations() const { - return d->operations_; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static bool compareStepCRS(const crs::CRS *a, const crs::CRS *b) { - const auto &aIds = a->identifiers(); - const auto &bIds = b->identifiers(); - if (aIds.size() == 1 && bIds.size() == 1 && - aIds[0]->code() == bIds[0]->code() && - *aIds[0]->codeSpace() == *bIds[0]->codeSpace()) { - return true; - } - return a->_isEquivalentTo(b, util::IComparable::Criterion::EQUIVALENT); -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a ConcatenatedOperation - * - * @param properties See \ref general_properties. At minimum the name should - * be - * defined. - * @param operationsIn Vector of the CoordinateOperation steps. - * @param accuracies Vector of positional accuracy (might be empty). - * @return new Transformation. - * @throws InvalidOperation - */ -ConcatenatedOperationNNPtr ConcatenatedOperation::create( - const util::PropertyMap &properties, - const std::vector<CoordinateOperationNNPtr> &operationsIn, - const std::vector<metadata::PositionalAccuracyNNPtr> - &accuracies) // throw InvalidOperation -{ - if (operationsIn.size() < 2) { - throw InvalidOperation( - "ConcatenatedOperation must have at least 2 operations"); - } - crs::CRSPtr lastTargetCRS; - - crs::CRSPtr interpolationCRS; - bool interpolationCRSValid = true; - for (size_t i = 0; i < operationsIn.size(); i++) { - auto l_sourceCRS = operationsIn[i]->sourceCRS(); - auto l_targetCRS = operationsIn[i]->targetCRS(); - - if (interpolationCRSValid) { - auto subOpInterpCRS = operationsIn[i]->interpolationCRS(); - if (interpolationCRS == nullptr) - interpolationCRS = subOpInterpCRS; - else if (subOpInterpCRS == nullptr || - !(subOpInterpCRS->isEquivalentTo( - interpolationCRS.get(), - util::IComparable::Criterion::EQUIVALENT))) { - interpolationCRS = nullptr; - interpolationCRSValid = false; - } - } - - if (l_sourceCRS == nullptr || l_targetCRS == nullptr) { - throw InvalidOperation("At least one of the operation lacks a " - "source and/or target CRS"); - } - if (i >= 1) { - if (!compareStepCRS(l_sourceCRS.get(), lastTargetCRS.get())) { -#ifdef DEBUG_CONCATENATED_OPERATION - std::cerr << "Step " << i - 1 << ": " - << operationsIn[i - 1]->nameStr() << std::endl; - std::cerr << "Step " << i << ": " << operationsIn[i]->nameStr() - << std::endl; - { - auto f(io::WKTFormatter::create( - io::WKTFormatter::Convention::WKT2_2019)); - std::cerr << "Source CRS of step " << i << ":" << std::endl; - std::cerr << l_sourceCRS->exportToWKT(f.get()) << std::endl; - } - { - auto f(io::WKTFormatter::create( - io::WKTFormatter::Convention::WKT2_2019)); - std::cerr << "Target CRS of step " << i - 1 << ":" - << std::endl; - std::cerr << lastTargetCRS->exportToWKT(f.get()) - << std::endl; - } -#endif - throw InvalidOperation( - "Inconsistent chaining of CRS in operations"); - } - } - lastTargetCRS = l_targetCRS; - } - auto op = ConcatenatedOperation::nn_make_shared<ConcatenatedOperation>( - operationsIn); - op->assignSelf(op); - op->setProperties(properties); - op->setCRSs(NN_NO_CHECK(operationsIn[0]->sourceCRS()), - NN_NO_CHECK(operationsIn.back()->targetCRS()), - interpolationCRS); - op->setAccuracies(accuracies); -#ifdef DEBUG_CONCATENATED_OPERATION - { - auto f( - io::WKTFormatter::create(io::WKTFormatter::Convention::WKT2_2019)); - std::cerr << "ConcatenatedOperation::create()" << std::endl; - std::cerr << op->exportToWKT(f.get()) << std::endl; - } -#endif - return op; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -// --------------------------------------------------------------------------- - -void ConcatenatedOperation::fixStepsDirection( - const crs::CRSNNPtr &concatOpSourceCRS, - const crs::CRSNNPtr &concatOpTargetCRS, - std::vector<CoordinateOperationNNPtr> &operationsInOut) { - - // Set of heuristics to assign CRS to steps, and possibly reverse them. - - const auto isGeographic = [](const crs::CRS *crs) -> bool { - return dynamic_cast<const crs::GeographicCRS *>(crs) != nullptr; - }; - - const auto isGeocentric = [](const crs::CRS *crs) -> bool { - auto geodCRS = dynamic_cast<const crs::GeodeticCRS *>(crs); - if (geodCRS && geodCRS->coordinateSystem()->axisList().size() == 3) - return true; - return false; - }; - - for (size_t i = 0; i < operationsInOut.size(); ++i) { - auto &op = operationsInOut[i]; - auto l_sourceCRS = op->sourceCRS(); - auto l_targetCRS = op->targetCRS(); - auto conv = dynamic_cast<const Conversion *>(op.get()); - if (conv && i == 0 && !l_sourceCRS && !l_targetCRS) { - auto derivedCRS = - dynamic_cast<const crs::DerivedCRS *>(concatOpSourceCRS.get()); - if (derivedCRS) { - if (i + 1 < operationsInOut.size()) { - // use the sourceCRS of the next operation as our target CRS - l_targetCRS = operationsInOut[i + 1]->sourceCRS(); - // except if it looks like the next operation should - // actually be reversed !!! - if (l_targetCRS && - !compareStepCRS(l_targetCRS.get(), - derivedCRS->baseCRS().get()) && - operationsInOut[i + 1]->targetCRS() && - compareStepCRS( - operationsInOut[i + 1]->targetCRS().get(), - derivedCRS->baseCRS().get())) { - l_targetCRS = operationsInOut[i + 1]->targetCRS(); - } - } - if (!l_targetCRS) { - l_targetCRS = derivedCRS->baseCRS().as_nullable(); - } - auto invConv = - util::nn_dynamic_pointer_cast<InverseConversion>(op); - auto nn_targetCRS = NN_NO_CHECK(l_targetCRS); - if (invConv) { - invConv->inverse()->setCRSs(nn_targetCRS, concatOpSourceCRS, - nullptr); - op->setCRSs(concatOpSourceCRS, nn_targetCRS, nullptr); - } else { - op->setCRSs(nn_targetCRS, concatOpSourceCRS, nullptr); - op = op->inverse(); - } - } else if (i + 1 < operationsInOut.size()) { - /* coverity[copy_paste_error] */ - l_targetCRS = operationsInOut[i + 1]->sourceCRS(); - if (l_targetCRS) { - op->setCRSs(concatOpSourceCRS, NN_NO_CHECK(l_targetCRS), - nullptr); - } - } - } else if (conv && i + 1 == operationsInOut.size() && !l_sourceCRS && - !l_targetCRS) { - auto derivedCRS = - dynamic_cast<const crs::DerivedCRS *>(concatOpTargetCRS.get()); - if (derivedCRS) { - if (i >= 1) { - // use the sourceCRS of the previous operation as our source - // CRS - l_sourceCRS = operationsInOut[i - 1]->targetCRS(); - // except if it looks like the previous operation should - // actually be reversed !!! - if (l_sourceCRS && - !compareStepCRS(l_sourceCRS.get(), - derivedCRS->baseCRS().get()) && - operationsInOut[i - 1]->sourceCRS() && - compareStepCRS( - operationsInOut[i - 1]->sourceCRS().get(), - derivedCRS->baseCRS().get())) { - l_targetCRS = operationsInOut[i - 1]->sourceCRS(); - } - } - if (!l_sourceCRS) { - l_sourceCRS = derivedCRS->baseCRS().as_nullable(); - } - op->setCRSs(NN_NO_CHECK(l_sourceCRS), concatOpTargetCRS, - nullptr); - } else if (i >= 1) { - l_sourceCRS = operationsInOut[i - 1]->targetCRS(); - if (l_sourceCRS) { - derivedCRS = dynamic_cast<const crs::DerivedCRS *>( - l_sourceCRS.get()); - if (conv->isEquivalentTo( - derivedCRS->derivingConversion().get(), - util::IComparable::Criterion::EQUIVALENT)) { - op->setCRSs(concatOpTargetCRS, NN_NO_CHECK(l_sourceCRS), - nullptr); - op = op->inverse(); - } - op->setCRSs(NN_NO_CHECK(l_sourceCRS), concatOpTargetCRS, - nullptr); - } - } - } else if (conv && i > 0 && i < operationsInOut.size() - 1) { - // For an intermediate conversion, use the target CRS of the - // previous step and the source CRS of the next step - l_sourceCRS = operationsInOut[i - 1]->targetCRS(); - l_targetCRS = operationsInOut[i + 1]->sourceCRS(); - if (l_sourceCRS && l_targetCRS) { - op->setCRSs(NN_NO_CHECK(l_sourceCRS), NN_NO_CHECK(l_targetCRS), - nullptr); - } - } else if (!conv && l_sourceCRS && l_targetCRS) { - - // Transformations might be mentioned in their forward directions, - // whereas we should instead use the reverse path. - auto prevOpTarget = (i == 0) ? concatOpSourceCRS.as_nullable() - : operationsInOut[i - 1]->targetCRS(); - if (compareStepCRS(l_sourceCRS.get(), prevOpTarget.get())) { - // do nothing - } else if (compareStepCRS(l_targetCRS.get(), prevOpTarget.get())) { - op = op->inverse(); - } - // Below is needed for EPSG:9103 which chains NAD83(2011) geographic - // 2D with NAD83(2011) geocentric - else if (l_sourceCRS->nameStr() == prevOpTarget->nameStr() && - ((isGeographic(l_sourceCRS.get()) && - isGeocentric(prevOpTarget.get())) || - (isGeocentric(l_sourceCRS.get()) && - isGeographic(prevOpTarget.get())))) { - auto newOp(Conversion::createGeographicGeocentric( - NN_NO_CHECK(prevOpTarget), NN_NO_CHECK(l_sourceCRS))); - operationsInOut.insert(operationsInOut.begin() + i, newOp); - } else if (l_targetCRS->nameStr() == prevOpTarget->nameStr() && - ((isGeographic(l_targetCRS.get()) && - isGeocentric(prevOpTarget.get())) || - (isGeocentric(l_targetCRS.get()) && - isGeographic(prevOpTarget.get())))) { - auto newOp(Conversion::createGeographicGeocentric( - NN_NO_CHECK(prevOpTarget), NN_NO_CHECK(l_targetCRS))); - operationsInOut.insert(operationsInOut.begin() + i, newOp); - } - } - } - - if (!operationsInOut.empty()) { - auto l_sourceCRS = operationsInOut.front()->sourceCRS(); - if (l_sourceCRS && - !compareStepCRS(l_sourceCRS.get(), concatOpSourceCRS.get())) { - throw InvalidOperation("The source CRS of the first step of " - "concatenated operation is not the same " - "as the source CRS of the concatenated " - "operation itself"); - } - - auto l_targetCRS = operationsInOut.back()->targetCRS(); - if (l_targetCRS && - !compareStepCRS(l_targetCRS.get(), concatOpTargetCRS.get())) { - if (l_targetCRS->nameStr() == concatOpTargetCRS->nameStr() && - ((isGeographic(l_targetCRS.get()) && - isGeocentric(concatOpTargetCRS.get())) || - (isGeocentric(l_targetCRS.get()) && - isGeographic(concatOpTargetCRS.get())))) { - auto newOp(Conversion::createGeographicGeocentric( - NN_NO_CHECK(l_targetCRS), concatOpTargetCRS)); - operationsInOut.push_back(newOp); - } else { - throw InvalidOperation("The target CRS of the last step of " - "concatenated operation is not the same " - "as the target CRS of the concatenated " - "operation itself"); - } - } - } -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static std::string computeConcatenatedName( - const std::vector<CoordinateOperationNNPtr> &flattenOps) { - std::string name; - for (const auto &subOp : flattenOps) { - if (!name.empty()) { - name += " + "; - } - const auto &l_name = subOp->nameStr(); - if (l_name.empty()) { - name += "unnamed"; - } else { - name += l_name; - } - } - return name; -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a ConcatenatedOperation, or return a single - * coordinate - * operation. - * - * This computes its accuracy from the sum of its member operations, its - * extent - * - * @param operationsIn Vector of the CoordinateOperation steps. - * @param checkExtent Whether we should check the non-emptyness of the - * intersection - * of the extents of the operations - * @throws InvalidOperation - */ -CoordinateOperationNNPtr ConcatenatedOperation::createComputeMetadata( - const std::vector<CoordinateOperationNNPtr> &operationsIn, - bool checkExtent) // throw InvalidOperation -{ - util::PropertyMap properties; - - if (operationsIn.size() == 1) { - return operationsIn[0]; - } - - std::vector<CoordinateOperationNNPtr> flattenOps; - bool hasBallparkTransformation = false; - for (const auto &subOp : operationsIn) { - hasBallparkTransformation |= subOp->hasBallparkTransformation(); - auto subOpConcat = - dynamic_cast<const ConcatenatedOperation *>(subOp.get()); - if (subOpConcat) { - auto subOps = subOpConcat->operations(); - for (const auto &subSubOp : subOps) { - flattenOps.emplace_back(subSubOp); - } - } else { - flattenOps.emplace_back(subOp); - } - } - if (flattenOps.size() == 1) { - return flattenOps[0]; - } - - properties.set(common::IdentifiedObject::NAME_KEY, - computeConcatenatedName(flattenOps)); - - bool emptyIntersection = false; - auto extent = getExtent(flattenOps, false, emptyIntersection); - if (checkExtent && emptyIntersection) { - std::string msg( - "empty intersection of area of validity of concatenated " - "operations"); - throw InvalidOperationEmptyIntersection(msg); - } - if (extent) { - properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - NN_NO_CHECK(extent)); - } - - std::vector<metadata::PositionalAccuracyNNPtr> accuracies; - const double accuracy = getAccuracy(flattenOps); - if (accuracy >= 0.0) { - accuracies.emplace_back( - metadata::PositionalAccuracy::create(toString(accuracy))); - } - - auto op = create(properties, flattenOps, accuracies); - op->setHasBallparkTransformation(hasBallparkTransformation); - op->d->computedName_ = true; - return op; -} - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr ConcatenatedOperation::inverse() const { - std::vector<CoordinateOperationNNPtr> inversedOperations; - auto l_operations = operations(); - inversedOperations.reserve(l_operations.size()); - for (const auto &operation : l_operations) { - inversedOperations.emplace_back(operation->inverse()); - } - std::reverse(inversedOperations.begin(), inversedOperations.end()); - - auto properties = createPropertiesForInverse(this, false, false); - if (d->computedName_) { - properties.set(common::IdentifiedObject::NAME_KEY, - computeConcatenatedName(inversedOperations)); - } - - auto op = - create(properties, inversedOperations, coordinateOperationAccuracies()); - op->d->computedName_ = d->computedName_; - op->setHasBallparkTransformation(hasBallparkTransformation()); - return op; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -void ConcatenatedOperation::_exportToWKT(io::WKTFormatter *formatter) const { - const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; - if (!isWKT2 || !formatter->use2019Keywords()) { - throw io::FormattingException( - "Transformation can only be exported to WKT2:2019"); - } - - formatter->startNode(io::WKTConstants::CONCATENATEDOPERATION, - !identifiers().empty()); - formatter->addQuotedString(nameStr()); - - if (formatter->use2019Keywords()) { - const auto &version = operationVersion(); - if (version.has_value()) { - formatter->startNode(io::WKTConstants::VERSION, false); - formatter->addQuotedString(*version); - formatter->endNode(); - } - } - - exportSourceCRSAndTargetCRSToWKT(this, formatter); - - const bool canExportOperationId = - !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId()); - - const bool hasDomains = !domains().empty(); - if (hasDomains) { - formatter->pushDisableUsage(); - } - - for (const auto &operation : operations()) { - formatter->startNode(io::WKTConstants::STEP, false); - if (canExportOperationId && !operation->identifiers().empty()) { - // fake that top node has no id, so that the operation id is - // considered - formatter->pushHasId(false); - operation->_exportToWKT(formatter); - formatter->popHasId(); - } else { - operation->_exportToWKT(formatter); - } - formatter->endNode(); - } - - if (hasDomains) { - formatter->popDisableUsage(); - } - - ObjectUsage::baseExportToWKT(formatter); - formatter->endNode(); -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -void ConcatenatedOperation::_exportToJSON( - io::JSONFormatter *formatter) const // throw(FormattingException) -{ - auto writer = formatter->writer(); - auto objectContext(formatter->MakeObjectContext("ConcatenatedOperation", - !identifiers().empty())); - - writer->AddObjKey("name"); - auto l_name = nameStr(); - if (l_name.empty()) { - writer->Add("unnamed"); - } else { - writer->Add(l_name); - } - - writer->AddObjKey("source_crs"); - formatter->setAllowIDInImmediateChild(); - sourceCRS()->_exportToJSON(formatter); - - writer->AddObjKey("target_crs"); - formatter->setAllowIDInImmediateChild(); - targetCRS()->_exportToJSON(formatter); - - writer->AddObjKey("steps"); - { - auto parametersContext(writer->MakeArrayContext(false)); - for (const auto &operation : operations()) { - formatter->setAllowIDInImmediateChild(); - operation->_exportToJSON(formatter); - } - } - - ObjectUsage::baseExportToJSON(formatter); -} - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -CoordinateOperationNNPtr ConcatenatedOperation::_shallowClone() const { - auto op = - ConcatenatedOperation::nn_make_shared<ConcatenatedOperation>(*this); - std::vector<CoordinateOperationNNPtr> ops; - for (const auto &subOp : d->operations_) { - ops.emplace_back(subOp->shallowClone()); - } - op->d->operations_ = ops; - op->assignSelf(op); - op->setCRSs(this, false); - return util::nn_static_pointer_cast<CoordinateOperation>(op); -} -//! @endcond - -// --------------------------------------------------------------------------- - -void ConcatenatedOperation::_exportToPROJString( - io::PROJStringFormatter *formatter) const // throw(FormattingException) -{ - for (const auto &operation : operations()) { - operation->_exportToPROJString(formatter); - } -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -bool ConcatenatedOperation::_isEquivalentTo( - const util::IComparable *other, util::IComparable::Criterion criterion, - const io::DatabaseContextPtr &dbContext) const { - auto otherCO = dynamic_cast<const ConcatenatedOperation *>(other); - if (otherCO == nullptr || - (criterion == util::IComparable::Criterion::STRICT && - !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { - return false; - } - const auto &steps = operations(); - const auto &otherSteps = otherCO->operations(); - if (steps.size() != otherSteps.size()) { - return false; - } - for (size_t i = 0; i < steps.size(); i++) { - if (!steps[i]->_isEquivalentTo(otherSteps[i].get(), criterion, - dbContext)) { - return false; - } - } - return true; -} -//! @endcond - -// --------------------------------------------------------------------------- - -std::set<GridDescription> ConcatenatedOperation::gridsNeeded( - const io::DatabaseContextPtr &databaseContext, - bool considerKnownGridsAsAvailable) const { - std::set<GridDescription> res; - for (const auto &operation : operations()) { - const auto l_gridsNeeded = operation->gridsNeeded( - databaseContext, considerKnownGridsAsAvailable); - for (const auto &gridDesc : l_gridsNeeded) { - res.insert(gridDesc); - } - } - return res; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct CoordinateOperationContext::Private { - io::AuthorityFactoryPtr authorityFactory_{}; - metadata::ExtentPtr extent_{}; - double accuracy_ = 0.0; - SourceTargetCRSExtentUse sourceAndTargetCRSExtentUse_ = - CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST; - SpatialCriterion spatialCriterion_ = - CoordinateOperationContext::SpatialCriterion::STRICT_CONTAINMENT; - bool usePROJNames_ = true; - GridAvailabilityUse gridAvailabilityUse_ = - GridAvailabilityUse::USE_FOR_SORTING; - IntermediateCRSUse allowUseIntermediateCRS_ = CoordinateOperationContext:: - IntermediateCRSUse::IF_NO_DIRECT_TRANSFORMATION; - std::vector<std::pair<std::string, std::string>> - intermediateCRSAuthCodes_{}; - bool discardSuperseded_ = true; - bool allowBallpark_ = true; -}; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -CoordinateOperationContext::~CoordinateOperationContext() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -CoordinateOperationContext::CoordinateOperationContext() - : d(internal::make_unique<Private>()) {} - -// --------------------------------------------------------------------------- - -/** \brief Return the authority factory, or null */ -const io::AuthorityFactoryPtr & -CoordinateOperationContext::getAuthorityFactory() const { - return d->authorityFactory_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the desired area of interest, or null */ -const metadata::ExtentPtr & -CoordinateOperationContext::getAreaOfInterest() const { - return d->extent_; -} - -// --------------------------------------------------------------------------- - -/** \brief Set the desired area of interest, or null */ -void CoordinateOperationContext::setAreaOfInterest( - const metadata::ExtentPtr &extent) { - d->extent_ = extent; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the desired accuracy (in metre), or 0 */ -double CoordinateOperationContext::getDesiredAccuracy() const { - return d->accuracy_; -} - -// --------------------------------------------------------------------------- - -/** \brief Set the desired accuracy (in metre), or 0 */ -void CoordinateOperationContext::setDesiredAccuracy(double accuracy) { - d->accuracy_ = 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). - * - * The default is - * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST. - */ -void CoordinateOperationContext::setSourceAndTargetCRSExtentUse( - SourceTargetCRSExtentUse use) { - d->sourceAndTargetCRSExtentUse_ = use; -} - -// --------------------------------------------------------------------------- - -/** \brief Return 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). - * - * The default is - * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST. - */ -CoordinateOperationContext::SourceTargetCRSExtentUse -CoordinateOperationContext::getSourceAndTargetCRSExtentUse() const { - return d->sourceAndTargetCRSExtentUse_; -} - -// --------------------------------------------------------------------------- - -/** \brief Set the spatial criterion to use when comparing the area of - * validity - * of coordinate operations with the area of interest / area of validity of - * source and target CRS. - * - * The default is STRICT_CONTAINMENT. - */ -void CoordinateOperationContext::setSpatialCriterion( - SpatialCriterion criterion) { - d->spatialCriterion_ = criterion; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the spatial criterion to use when comparing the area of - * validity - * of coordinate operations with the area of interest / area of validity of - * source and target CRS. - * - * The default is STRICT_CONTAINMENT. - */ -CoordinateOperationContext::SpatialCriterion -CoordinateOperationContext::getSpatialCriterion() const { - return d->spatialCriterion_; -} - -// --------------------------------------------------------------------------- - -/** \brief Set whether PROJ alternative grid names should be substituted to - * the official authority names. - * - * This only has effect is an authority factory with a non-null database context - * has been attached to this context. - * - * If set to false, it is still possible to - * obtain later the substitution by using io::PROJStringFormatter::create() - * with a non-null database context. - * - * The default is true. - */ -void CoordinateOperationContext::setUsePROJAlternativeGridNames( - bool usePROJNames) { - d->usePROJNames_ = usePROJNames; -} - -// --------------------------------------------------------------------------- - -/** \brief Return whether PROJ alternative grid names should be substituted to - * the official authority names. - * - * The default is true. - */ -bool CoordinateOperationContext::getUsePROJAlternativeGridNames() const { - return d->usePROJNames_; -} - -// --------------------------------------------------------------------------- - -/** \brief Return whether transformations that are superseded (but not - * deprecated) - * should be discarded. - * - * The default is true. - */ -bool CoordinateOperationContext::getDiscardSuperseded() const { - return d->discardSuperseded_; -} - -// --------------------------------------------------------------------------- - -/** \brief Set whether transformations that are superseded (but not deprecated) - * should be discarded. - * - * The default is true. - */ -void CoordinateOperationContext::setDiscardSuperseded(bool discard) { - d->discardSuperseded_ = discard; -} - -// --------------------------------------------------------------------------- - -/** \brief Set how grid availability is used. - * - * The default is USE_FOR_SORTING. - */ -void CoordinateOperationContext::setGridAvailabilityUse( - GridAvailabilityUse use) { - d->gridAvailabilityUse_ = use; -} - -// --------------------------------------------------------------------------- - -/** \brief Return how grid availability is used. - * - * The default is USE_FOR_SORTING. - */ -CoordinateOperationContext::GridAvailabilityUse -CoordinateOperationContext::getGridAvailabilityUse() const { - return d->gridAvailabilityUse_; -} - -// --------------------------------------------------------------------------- - -/** \brief Set whether an intermediate pivot CRS can be used for researching - * coordinate operations between a source and target CRS. - * - * Concretely if in the database there is an operation from A to C - * (or C to A), and another one from C to B (or B to C), but no direct - * operation between A and B, setting this parameter to - * ALWAYS/IF_NO_DIRECT_TRANSFORMATION, allow chaining both operations. - * - * The current implementation is limited to researching one intermediate - * step. - * - * By default, with the IF_NO_DIRECT_TRANSFORMATION stratgey, all potential - * C candidates will be used if there is no direct transformation. - */ -void CoordinateOperationContext::setAllowUseIntermediateCRS( - IntermediateCRSUse use) { - d->allowUseIntermediateCRS_ = use; -} - -// --------------------------------------------------------------------------- - -/** \brief Return whether an intermediate pivot CRS can be used for researching - * coordinate operations between a source and target CRS. - * - * Concretely if in the database there is an operation from A to C - * (or C to A), and another one from C to B (or B to C), but no direct - * operation between A and B, setting this parameter to - * ALWAYS/IF_NO_DIRECT_TRANSFORMATION, allow chaining both operations. - * - * The default is IF_NO_DIRECT_TRANSFORMATION. - */ -CoordinateOperationContext::IntermediateCRSUse -CoordinateOperationContext::getAllowUseIntermediateCRS() const { - return d->allowUseIntermediateCRS_; -} - -// --------------------------------------------------------------------------- - -/** \brief Restrict the potential pivot CRSs that can be used when trying to - * build a coordinate operation between two CRS that have no direct operation. - * - * @param intermediateCRSAuthCodes a vector of (auth_name, code) that can be - * used as potential pivot RS - */ -void CoordinateOperationContext::setIntermediateCRS( - const std::vector<std::pair<std::string, std::string>> - &intermediateCRSAuthCodes) { - d->intermediateCRSAuthCodes_ = intermediateCRSAuthCodes; -} - -// --------------------------------------------------------------------------- - -/** \brief Return the potential pivot CRSs that can be used when trying to - * build a coordinate operation between two CRS that have no direct operation. - * - */ -const std::vector<std::pair<std::string, std::string>> & -CoordinateOperationContext::getIntermediateCRS() const { - return d->intermediateCRSAuthCodes_; -} - -// --------------------------------------------------------------------------- - -/** \brief Creates a context for a coordinate operation. - * - * If a non null authorityFactory is provided, the resulting context should - * not be used simultaneously by more than one thread. - * - * If authorityFactory->getAuthority() is the empty string, then coordinate - * operations from any authority will be searched, with the restrictions set - * in the authority_to_authority_preference database table. - * If authorityFactory->getAuthority() is set to "any", then coordinate - * operations from any authority will be searched - * If authorityFactory->getAuthority() is a non-empty string different of "any", - * then coordinate operatiosn will be searched only in that authority namespace. - * - * @param authorityFactory Authority factory, or null if no database lookup - * is allowed. - * Use io::authorityFactory::create(context, std::string()) to allow all - * authorities to be used. - * @param extent Area of interest, or null if none is known. - * @param accuracy Maximum allowed accuracy in metre, as specified in or - * 0 to get best accuracy. - * @return a new context. - */ -CoordinateOperationContextNNPtr CoordinateOperationContext::create( - const io::AuthorityFactoryPtr &authorityFactory, - const metadata::ExtentPtr &extent, double accuracy) { - auto ctxt = NN_NO_CHECK( - CoordinateOperationContext::make_unique<CoordinateOperationContext>()); - ctxt->d->authorityFactory_ = authorityFactory; - ctxt->d->extent_ = extent; - ctxt->d->accuracy_ = accuracy; - return ctxt; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -struct CoordinateOperationFactory::Private { - - struct Context { - // This is the extent of the source CRS and target CRS of the initial - // CoordinateOperationFactory::createOperations() public call, not - // necessarily the ones of intermediate - // CoordinateOperationFactory::Private::createOperations() calls. - // This is used to compare transformations area of use against the - // area of use of the source & target CRS. - const metadata::ExtentPtr &extent1; - const metadata::ExtentPtr &extent2; - const CoordinateOperationContextNNPtr &context; - bool inCreateOperationsWithDatumPivotAntiRecursion = false; - bool inCreateOperationsGeogToVertWithAlternativeGeog = false; - bool inCreateOperationsGeogToVertWithIntermediateVert = false; - bool skipHorizontalTransformation = false; - std::map<std::pair<io::AuthorityFactory::ObjectType, std::string>, - std::list<std::pair<std::string, std::string>>> - cacheNameToCRS{}; - - Context(const metadata::ExtentPtr &extent1In, - const metadata::ExtentPtr &extent2In, - const CoordinateOperationContextNNPtr &contextIn) - : extent1(extent1In), extent2(extent2In), context(contextIn) {} - }; - - static std::vector<CoordinateOperationNNPtr> - createOperations(const crs::CRSNNPtr &sourceCRS, - const crs::CRSNNPtr &targetCRS, Context &context); - - private: - static constexpr bool disallowEmptyIntersection = true; - - static void - buildCRSIds(const crs::CRSNNPtr &crs, Private::Context &context, - std::list<std::pair<std::string, std::string>> &ids); - - static std::vector<CoordinateOperationNNPtr> findOpsInRegistryDirect( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, bool &resNonEmptyBeforeFiltering); - - static std::vector<CoordinateOperationNNPtr> - findOpsInRegistryDirectTo(const crs::CRSNNPtr &targetCRS, - Private::Context &context); - - static std::vector<CoordinateOperationNNPtr> - findsOpsInRegistryWithIntermediate( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, - bool useCreateBetweenGeodeticCRSWithDatumBasedIntermediates); - - static void createOperationsFromProj4Ext( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - const crs::BoundCRS *boundSrc, const crs::BoundCRS *boundDst, - std::vector<CoordinateOperationNNPtr> &res); - - static bool createOperationsFromDatabase( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::GeodeticCRS *geodSrc, - const crs::GeodeticCRS *geodDst, const crs::GeographicCRS *geogSrc, - const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc, - const crs::VerticalCRS *vertDst, - std::vector<CoordinateOperationNNPtr> &res); - - static std::vector<CoordinateOperationNNPtr> - createOperationsGeogToVertFromGeoid(const crs::CRSNNPtr &sourceCRS, - const crs::CRSNNPtr &targetCRS, - const crs::VerticalCRS *vertDst, - Context &context); - - static std::vector<CoordinateOperationNNPtr> - createOperationsGeogToVertWithIntermediateVert( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - const crs::VerticalCRS *vertDst, Context &context); - - static std::vector<CoordinateOperationNNPtr> - createOperationsGeogToVertWithAlternativeGeog( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Context &context); - - static void createOperationsFromDatabaseWithVertCRS( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::GeographicCRS *geogSrc, - const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc, - const crs::VerticalCRS *vertDst, - std::vector<CoordinateOperationNNPtr> &res); - - static void createOperationsGeodToGeod( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::GeodeticCRS *geodSrc, - const crs::GeodeticCRS *geodDst, - std::vector<CoordinateOperationNNPtr> &res); - - static void createOperationsDerivedTo( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::DerivedCRS *derivedSrc, - std::vector<CoordinateOperationNNPtr> &res); - - static void createOperationsBoundToGeog( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::BoundCRS *boundSrc, - const crs::GeographicCRS *geogDst, - std::vector<CoordinateOperationNNPtr> &res); - - static void createOperationsBoundToVert( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::BoundCRS *boundSrc, - const crs::VerticalCRS *vertDst, - std::vector<CoordinateOperationNNPtr> &res); - - static void createOperationsVertToVert( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::VerticalCRS *vertSrc, - const crs::VerticalCRS *vertDst, - std::vector<CoordinateOperationNNPtr> &res); - - static void createOperationsVertToGeog( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::VerticalCRS *vertSrc, - const crs::GeographicCRS *geogDst, - std::vector<CoordinateOperationNNPtr> &res); - - static void createOperationsVertToGeogBallpark( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::VerticalCRS *vertSrc, - const crs::GeographicCRS *geogDst, - std::vector<CoordinateOperationNNPtr> &res); - - static void createOperationsBoundToBound( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::BoundCRS *boundSrc, - const crs::BoundCRS *boundDst, - std::vector<CoordinateOperationNNPtr> &res); - - static void createOperationsCompoundToGeog( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::CompoundCRS *compoundSrc, - const crs::GeographicCRS *geogDst, - std::vector<CoordinateOperationNNPtr> &res); - - static void createOperationsToGeod( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::GeodeticCRS *geodDst, - std::vector<CoordinateOperationNNPtr> &res); - - static void createOperationsCompoundToCompound( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::CompoundCRS *compoundSrc, - const crs::CompoundCRS *compoundDst, - std::vector<CoordinateOperationNNPtr> &res); - - static void createOperationsBoundToCompound( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::BoundCRS *boundSrc, - const crs::CompoundCRS *compoundDst, - std::vector<CoordinateOperationNNPtr> &res); - - static std::vector<CoordinateOperationNNPtr> createOperationsGeogToGeog( - std::vector<CoordinateOperationNNPtr> &res, - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::GeographicCRS *geogSrc, - const crs::GeographicCRS *geogDst); - - static void createOperationsWithDatumPivot( - std::vector<CoordinateOperationNNPtr> &res, - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - const crs::GeodeticCRS *geodSrc, const crs::GeodeticCRS *geodDst, - Context &context); - - static bool - hasPerfectAccuracyResult(const std::vector<CoordinateOperationNNPtr> &res, - const Context &context); - - static void setCRSs(CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS, - const crs::CRSNNPtr &targetCRS); -}; -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -CoordinateOperationFactory::~CoordinateOperationFactory() = default; -//! @endcond - -// --------------------------------------------------------------------------- - -CoordinateOperationFactory::CoordinateOperationFactory() : d(nullptr) {} - -// --------------------------------------------------------------------------- - -/** \brief Find a CoordinateOperation from sourceCRS to targetCRS. - * - * This is a helper of createOperations(), using a coordinate operation - * context - * with no authority factory (so no catalog searching is done), no desired - * accuracy and no area of interest. - * This returns the first operation of the result set of createOperations(), - * or null if none found. - * - * @param sourceCRS source CRS. - * @param targetCRS source CRS. - * @return a CoordinateOperation or nullptr. - */ -CoordinateOperationPtr CoordinateOperationFactory::createOperation( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) const { - auto res = createOperations( - sourceCRS, targetCRS, - CoordinateOperationContext::create(nullptr, nullptr, 0.0)); - if (!res.empty()) { - return res[0]; - } - return nullptr; -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -// --------------------------------------------------------------------------- - -struct PrecomputedOpCharacteristics { - double area_{}; - double accuracy_{}; - bool isPROJExportable_ = false; - bool hasGrids_ = false; - bool gridsAvailable_ = false; - bool gridsKnown_ = false; - size_t stepCount_ = 0; - bool isApprox_ = false; - bool hasBallparkVertical_ = false; - bool isNullTransformation_ = false; - - PrecomputedOpCharacteristics() = default; - PrecomputedOpCharacteristics(double area, double accuracy, - bool isPROJExportable, bool hasGrids, - bool gridsAvailable, bool gridsKnown, - size_t stepCount, bool isApprox, - bool hasBallparkVertical, - bool isNullTransformation) - : area_(area), accuracy_(accuracy), isPROJExportable_(isPROJExportable), - hasGrids_(hasGrids), gridsAvailable_(gridsAvailable), - gridsKnown_(gridsKnown), stepCount_(stepCount), isApprox_(isApprox), - hasBallparkVertical_(hasBallparkVertical), - isNullTransformation_(isNullTransformation) {} -}; - -// --------------------------------------------------------------------------- - -// We could have used a lambda instead of this old-school way, but -// filterAndSort() is already huge. -struct SortFunction { - - const std::map<CoordinateOperation *, PrecomputedOpCharacteristics> ↦ - - explicit SortFunction(const std::map<CoordinateOperation *, - PrecomputedOpCharacteristics> &mapIn) - : map(mapIn) {} - - // Sorting function - // Return true if a < b - bool compare(const CoordinateOperationNNPtr &a, - const CoordinateOperationNNPtr &b) const { - auto iterA = map.find(a.get()); - assert(iterA != map.end()); - auto iterB = map.find(b.get()); - assert(iterB != map.end()); - - // CAUTION: the order of the comparisons is extremely important - // to get the intended result. - - if (iterA->second.isPROJExportable_ && - !iterB->second.isPROJExportable_) { - return true; - } - if (!iterA->second.isPROJExportable_ && - iterB->second.isPROJExportable_) { - return false; - } - - if (!iterA->second.isApprox_ && iterB->second.isApprox_) { - return true; - } - if (iterA->second.isApprox_ && !iterB->second.isApprox_) { - return false; - } - - if (!iterA->second.hasBallparkVertical_ && - iterB->second.hasBallparkVertical_) { - return true; - } - if (iterA->second.hasBallparkVertical_ && - !iterB->second.hasBallparkVertical_) { - return false; - } - - if (!iterA->second.isNullTransformation_ && - iterB->second.isNullTransformation_) { - return true; - } - if (iterA->second.isNullTransformation_ && - !iterB->second.isNullTransformation_) { - return false; - } - - // Operations where grids are all available go before other - if (iterA->second.gridsAvailable_ && !iterB->second.gridsAvailable_) { - return true; - } - if (iterB->second.gridsAvailable_ && !iterA->second.gridsAvailable_) { - return false; - } - - // Operations where grids are all known in our DB go before other - if (iterA->second.gridsKnown_ && !iterB->second.gridsKnown_) { - return true; - } - if (iterB->second.gridsKnown_ && !iterA->second.gridsKnown_) { - return false; - } - - // Operations with known accuracy go before those with unknown accuracy - const double accuracyA = iterA->second.accuracy_; - const double accuracyB = iterB->second.accuracy_; - if (accuracyA >= 0 && accuracyB < 0) { - return true; - } - if (accuracyB >= 0 && accuracyA < 0) { - return false; - } - - if (accuracyA < 0 && accuracyB < 0) { - // unknown accuracy ? then prefer operations with grids, which - // are likely to have best practical accuracy - if (iterA->second.hasGrids_ && !iterB->second.hasGrids_) { - return true; - } - if (!iterA->second.hasGrids_ && iterB->second.hasGrids_) { - return false; - } - } - - // Operations with larger non-zero area of use go before those with - // lower one - const double areaA = iterA->second.area_; - const double areaB = iterB->second.area_; - if (areaA > 0) { - if (areaA > areaB) { - return true; - } - if (areaA < areaB) { - return false; - } - } else if (areaB > 0) { - return false; - } - - // Operations with better accuracy go before those with worse one - if (accuracyA >= 0 && accuracyA < accuracyB) { - return true; - } - if (accuracyB >= 0 && accuracyB < accuracyA) { - return false; - } - - if (accuracyA >= 0 && accuracyA == accuracyB) { - // same accuracy ? then prefer operations without grids - if (!iterA->second.hasGrids_ && iterB->second.hasGrids_) { - return true; - } - if (iterA->second.hasGrids_ && !iterB->second.hasGrids_) { - return false; - } - } - - // The less intermediate steps, the better - if (iterA->second.stepCount_ < iterB->second.stepCount_) { - return true; - } - if (iterB->second.stepCount_ < iterA->second.stepCount_) { - return false; - } - - const auto &a_name = a->nameStr(); - const auto &b_name = b->nameStr(); - // The shorter name, the better ? - if (a_name.size() < b_name.size()) { - return true; - } - if (b_name.size() < a_name.size()) { - return false; - } - - // Arbitrary final criterion. We actually return the greater element - // first, so that "Amersfoort to WGS 84 (4)" is presented before - // "Amersfoort to WGS 84 (3)", which is probably a better guess. - - // Except for French NTF (Paris) to NTF, where the (1) conversion - // should be preferred because in the remarks of (2), it is mentioned - // OGP prefers value from IGN Paris (code 1467)... - if (a_name.find("NTF (Paris) to NTF (1)") != std::string::npos && - b_name.find("NTF (Paris) to NTF (2)") != std::string::npos) { - return true; - } - if (a_name.find("NTF (Paris) to NTF (2)") != std::string::npos && - b_name.find("NTF (Paris) to NTF (1)") != std::string::npos) { - return false; - } - if (a_name.find("NTF (Paris) to RGF93 (1)") != std::string::npos && - b_name.find("NTF (Paris) to RGF93 (2)") != std::string::npos) { - return true; - } - if (a_name.find("NTF (Paris) to RGF93 (2)") != std::string::npos && - b_name.find("NTF (Paris) to RGF93 (1)") != std::string::npos) { - return false; - } - - return a_name > b_name; - } - - bool operator()(const CoordinateOperationNNPtr &a, - const CoordinateOperationNNPtr &b) const { - const bool ret = compare(a, b); -#if 0 - std::cerr << a->nameStr() << " < " << b->nameStr() << " : " << ret << std::endl; -#endif - return ret; - } -}; - -// --------------------------------------------------------------------------- - -static size_t getStepCount(const CoordinateOperationNNPtr &op) { - auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get()); - size_t stepCount = 1; - if (concat) { - stepCount = concat->operations().size(); - } - return stepCount; -} - -// --------------------------------------------------------------------------- - -// Return number of steps that are transformations (and not conversions) -static size_t getTransformationStepCount(const CoordinateOperationNNPtr &op) { - auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get()); - size_t stepCount = 1; - if (concat) { - stepCount = 0; - for (const auto &subOp : concat->operations()) { - if (dynamic_cast<const Conversion *>(subOp.get()) == nullptr) { - stepCount++; - } - } - } - return stepCount; -} - -// --------------------------------------------------------------------------- - -static bool isNullTransformation(const std::string &name) { - if (name.find(" + ") != std::string::npos) - return false; - return starts_with(name, BALLPARK_GEOCENTRIC_TRANSLATION) || - starts_with(name, BALLPARK_GEOGRAPHIC_OFFSET) || - starts_with(name, NULL_GEOGRAPHIC_OFFSET) || - starts_with(name, NULL_GEOCENTRIC_TRANSLATION); -} - -// --------------------------------------------------------------------------- - -struct FilterResults { - - FilterResults(const std::vector<CoordinateOperationNNPtr> &sourceListIn, - const CoordinateOperationContextNNPtr &contextIn, - const metadata::ExtentPtr &extent1In, - const metadata::ExtentPtr &extent2In, - bool forceStrictContainmentTest) - : sourceList(sourceListIn), context(contextIn), extent1(extent1In), - extent2(extent2In), areaOfInterest(context->getAreaOfInterest()), - desiredAccuracy(context->getDesiredAccuracy()), - sourceAndTargetCRSExtentUse( - context->getSourceAndTargetCRSExtentUse()) { - - computeAreaOfInterest(); - filterOut(forceStrictContainmentTest); - } - - FilterResults &andSort() { - sort(); - - // And now that we have a sorted list, we can remove uninteresting - // results - // ... - removeSyntheticNullTransforms(); - removeUninterestingOps(); - removeDuplicateOps(); - removeSyntheticNullTransforms(); - return *this; - } - - // ---------------------------------------------------------------------- - - // cppcheck-suppress functionStatic - const std::vector<CoordinateOperationNNPtr> &getRes() { return res; } - - // ---------------------------------------------------------------------- - private: - const std::vector<CoordinateOperationNNPtr> &sourceList; - const CoordinateOperationContextNNPtr &context; - const metadata::ExtentPtr &extent1; - const metadata::ExtentPtr &extent2; - metadata::ExtentPtr areaOfInterest; - const double desiredAccuracy = context->getDesiredAccuracy(); - const CoordinateOperationContext::SourceTargetCRSExtentUse - sourceAndTargetCRSExtentUse; - - bool hasOpThatContainsAreaOfInterestAndNoGrid = false; - std::vector<CoordinateOperationNNPtr> res{}; - - // ---------------------------------------------------------------------- - void computeAreaOfInterest() { - - // Compute an area of interest from the CRS extent if the user did - // not specify one - if (!areaOfInterest) { - if (sourceAndTargetCRSExtentUse == - CoordinateOperationContext::SourceTargetCRSExtentUse:: - INTERSECTION) { - if (extent1 && extent2) { - areaOfInterest = - extent1->intersection(NN_NO_CHECK(extent2)); - } - } else if (sourceAndTargetCRSExtentUse == - CoordinateOperationContext::SourceTargetCRSExtentUse:: - SMALLEST) { - if (extent1 && extent2) { - if (getPseudoArea(extent1) < getPseudoArea(extent2)) { - areaOfInterest = extent1; - } else { - areaOfInterest = extent2; - } - } else if (extent1) { - areaOfInterest = extent1; - } else { - areaOfInterest = extent2; - } - } - } - } - - // --------------------------------------------------------------------------- - - void filterOut(bool forceStrictContainmentTest) { - - // Filter out operations that do not match the expected accuracy - // and area of use. - const auto spatialCriterion = - forceStrictContainmentTest - ? CoordinateOperationContext::SpatialCriterion:: - STRICT_CONTAINMENT - : context->getSpatialCriterion(); - bool hasOnlyBallpark = true; - bool hasNonBallparkWithoutExtent = false; - bool hasNonBallparkOpWithExtent = false; - const bool allowBallpark = context->getAllowBallparkTransformations(); - for (const auto &op : sourceList) { - if (desiredAccuracy != 0) { - const double accuracy = getAccuracy(op); - if (accuracy < 0 || accuracy > desiredAccuracy) { - continue; - } - } - if (!allowBallpark && op->hasBallparkTransformation()) { - continue; - } - if (areaOfInterest) { - bool emptyIntersection = false; - auto extent = getExtent(op, true, emptyIntersection); - if (!extent) { - if (!op->hasBallparkTransformation()) { - hasNonBallparkWithoutExtent = true; - } - continue; - } - if (!op->hasBallparkTransformation()) { - hasNonBallparkOpWithExtent = true; - } - bool extentContains = - extent->contains(NN_NO_CHECK(areaOfInterest)); - if (!hasOpThatContainsAreaOfInterestAndNoGrid && - extentContains) { - if (!op->hasBallparkTransformation() && - op->gridsNeeded(nullptr, true).empty()) { - hasOpThatContainsAreaOfInterestAndNoGrid = true; - } - } - if (spatialCriterion == - CoordinateOperationContext::SpatialCriterion:: - STRICT_CONTAINMENT && - !extentContains) { - continue; - } - if (spatialCriterion == - CoordinateOperationContext::SpatialCriterion:: - PARTIAL_INTERSECTION && - !extent->intersects(NN_NO_CHECK(areaOfInterest))) { - continue; - } - } else if (sourceAndTargetCRSExtentUse == - CoordinateOperationContext::SourceTargetCRSExtentUse:: - BOTH) { - bool emptyIntersection = false; - auto extent = getExtent(op, true, emptyIntersection); - if (!extent) { - if (!op->hasBallparkTransformation()) { - hasNonBallparkWithoutExtent = true; - } - continue; - } - if (!op->hasBallparkTransformation()) { - hasNonBallparkOpWithExtent = true; - } - bool extentContainsExtent1 = - !extent1 || extent->contains(NN_NO_CHECK(extent1)); - bool extentContainsExtent2 = - !extent2 || extent->contains(NN_NO_CHECK(extent2)); - if (!hasOpThatContainsAreaOfInterestAndNoGrid && - extentContainsExtent1 && extentContainsExtent2) { - if (!op->hasBallparkTransformation() && - op->gridsNeeded(nullptr, true).empty()) { - hasOpThatContainsAreaOfInterestAndNoGrid = true; - } - } - if (spatialCriterion == - CoordinateOperationContext::SpatialCriterion:: - STRICT_CONTAINMENT) { - if (!extentContainsExtent1 || !extentContainsExtent2) { - continue; - } - } else if (spatialCriterion == - CoordinateOperationContext::SpatialCriterion:: - PARTIAL_INTERSECTION) { - bool extentIntersectsExtent1 = - !extent1 || extent->intersects(NN_NO_CHECK(extent1)); - bool extentIntersectsExtent2 = - extent2 && extent->intersects(NN_NO_CHECK(extent2)); - if (!extentIntersectsExtent1 || !extentIntersectsExtent2) { - continue; - } - } - } - if (!op->hasBallparkTransformation()) { - hasOnlyBallpark = false; - } - res.emplace_back(op); - } - - // In case no operation has an extent and no result is found, - // retain all initial operations that match accuracy criterion. - if ((res.empty() && !hasNonBallparkOpWithExtent) || - (hasOnlyBallpark && hasNonBallparkWithoutExtent)) { - for (const auto &op : sourceList) { - if (desiredAccuracy != 0) { - const double accuracy = getAccuracy(op); - if (accuracy < 0 || accuracy > desiredAccuracy) { - continue; - } - } - if (!allowBallpark && op->hasBallparkTransformation()) { - continue; - } - res.emplace_back(op); - } - } - } - - // ---------------------------------------------------------------------- - - void sort() { - - // Precompute a number of parameters for each operation that will be - // useful for the sorting. - std::map<CoordinateOperation *, PrecomputedOpCharacteristics> map; - const auto gridAvailabilityUse = context->getGridAvailabilityUse(); - for (const auto &op : res) { - bool dummy = false; - auto extentOp = getExtent(op, true, dummy); - double area = 0.0; - if (extentOp) { - if (areaOfInterest) { - area = getPseudoArea( - extentOp->intersection(NN_NO_CHECK(areaOfInterest))); - } else if (extent1 && extent2) { - auto x = extentOp->intersection(NN_NO_CHECK(extent1)); - auto y = extentOp->intersection(NN_NO_CHECK(extent2)); - area = getPseudoArea(x) + getPseudoArea(y) - - ((x && y) - ? getPseudoArea(x->intersection(NN_NO_CHECK(y))) - : 0.0); - } else if (extent1) { - area = getPseudoArea( - extentOp->intersection(NN_NO_CHECK(extent1))); - } else if (extent2) { - area = getPseudoArea( - extentOp->intersection(NN_NO_CHECK(extent2))); - } else { - area = getPseudoArea(extentOp); - } - } - - bool hasGrids = false; - bool gridsAvailable = true; - bool gridsKnown = true; - if (context->getAuthorityFactory()) { - const auto gridsNeeded = op->gridsNeeded( - context->getAuthorityFactory()->databaseContext(), - gridAvailabilityUse == - CoordinateOperationContext::GridAvailabilityUse:: - KNOWN_AVAILABLE); - for (const auto &gridDesc : gridsNeeded) { - hasGrids = true; - if (gridAvailabilityUse == - CoordinateOperationContext::GridAvailabilityUse:: - USE_FOR_SORTING && - !gridDesc.available) { - gridsAvailable = false; - } - if (gridDesc.packageName.empty() && - !(!gridDesc.url.empty() && gridDesc.openLicense) && - !gridDesc.available) { - gridsKnown = false; - } - } - } - - const auto stepCount = getStepCount(op); - - bool isPROJExportable = false; - auto formatter = io::PROJStringFormatter::create(); - try { - op->exportToPROJString(formatter.get()); - // Grids might be missing, but at least this is something - // PROJ could potentially process - isPROJExportable = true; - } catch (const std::exception &) { - } - -#if 0 - std::cerr << op->nameStr() << " "; - std::cerr << area << " "; - std::cerr << getAccuracy(op) << " "; - std::cerr << isPROJExportable << " "; - std::cerr << hasGrids << " "; - std::cerr << gridsAvailable << " "; - std::cerr << gridsKnown << " "; - std::cerr << stepCount << " "; - std::cerr << op->hasBallparkTransformation() << " "; - std::cerr << isNullTransformation(op->nameStr()) << " "; - std::cerr << std::endl; -#endif - map[op.get()] = PrecomputedOpCharacteristics( - area, getAccuracy(op), isPROJExportable, hasGrids, - gridsAvailable, gridsKnown, stepCount, - op->hasBallparkTransformation(), - op->nameStr().find("ballpark vertical transformation") != - std::string::npos, - isNullTransformation(op->nameStr())); - } - - // Sort ! - SortFunction sortFunc(map); - std::sort(res.begin(), res.end(), sortFunc); - -// Debug code to check consistency of the sort function -#ifdef DEBUG_SORT - constexpr bool debugSort = true; -#elif !defined(NDEBUG) - const bool debugSort = getenv("PROJ_DEBUG_SORT_FUNCT") != nullptr; -#endif -#if defined(DEBUG_SORT) || !defined(NDEBUG) - if (debugSort) { - const bool assertIfIssue = - !(getenv("PROJ_DEBUG_SORT_FUNCT_ASSERT") != nullptr); - for (size_t i = 0; i < res.size(); ++i) { - for (size_t j = i + 1; j < res.size(); ++j) { - if (sortFunc(res[j], res[i])) { -#ifdef DEBUG_SORT - std::cerr << "Sorting issue with entry " << i << "(" - << res[i]->nameStr() << ") and " << j << "(" - << res[j]->nameStr() << ")" << std::endl; -#endif - if (assertIfIssue) { - assert(false); - } - } - } - } - } -#endif - } - - // ---------------------------------------------------------------------- - - void removeSyntheticNullTransforms() { - - // 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 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(); - if (opLast->hasBallparkTransformation() || - isNullTransformation(opLast->nameStr())) { - std::vector<CoordinateOperationNNPtr> resTemp; - for (size_t i = 0; i < res.size() - 1; i++) { - resTemp.emplace_back(res[i]); - } - res = std::move(resTemp); - } - } - } - - // ---------------------------------------------------------------------- - - void removeUninterestingOps() { - - // Eliminate operations that bring nothing, ie for a given area of use, - // do not keep operations that have similar or worse accuracy, but - // involve more (non conversion) steps - std::vector<CoordinateOperationNNPtr> resTemp; - metadata::ExtentPtr lastExtent; - double lastAccuracy = -1; - size_t lastStepCount = 0; - CoordinateOperationPtr lastOp; - - bool first = true; - for (const auto &op : res) { - const auto curAccuracy = getAccuracy(op); - bool dummy = false; - const auto curExtent = getExtent(op, true, dummy); - const auto curStepCount = getTransformationStepCount(op); - - if (first) { - resTemp.emplace_back(op); - first = false; - } else { - if (lastOp->_isEquivalentTo(op.get())) { - continue; - } - const bool sameExtent = - ((!curExtent && !lastExtent) || - (curExtent && lastExtent && - curExtent->contains(NN_NO_CHECK(lastExtent)) && - lastExtent->contains(NN_NO_CHECK(curExtent)))); - if (((curAccuracy >= lastAccuracy && lastAccuracy >= 0) || - (curAccuracy < 0 && lastAccuracy >= 0)) && - sameExtent && curStepCount > lastStepCount) { - continue; - } - - resTemp.emplace_back(op); - } - - lastOp = op.as_nullable(); - lastStepCount = curStepCount; - lastExtent = curExtent; - lastAccuracy = curAccuracy; - } - res = std::move(resTemp); - } - - // ---------------------------------------------------------------------- - - // cppcheck-suppress functionStatic - void removeDuplicateOps() { - - if (res.size() <= 1) { - return; - } - - // When going from EPSG:4807 (NTF Paris) to EPSG:4171 (RGC93), we get - // EPSG:7811, NTF (Paris) to RGF93 (2), 1 m - // and unknown id, NTF (Paris) to NTF (1) + Inverse of RGF93 to NTF (2), - // 1 m - // both have same PROJ string and extent - // Do not keep the later (that has more steps) as it adds no value. - - std::set<std::string> setPROJPlusExtent; - std::vector<CoordinateOperationNNPtr> resTemp; - for (const auto &op : res) { - auto formatter = io::PROJStringFormatter::create(); - try { - std::string key(op->exportToPROJString(formatter.get())); - bool dummy = false; - auto extentOp = getExtent(op, true, dummy); - if (extentOp) { - const auto &geogElts = extentOp->geographicElements(); - if (geogElts.size() == 1) { - auto bbox = dynamic_cast< - const metadata::GeographicBoundingBox *>( - geogElts[0].get()); - if (bbox) { - double w = bbox->westBoundLongitude(); - double s = bbox->southBoundLatitude(); - double e = bbox->eastBoundLongitude(); - double n = bbox->northBoundLatitude(); - key += "-"; - key += toString(w); - key += "-"; - key += toString(s); - key += "-"; - key += toString(e); - key += "-"; - key += toString(n); - } - } - } - - if (setPROJPlusExtent.find(key) == setPROJPlusExtent.end()) { - resTemp.emplace_back(op); - setPROJPlusExtent.insert(key); - } - } catch (const std::exception &) { - resTemp.emplace_back(op); - } - } - res = std::move(resTemp); - } -}; - -// --------------------------------------------------------------------------- - -/** \brief Filter operations and sort them given context. - * - * If a desired accuracy is specified, only keep operations whose accuracy - * is at least the desired one. - * If an area of interest is specified, only keep operations whose area of - * use include the area of interest. - * Then sort remaining operations by descending area of use, and increasing - * accuracy. - */ -static std::vector<CoordinateOperationNNPtr> -filterAndSort(const std::vector<CoordinateOperationNNPtr> &sourceList, - const CoordinateOperationContextNNPtr &context, - const metadata::ExtentPtr &extent1, - const metadata::ExtentPtr &extent2) { -#ifdef TRACE_CREATE_OPERATIONS - ENTER_FUNCTION(); - logTrace("number of results before filter and sort: " + - toString(static_cast<int>(sourceList.size()))); -#endif - auto resFiltered = - FilterResults(sourceList, context, extent1, extent2, false) - .andSort() - .getRes(); -#ifdef TRACE_CREATE_OPERATIONS - logTrace("number of results after filter and sort: " + - toString(static_cast<int>(resFiltered.size()))); -#endif - return resFiltered; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -// Apply the inverse() method on all elements of the input list -static std::vector<CoordinateOperationNNPtr> -applyInverse(const std::vector<CoordinateOperationNNPtr> &list) { - auto res = list; - for (auto &op : res) { -#ifdef DEBUG - auto opNew = op->inverse(); - assert(opNew->targetCRS()->isEquivalentTo(op->sourceCRS().get())); - assert(opNew->sourceCRS()->isEquivalentTo(op->targetCRS().get())); - op = opNew; -#else - op = op->inverse(); -#endif - } - return res; -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -void CoordinateOperationFactory::Private::buildCRSIds( - const crs::CRSNNPtr &crs, Private::Context &context, - std::list<std::pair<std::string, std::string>> &ids) { - const auto &authFactory = context.context->getAuthorityFactory(); - assert(authFactory); - for (const auto &id : crs->identifiers()) { - const auto &authName = *(id->codeSpace()); - const auto &code = id->code(); - if (!authName.empty()) { - const auto tmpAuthFactory = io::AuthorityFactory::create( - authFactory->databaseContext(), authName); - try { - // Consistency check for the ID attached to the object. - // See https://github.com/OSGeo/PROJ/issues/1982 where EPSG:4656 - // is attached to a GeographicCRS whereas it is a ProjectedCRS - if (tmpAuthFactory->createCoordinateReferenceSystem(code) - ->_isEquivalentTo( - crs.get(), - util::IComparable::Criterion:: - EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)) { - ids.emplace_back(authName, code); - } else { - // TODO? log this inconsistency - } - } catch (const std::exception &) { - // TODO? log this inconsistency - } - } - } - if (ids.empty()) { - std::vector<io::AuthorityFactory::ObjectType> allowedObjects; - auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(crs.get()); - if (geogCRS) { - allowedObjects.push_back( - geogCRS->coordinateSystem()->axisList().size() == 2 - ? io::AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS - : io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS); - } else if (dynamic_cast<crs::ProjectedCRS *>(crs.get())) { - allowedObjects.push_back( - io::AuthorityFactory::ObjectType::PROJECTED_CRS); - } else if (dynamic_cast<crs::VerticalCRS *>(crs.get())) { - allowedObjects.push_back( - io::AuthorityFactory::ObjectType::VERTICAL_CRS); - } - if (!allowedObjects.empty()) { - - const std::pair<io::AuthorityFactory::ObjectType, std::string> key( - allowedObjects[0], crs->nameStr()); - auto iter = context.cacheNameToCRS.find(key); - if (iter != context.cacheNameToCRS.end()) { - ids = iter->second; - return; - } - - const auto &authFactoryName = authFactory->getAuthority(); - try { - const auto tmpAuthFactory = io::AuthorityFactory::create( - authFactory->databaseContext(), - (authFactoryName.empty() || authFactoryName == "any") - ? std::string() - : authFactoryName); - - auto matches = tmpAuthFactory->createObjectsFromName( - crs->nameStr(), allowedObjects, false, 2); - if (matches.size() == 1 && - crs->_isEquivalentTo( - matches.front().get(), - util::IComparable::Criterion::EQUIVALENT) && - !matches.front()->identifiers().empty()) { - const auto &tmpIds = matches.front()->identifiers(); - ids.emplace_back(*(tmpIds[0]->codeSpace()), - tmpIds[0]->code()); - } - } catch (const std::exception &) { - } - context.cacheNameToCRS[key] = ids; - } - } -} - -// --------------------------------------------------------------------------- - -static std::vector<std::string> -getCandidateAuthorities(const io::AuthorityFactoryPtr &authFactory, - const std::string &srcAuthName, - const std::string &targetAuthName) { - const auto &authFactoryName = authFactory->getAuthority(); - std::vector<std::string> authorities; - if (authFactoryName == "any") { - authorities.emplace_back(); - } - if (authFactoryName.empty()) { - authorities = authFactory->databaseContext()->getAllowedAuthorities( - srcAuthName, targetAuthName); - if (authorities.empty()) { - authorities.emplace_back(); - } - } else { - authorities.emplace_back(authFactoryName); - } - return authorities; -} - -// --------------------------------------------------------------------------- - -// Look in the authority registry for operations from sourceCRS to targetCRS -std::vector<CoordinateOperationNNPtr> -CoordinateOperationFactory::Private::findOpsInRegistryDirect( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, bool &resNonEmptyBeforeFiltering) { - const auto &authFactory = context.context->getAuthorityFactory(); - assert(authFactory); - -#ifdef TRACE_CREATE_OPERATIONS - ENTER_BLOCK("findOpsInRegistryDirect(" + objectAsStr(sourceCRS.get()) + - " --> " + objectAsStr(targetCRS.get()) + ")"); -#endif - - resNonEmptyBeforeFiltering = false; - std::list<std::pair<std::string, std::string>> sourceIds; - std::list<std::pair<std::string, std::string>> targetIds; - buildCRSIds(sourceCRS, context, sourceIds); - buildCRSIds(targetCRS, context, targetIds); - - const auto gridAvailabilityUse = context.context->getGridAvailabilityUse(); - for (const auto &idSrc : sourceIds) { - const auto &srcAuthName = idSrc.first; - const auto &srcCode = idSrc.second; - for (const auto &idTarget : targetIds) { - const auto &targetAuthName = idTarget.first; - const auto &targetCode = idTarget.second; - - const auto authorities(getCandidateAuthorities( - authFactory, srcAuthName, targetAuthName)); - std::vector<CoordinateOperationNNPtr> res; - for (const auto &authority : authorities) { - const auto authName = - authority == "any" ? std::string() : authority; - const auto tmpAuthFactory = io::AuthorityFactory::create( - authFactory->databaseContext(), authName); - auto resTmp = - tmpAuthFactory->createFromCoordinateReferenceSystemCodes( - srcAuthName, srcCode, targetAuthName, targetCode, - context.context->getUsePROJAlternativeGridNames(), - gridAvailabilityUse == - CoordinateOperationContext:: - GridAvailabilityUse:: - DISCARD_OPERATION_IF_MISSING_GRID || - gridAvailabilityUse == - CoordinateOperationContext:: - GridAvailabilityUse::KNOWN_AVAILABLE, - gridAvailabilityUse == - CoordinateOperationContext::GridAvailabilityUse:: - KNOWN_AVAILABLE, - context.context->getDiscardSuperseded(), true, false, - context.extent1, context.extent2); - res.insert(res.end(), resTmp.begin(), resTmp.end()); - if (authName == "PROJ") { - continue; - } - if (!res.empty()) { - resNonEmptyBeforeFiltering = true; - auto resFiltered = - FilterResults(res, context.context, context.extent1, - context.extent2, false) - .getRes(); -#ifdef TRACE_CREATE_OPERATIONS - logTrace("filtering reduced from " + - toString(static_cast<int>(res.size())) + " to " + - toString(static_cast<int>(resFiltered.size()))); -#endif - return resFiltered; - } - } - } - } - return std::vector<CoordinateOperationNNPtr>(); -} - -// --------------------------------------------------------------------------- - -// Look in the authority registry for operations to targetCRS -std::vector<CoordinateOperationNNPtr> -CoordinateOperationFactory::Private::findOpsInRegistryDirectTo( - const crs::CRSNNPtr &targetCRS, Private::Context &context) { -#ifdef TRACE_CREATE_OPERATIONS - ENTER_BLOCK("findOpsInRegistryDirectTo({any} -->" + - objectAsStr(targetCRS.get()) + ")"); -#endif - - const auto &authFactory = context.context->getAuthorityFactory(); - assert(authFactory); - - std::list<std::pair<std::string, std::string>> ids; - buildCRSIds(targetCRS, context, ids); - - const auto gridAvailabilityUse = context.context->getGridAvailabilityUse(); - for (const auto &id : ids) { - const auto &targetAuthName = id.first; - const auto &targetCode = id.second; - - const auto authorities(getCandidateAuthorities( - authFactory, targetAuthName, targetAuthName)); - for (const auto &authority : authorities) { - const auto tmpAuthFactory = io::AuthorityFactory::create( - authFactory->databaseContext(), - authority == "any" ? std::string() : authority); - auto res = tmpAuthFactory->createFromCoordinateReferenceSystemCodes( - std::string(), std::string(), targetAuthName, targetCode, - context.context->getUsePROJAlternativeGridNames(), - - gridAvailabilityUse == - CoordinateOperationContext::GridAvailabilityUse:: - DISCARD_OPERATION_IF_MISSING_GRID || - gridAvailabilityUse == - CoordinateOperationContext::GridAvailabilityUse:: - KNOWN_AVAILABLE, - gridAvailabilityUse == CoordinateOperationContext:: - GridAvailabilityUse::KNOWN_AVAILABLE, - context.context->getDiscardSuperseded(), true, true, - context.extent1, context.extent2); - if (!res.empty()) { - auto resFiltered = - FilterResults(res, context.context, context.extent1, - context.extent2, false) - .getRes(); -#ifdef TRACE_CREATE_OPERATIONS - logTrace("filtering reduced from " + - toString(static_cast<int>(res.size())) + " to " + - toString(static_cast<int>(resFiltered.size()))); -#endif - return resFiltered; - } - } - } - return std::vector<CoordinateOperationNNPtr>(); -} - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -// Look in the authority registry for operations from sourceCRS to targetCRS -// using an intermediate pivot -std::vector<CoordinateOperationNNPtr> -CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, - bool useCreateBetweenGeodeticCRSWithDatumBasedIntermediates) { - -#ifdef TRACE_CREATE_OPERATIONS - ENTER_BLOCK("findsOpsInRegistryWithIntermediate(" + - objectAsStr(sourceCRS.get()) + " --> " + - objectAsStr(targetCRS.get()) + ")"); -#endif - - const auto &authFactory = context.context->getAuthorityFactory(); - assert(authFactory); - - std::list<std::pair<std::string, std::string>> sourceIds; - std::list<std::pair<std::string, std::string>> targetIds; - buildCRSIds(sourceCRS, context, sourceIds); - buildCRSIds(targetCRS, context, targetIds); - - const auto gridAvailabilityUse = context.context->getGridAvailabilityUse(); - for (const auto &idSrc : sourceIds) { - const auto &srcAuthName = idSrc.first; - const auto &srcCode = idSrc.second; - for (const auto &idTarget : targetIds) { - const auto &targetAuthName = idTarget.first; - const auto &targetCode = idTarget.second; - - const auto authorities(getCandidateAuthorities( - authFactory, srcAuthName, targetAuthName)); - assert(!authorities.empty()); - - const auto tmpAuthFactory = io::AuthorityFactory::create( - authFactory->databaseContext(), - (authFactory->getAuthority() == "any" || authorities.size() > 1) - ? std::string() - : authorities.front()); - - std::vector<CoordinateOperationNNPtr> res; - if (useCreateBetweenGeodeticCRSWithDatumBasedIntermediates) { - res = - tmpAuthFactory - ->createBetweenGeodeticCRSWithDatumBasedIntermediates( - sourceCRS, srcAuthName, srcCode, targetCRS, - targetAuthName, targetCode, - context.context->getUsePROJAlternativeGridNames(), - gridAvailabilityUse == - CoordinateOperationContext:: - GridAvailabilityUse:: - DISCARD_OPERATION_IF_MISSING_GRID || - gridAvailabilityUse == - CoordinateOperationContext:: - GridAvailabilityUse::KNOWN_AVAILABLE, - gridAvailabilityUse == - CoordinateOperationContext:: - GridAvailabilityUse::KNOWN_AVAILABLE, - context.context->getDiscardSuperseded(), - authFactory->getAuthority() != "any" && - authorities.size() > 1 - ? authorities - : std::vector<std::string>(), - context.extent1, context.extent2); - } else { - io::AuthorityFactory::ObjectType intermediateObjectType = - io::AuthorityFactory::ObjectType::CRS; - - // If doing GeogCRS --> GeogCRS, only use GeogCRS as - // intermediate CRS - // Avoid weird behavior when doing NAD83 -> NAD83(2011) - // that would go through NAVD88 otherwise. - if (context.context->getIntermediateCRS().empty() && - dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()) && - dynamic_cast<const crs::GeographicCRS *>(targetCRS.get())) { - intermediateObjectType = - io::AuthorityFactory::ObjectType::GEOGRAPHIC_CRS; - } - res = tmpAuthFactory->createFromCRSCodesWithIntermediates( - srcAuthName, srcCode, targetAuthName, targetCode, - context.context->getUsePROJAlternativeGridNames(), - gridAvailabilityUse == - CoordinateOperationContext::GridAvailabilityUse:: - DISCARD_OPERATION_IF_MISSING_GRID || - gridAvailabilityUse == - CoordinateOperationContext::GridAvailabilityUse:: - KNOWN_AVAILABLE, - gridAvailabilityUse == - CoordinateOperationContext::GridAvailabilityUse:: - KNOWN_AVAILABLE, - context.context->getDiscardSuperseded(), - context.context->getIntermediateCRS(), - intermediateObjectType, - authFactory->getAuthority() != "any" && - authorities.size() > 1 - ? authorities - : std::vector<std::string>(), - context.extent1, context.extent2); - } - if (!res.empty()) { - - auto resFiltered = - FilterResults(res, context.context, context.extent1, - context.extent2, false) - .getRes(); -#ifdef TRACE_CREATE_OPERATIONS - logTrace("filtering reduced from " + - toString(static_cast<int>(res.size())) + " to " + - toString(static_cast<int>(resFiltered.size()))); -#endif - return resFiltered; - } - } - } - return std::vector<CoordinateOperationNNPtr>(); -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress -static TransformationNNPtr -createBallparkGeographicOffset(const crs::CRSNNPtr &sourceCRS, - const crs::CRSNNPtr &targetCRS, - const io::DatabaseContextPtr &dbContext) { - - const crs::GeographicCRS *geogSrc = - dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); - const crs::GeographicCRS *geogDst = - dynamic_cast<const crs::GeographicCRS *>(targetCRS.get()); - const bool isSameDatum = geogSrc && geogDst && - geogSrc->datumNonNull(dbContext)->_isEquivalentTo( - geogDst->datumNonNull(dbContext).get(), - util::IComparable::Criterion::EQUIVALENT); - - auto name = buildOpName(isSameDatum ? NULL_GEOGRAPHIC_OFFSET - : BALLPARK_GEOGRAPHIC_OFFSET, - sourceCRS, targetCRS); - - const auto &sourceCRSExtent = getExtent(sourceCRS); - const auto &targetCRSExtent = getExtent(targetCRS); - const bool sameExtent = - sourceCRSExtent && targetCRSExtent && - sourceCRSExtent->_isEquivalentTo( - targetCRSExtent.get(), util::IComparable::Criterion::EQUIVALENT); - - util::PropertyMap map; - map.set(common::IdentifiedObject::NAME_KEY, name) - .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - sameExtent ? NN_NO_CHECK(sourceCRSExtent) - : metadata::Extent::WORLD); - const common::Angle angle0(0); - - std::vector<metadata::PositionalAccuracyNNPtr> accuracies; - if (isSameDatum) { - accuracies.emplace_back(metadata::PositionalAccuracy::create("0")); - } - - if (dynamic_cast<const crs::SingleCRS *>(sourceCRS.get()) - ->coordinateSystem() - ->axisList() - .size() == 3 || - dynamic_cast<const crs::SingleCRS *>(targetCRS.get()) - ->coordinateSystem() - ->axisList() - .size() == 3) { - return Transformation::createGeographic3DOffsets( - map, sourceCRS, targetCRS, angle0, angle0, common::Length(0), - accuracies); - } else { - return Transformation::createGeographic2DOffsets( - map, sourceCRS, targetCRS, angle0, angle0, accuracies); - } -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -// --------------------------------------------------------------------------- - -struct MyPROJStringExportableGeodToGeod final - : public io::IPROJStringExportable { - crs::GeodeticCRSPtr geodSrc{}; - crs::GeodeticCRSPtr geodDst{}; - - MyPROJStringExportableGeodToGeod(const crs::GeodeticCRSPtr &geodSrcIn, - const crs::GeodeticCRSPtr &geodDstIn) - : geodSrc(geodSrcIn), geodDst(geodDstIn) {} - - ~MyPROJStringExportableGeodToGeod() override; - - void - // cppcheck-suppress functionStatic - _exportToPROJString(io::PROJStringFormatter *formatter) const override { - - formatter->startInversion(); - geodSrc->_exportToPROJString(formatter); - formatter->stopInversion(); - geodDst->_exportToPROJString(formatter); - } -}; - -MyPROJStringExportableGeodToGeod::~MyPROJStringExportableGeodToGeod() = default; - -// --------------------------------------------------------------------------- - -struct MyPROJStringExportableHorizVertical final - : public io::IPROJStringExportable { - CoordinateOperationPtr horizTransform{}; - CoordinateOperationPtr verticalTransform{}; - crs::GeographicCRSPtr geogDst{}; - - MyPROJStringExportableHorizVertical( - const CoordinateOperationPtr &horizTransformIn, - const CoordinateOperationPtr &verticalTransformIn, - const crs::GeographicCRSPtr &geogDstIn) - : horizTransform(horizTransformIn), - verticalTransform(verticalTransformIn), geogDst(geogDstIn) {} - - ~MyPROJStringExportableHorizVertical() override; - - void - // cppcheck-suppress functionStatic - _exportToPROJString(io::PROJStringFormatter *formatter) const override { - - formatter->pushOmitZUnitConversion(); - - horizTransform->_exportToPROJString(formatter); - - formatter->startInversion(); - geogDst->addAngularUnitConvertAndAxisSwap(formatter); - formatter->stopInversion(); - - formatter->popOmitZUnitConversion(); - - formatter->pushOmitHorizontalConversionInVertTransformation(); - verticalTransform->_exportToPROJString(formatter); - formatter->popOmitHorizontalConversionInVertTransformation(); - - formatter->pushOmitZUnitConversion(); - geogDst->addAngularUnitConvertAndAxisSwap(formatter); - formatter->popOmitZUnitConversion(); - } -}; - -MyPROJStringExportableHorizVertical::~MyPROJStringExportableHorizVertical() = - default; - -// --------------------------------------------------------------------------- - -struct MyPROJStringExportableHorizVerticalHorizPROJBased final - : public io::IPROJStringExportable { - CoordinateOperationPtr opSrcCRSToGeogCRS{}; - CoordinateOperationPtr verticalTransform{}; - CoordinateOperationPtr opGeogCRStoDstCRS{}; - crs::GeographicCRSPtr interpolationGeogCRS{}; - - MyPROJStringExportableHorizVerticalHorizPROJBased( - const CoordinateOperationPtr &opSrcCRSToGeogCRSIn, - const CoordinateOperationPtr &verticalTransformIn, - const CoordinateOperationPtr &opGeogCRStoDstCRSIn, - const crs::GeographicCRSPtr &interpolationGeogCRSIn) - : opSrcCRSToGeogCRS(opSrcCRSToGeogCRSIn), - verticalTransform(verticalTransformIn), - opGeogCRStoDstCRS(opGeogCRStoDstCRSIn), - interpolationGeogCRS(interpolationGeogCRSIn) {} - - ~MyPROJStringExportableHorizVerticalHorizPROJBased() override; - - void - // cppcheck-suppress functionStatic - _exportToPROJString(io::PROJStringFormatter *formatter) const override { - - formatter->pushOmitZUnitConversion(); - - opSrcCRSToGeogCRS->_exportToPROJString(formatter); - - formatter->startInversion(); - interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter); - formatter->stopInversion(); - - formatter->popOmitZUnitConversion(); - - formatter->pushOmitHorizontalConversionInVertTransformation(); - verticalTransform->_exportToPROJString(formatter); - formatter->popOmitHorizontalConversionInVertTransformation(); - - formatter->pushOmitZUnitConversion(); - - interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter); - - opGeogCRStoDstCRS->_exportToPROJString(formatter); - - formatter->popOmitZUnitConversion(); - } -}; - -MyPROJStringExportableHorizVerticalHorizPROJBased:: - ~MyPROJStringExportableHorizVerticalHorizPROJBased() = default; - -//! @endcond - -} // namespace operation -NS_PROJ_END - -#if 0 -namespace dropbox{ namespace oxygen { -template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableGeodToGeod>>::~nn() = default; -template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableHorizVertical>>::~nn() = default; -template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableHorizVerticalHorizPROJBased>>::~nn() = default; -}} -#endif - -NS_PROJ_START -namespace operation { - -//! @cond Doxygen_Suppress - -// --------------------------------------------------------------------------- - -static std::string buildTransfName(const std::string &srcName, - const std::string &targetName) { - std::string name("Transformation from "); - name += srcName; - name += " to "; - name += targetName; - return name; -} - -// --------------------------------------------------------------------------- - -static CoordinateOperationNNPtr -createGeodToGeodPROJBased(const crs::CRSNNPtr &geodSrc, - const crs::CRSNNPtr &geodDst) { - - auto exportable = util::nn_make_shared<MyPROJStringExportableGeodToGeod>( - util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(geodSrc), - util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(geodDst)); - - auto properties = util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - buildTransfName(geodSrc->nameStr(), geodDst->nameStr())); - return createPROJBased(properties, exportable, geodSrc, geodDst, nullptr, - {}, false); -} - -// --------------------------------------------------------------------------- - -static std::string -getRemarks(const std::vector<operation::CoordinateOperationNNPtr> &ops) { - std::string remarks; - for (const auto &op : ops) { - const auto &opRemarks = op->remarks(); - if (!opRemarks.empty()) { - if (!remarks.empty()) { - remarks += '\n'; - } - - std::string opName(op->nameStr()); - if (starts_with(opName, INVERSE_OF)) { - opName = opName.substr(INVERSE_OF.size()); - } - - remarks += "For "; - remarks += opName; - - const auto &ids = op->identifiers(); - if (!ids.empty()) { - std::string authority(*ids.front()->codeSpace()); - if (starts_with(authority, "INVERSE(") && - authority.back() == ')') { - authority = authority.substr(strlen("INVERSE("), - authority.size() - 1 - - strlen("INVERSE(")); - } - if (starts_with(authority, "DERIVED_FROM(") && - authority.back() == ')') { - authority = authority.substr(strlen("DERIVED_FROM("), - authority.size() - 1 - - strlen("DERIVED_FROM(")); - } - - remarks += " ("; - remarks += authority; - remarks += ':'; - remarks += ids.front()->code(); - remarks += ')'; - } - remarks += ": "; - remarks += opRemarks; - } - } - return remarks; -} - -// --------------------------------------------------------------------------- - -static CoordinateOperationNNPtr createHorizVerticalPROJBased( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - const operation::CoordinateOperationNNPtr &horizTransform, - const operation::CoordinateOperationNNPtr &verticalTransform, - bool checkExtent) { - - auto geogDst = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(targetCRS); - assert(geogDst); - - auto exportable = util::nn_make_shared<MyPROJStringExportableHorizVertical>( - horizTransform, verticalTransform, geogDst); - - const bool horizTransformIsNoOp = - starts_with(horizTransform->nameStr(), NULL_GEOGRAPHIC_OFFSET) && - horizTransform->nameStr().find(" + ") == std::string::npos; - if (horizTransformIsNoOp) { - auto properties = util::PropertyMap(); - properties.set(common::IdentifiedObject::NAME_KEY, - verticalTransform->nameStr()); - bool dummy = false; - auto extent = getExtent(verticalTransform, true, dummy); - if (extent) { - properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - NN_NO_CHECK(extent)); - } - const auto &remarks = verticalTransform->remarks(); - if (!remarks.empty()) { - properties.set(common::IdentifiedObject::REMARKS_KEY, remarks); - } - return createPROJBased( - properties, exportable, sourceCRS, targetCRS, nullptr, - verticalTransform->coordinateOperationAccuracies(), - verticalTransform->hasBallparkTransformation()); - } else { - bool emptyIntersection = false; - auto ops = std::vector<CoordinateOperationNNPtr>{horizTransform, - verticalTransform}; - auto extent = getExtent(ops, true, emptyIntersection); - if (checkExtent && emptyIntersection) { - std::string msg( - "empty intersection of area of validity of concatenated " - "operations"); - throw InvalidOperationEmptyIntersection(msg); - } - auto properties = util::PropertyMap(); - properties.set(common::IdentifiedObject::NAME_KEY, - computeConcatenatedName(ops)); - - if (extent) { - properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - NN_NO_CHECK(extent)); - } - - const auto remarks = getRemarks(ops); - if (!remarks.empty()) { - properties.set(common::IdentifiedObject::REMARKS_KEY, remarks); - } - - std::vector<metadata::PositionalAccuracyNNPtr> accuracies; - const double accuracy = getAccuracy(ops); - if (accuracy >= 0.0) { - accuracies.emplace_back( - metadata::PositionalAccuracy::create(toString(accuracy))); - } - - return createPROJBased( - properties, exportable, sourceCRS, targetCRS, nullptr, accuracies, - horizTransform->hasBallparkTransformation() || - verticalTransform->hasBallparkTransformation()); - } -} - -// --------------------------------------------------------------------------- - -static CoordinateOperationNNPtr createHorizVerticalHorizPROJBased( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - const operation::CoordinateOperationNNPtr &opSrcCRSToGeogCRS, - const operation::CoordinateOperationNNPtr &verticalTransform, - const operation::CoordinateOperationNNPtr &opGeogCRStoDstCRS, - const crs::GeographicCRSPtr &interpolationGeogCRS, bool checkExtent) { - - auto exportable = - util::nn_make_shared<MyPROJStringExportableHorizVerticalHorizPROJBased>( - opSrcCRSToGeogCRS, verticalTransform, opGeogCRStoDstCRS, - interpolationGeogCRS); - - std::vector<CoordinateOperationNNPtr> ops; - if (!(starts_with(opSrcCRSToGeogCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) && - opSrcCRSToGeogCRS->nameStr().find(" + ") == std::string::npos)) { - ops.emplace_back(opSrcCRSToGeogCRS); - } - ops.emplace_back(verticalTransform); - if (!(starts_with(opGeogCRStoDstCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) && - opGeogCRStoDstCRS->nameStr().find(" + ") == std::string::npos)) { - ops.emplace_back(opGeogCRStoDstCRS); - } - - bool hasBallparkTransformation = false; - for (const auto &op : ops) { - hasBallparkTransformation |= op->hasBallparkTransformation(); - } - bool emptyIntersection = false; - auto extent = getExtent(ops, false, emptyIntersection); - if (checkExtent && emptyIntersection) { - std::string msg( - "empty intersection of area of validity of concatenated " - "operations"); - throw InvalidOperationEmptyIntersection(msg); - } - auto properties = util::PropertyMap(); - properties.set(common::IdentifiedObject::NAME_KEY, - computeConcatenatedName(ops)); - - if (extent) { - properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - NN_NO_CHECK(extent)); - } - - const auto remarks = getRemarks(ops); - if (!remarks.empty()) { - properties.set(common::IdentifiedObject::REMARKS_KEY, remarks); - } - - std::vector<metadata::PositionalAccuracyNNPtr> accuracies; - const double accuracy = getAccuracy(ops); - if (accuracy >= 0.0) { - accuracies.emplace_back( - metadata::PositionalAccuracy::create(toString(accuracy))); - } - - return createPROJBased(properties, exportable, sourceCRS, targetCRS, - nullptr, accuracies, hasBallparkTransformation); -} - -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -std::vector<CoordinateOperationNNPtr> -CoordinateOperationFactory::Private::createOperationsGeogToGeog( - std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS, - const crs::CRSNNPtr &targetCRS, Private::Context &context, - const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst) { - - assert(sourceCRS.get() == geogSrc); - assert(targetCRS.get() == geogDst); - - const auto &src_pm = geogSrc->primeMeridian()->longitude(); - const auto &dst_pm = geogDst->primeMeridian()->longitude(); - auto offset_pm = - (src_pm.unit() == dst_pm.unit()) - ? common::Angle(src_pm.value() - dst_pm.value(), src_pm.unit()) - : common::Angle( - src_pm.convertToUnit(common::UnitOfMeasure::DEGREE) - - dst_pm.convertToUnit(common::UnitOfMeasure::DEGREE), - common::UnitOfMeasure::DEGREE); - - double vconvSrc = 1.0; - const auto &srcCS = geogSrc->coordinateSystem(); - const auto &srcAxisList = srcCS->axisList(); - if (srcAxisList.size() == 3) { - vconvSrc = srcAxisList[2]->unit().conversionToSI(); - } - double vconvDst = 1.0; - const auto &dstCS = geogDst->coordinateSystem(); - const auto &dstAxisList = dstCS->axisList(); - if (dstAxisList.size() == 3) { - vconvDst = dstAxisList[2]->unit().conversionToSI(); - } - - std::string name(buildTransfName(geogSrc->nameStr(), geogDst->nameStr())); - - const auto &authFactory = context.context->getAuthorityFactory(); - const auto dbContext = - authFactory ? authFactory->databaseContext().as_nullable() : nullptr; - - const bool sameDatum = geogSrc->datumNonNull(dbContext)->_isEquivalentTo( - geogDst->datumNonNull(dbContext).get(), - util::IComparable::Criterion::EQUIVALENT); - - // Do the CRS differ by their axis order ? - bool axisReversal2D = false; - bool axisReversal3D = false; - if (!srcCS->_isEquivalentTo(dstCS.get(), - util::IComparable::Criterion::EQUIVALENT)) { - auto srcOrder = srcCS->axisOrder(); - auto dstOrder = dstCS->axisOrder(); - if (((srcOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST || - srcOrder == cs::EllipsoidalCS::AxisOrder:: - LAT_NORTH_LONG_EAST_HEIGHT_UP) && - (dstOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH || - dstOrder == cs::EllipsoidalCS::AxisOrder:: - LONG_EAST_LAT_NORTH_HEIGHT_UP)) || - ((srcOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH || - srcOrder == cs::EllipsoidalCS::AxisOrder:: - LONG_EAST_LAT_NORTH_HEIGHT_UP) && - (dstOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST || - dstOrder == cs::EllipsoidalCS::AxisOrder:: - LAT_NORTH_LONG_EAST_HEIGHT_UP))) { - if (srcAxisList.size() == 3 || dstAxisList.size() == 3) - axisReversal3D = true; - else - axisReversal2D = true; - } - } - - // Do they differ by vertical units ? - if (vconvSrc != vconvDst && - geogSrc->ellipsoid()->_isEquivalentTo( - geogDst->ellipsoid().get(), - util::IComparable::Criterion::EQUIVALENT)) { - if (offset_pm.value() == 0 && !axisReversal2D && !axisReversal3D) { - // If only by vertical units, use a Change of Vertical - // Unit - // transformation - const double factor = vconvSrc / vconvDst; - auto conv = Conversion::createChangeVerticalUnit( - util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, - name), - common::Scale(factor)); - conv->setCRSs(sourceCRS, targetCRS, nullptr); - conv->setHasBallparkTransformation(!sameDatum); - res.push_back(conv); - return res; - } else { - auto op = createGeodToGeodPROJBased(sourceCRS, targetCRS); - op->setHasBallparkTransformation(!sameDatum); - res.emplace_back(op); - return res; - } - } - - // Do the CRS differ only by their axis order ? - if (sameDatum && (axisReversal2D || axisReversal3D)) { - auto conv = Conversion::createAxisOrderReversal(axisReversal3D); - conv->setCRSs(sourceCRS, targetCRS, nullptr); - res.emplace_back(conv); - return res; - } - - std::vector<CoordinateOperationNNPtr> steps; - // If both are geographic and only differ by their prime - // meridian, - // apply a longitude rotation transformation. - if (geogSrc->ellipsoid()->_isEquivalentTo( - geogDst->ellipsoid().get(), - util::IComparable::Criterion::EQUIVALENT) && - src_pm.getSIValue() != dst_pm.getSIValue()) { - - steps.emplace_back(Transformation::createLongitudeRotation( - util::PropertyMap() - .set(common::IdentifiedObject::NAME_KEY, name) - .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - metadata::Extent::WORLD), - sourceCRS, targetCRS, offset_pm)); - // If only the target has a non-zero prime meridian, chain a - // null geographic offset and then the longitude rotation - } else if (src_pm.getSIValue() == 0 && dst_pm.getSIValue() != 0) { - auto datum = datum::GeodeticReferenceFrame::create( - util::PropertyMap(), geogDst->ellipsoid(), - util::optional<std::string>(), geogSrc->primeMeridian()); - std::string interm_crs_name(geogDst->nameStr()); - interm_crs_name += " altered to use prime meridian of "; - interm_crs_name += geogSrc->nameStr(); - auto interm_crs = - util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create( - util::PropertyMap() - .set(common::IdentifiedObject::NAME_KEY, interm_crs_name) - .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - metadata::Extent::WORLD), - datum, dstCS)); - - steps.emplace_back( - createBallparkGeographicOffset(sourceCRS, interm_crs, dbContext)); - - steps.emplace_back(Transformation::createLongitudeRotation( - util::PropertyMap() - .set(common::IdentifiedObject::NAME_KEY, - buildTransfName(geogSrc->nameStr(), interm_crs->nameStr())) - .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - metadata::Extent::WORLD), - interm_crs, targetCRS, offset_pm)); - - } else { - // If the prime meridians are different, chain a longitude - // rotation and the null geographic offset. - if (src_pm.getSIValue() != dst_pm.getSIValue()) { - auto datum = datum::GeodeticReferenceFrame::create( - util::PropertyMap(), geogSrc->ellipsoid(), - util::optional<std::string>(), geogDst->primeMeridian()); - std::string interm_crs_name(geogSrc->nameStr()); - interm_crs_name += " altered to use prime meridian of "; - interm_crs_name += geogDst->nameStr(); - auto interm_crs = util::nn_static_pointer_cast<crs::CRS>( - crs::GeographicCRS::create( - util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, - interm_crs_name), - datum, srcCS)); - - steps.emplace_back(Transformation::createLongitudeRotation( - util::PropertyMap() - .set(common::IdentifiedObject::NAME_KEY, - buildTransfName(geogSrc->nameStr(), - interm_crs->nameStr())) - .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - metadata::Extent::WORLD), - sourceCRS, interm_crs, offset_pm)); - steps.emplace_back(createBallparkGeographicOffset( - interm_crs, targetCRS, dbContext)); - } else { - steps.emplace_back(createBallparkGeographicOffset( - sourceCRS, targetCRS, dbContext)); - } - } - - auto op = ConcatenatedOperation::createComputeMetadata( - steps, disallowEmptyIntersection); - op->setHasBallparkTransformation(!sameDatum); - res.emplace_back(op); - return res; -} - -// --------------------------------------------------------------------------- - -static bool hasIdentifiers(const CoordinateOperationNNPtr &op) { - if (!op->identifiers().empty()) { - return true; - } - auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get()); - if (concatenated) { - for (const auto &subOp : concatenated->operations()) { - if (hasIdentifiers(subOp)) { - return true; - } - } - } - return false; -} - -// --------------------------------------------------------------------------- - -static std::vector<crs::CRSNNPtr> -findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory, - const crs::GeodeticCRS *crs, - const datum::GeodeticReferenceFrame *datum) { - std::vector<crs::CRSNNPtr> 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<const datum::GeodeticReferenceFrame *>( - match.get())); - } - } - } - return candidates; -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::setCRSs( - CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS, - const crs::CRSNNPtr &targetCRS) { - co->setCRSs(sourceCRS, targetCRS, nullptr); - - auto invCO = dynamic_cast<InverseCoordinateOperation *>(co); - if (invCO) { - invCO->forwardOperation()->setCRSs(targetCRS, sourceCRS, nullptr); - } - - auto transf = dynamic_cast<Transformation *>(co); - if (transf) { - transf->inverseAsTransformation()->setCRSs(targetCRS, sourceCRS, - nullptr); - } - - auto concat = dynamic_cast<ConcatenatedOperation *>(co); - if (concat) { - auto first = concat->operations().front().get(); - auto &firstTarget(first->targetCRS()); - if (firstTarget) { - setCRSs(first, sourceCRS, NN_NO_CHECK(firstTarget)); - } - auto last = concat->operations().back().get(); - auto &lastSource(last->sourceCRS()); - if (lastSource) { - setCRSs(last, NN_NO_CHECK(lastSource), targetCRS); - } - } -} - -// --------------------------------------------------------------------------- - -static bool hasResultSetOnlyResultsWithPROJStep( - const std::vector<CoordinateOperationNNPtr> &res) { - for (const auto &op : res) { - auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get()); - if (concat) { - bool hasPROJStep = false; - const auto &steps = concat->operations(); - for (const auto &step : steps) { - const auto &ids = step->identifiers(); - if (!ids.empty()) { - const auto &opAuthority = *(ids.front()->codeSpace()); - if (opAuthority == "PROJ" || - opAuthority == "INVERSE(PROJ)" || - opAuthority == "DERIVED_FROM(PROJ)") { - hasPROJStep = true; - break; - } - } - } - if (!hasPROJStep) { - return false; - } - } else { - return false; - } - } - return true; -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::createOperationsWithDatumPivot( - std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS, - const crs::CRSNNPtr &targetCRS, const crs::GeodeticCRS *geodSrc, - const crs::GeodeticCRS *geodDst, Private::Context &context) { - -#ifdef TRACE_CREATE_OPERATIONS - ENTER_BLOCK("createOperationsWithDatumPivot(" + - objectAsStr(sourceCRS.get()) + "," + - objectAsStr(targetCRS.get()) + ")"); -#endif - - struct CreateOperationsWithDatumPivotAntiRecursion { - Context &context; - - explicit CreateOperationsWithDatumPivotAntiRecursion(Context &contextIn) - : context(contextIn) { - assert(!context.inCreateOperationsWithDatumPivotAntiRecursion); - context.inCreateOperationsWithDatumPivotAntiRecursion = true; - } - - ~CreateOperationsWithDatumPivotAntiRecursion() { - context.inCreateOperationsWithDatumPivotAntiRecursion = false; - } - }; - CreateOperationsWithDatumPivotAntiRecursion guard(context); - - const auto &authFactory = context.context->getAuthorityFactory(); - const auto &dbContext = authFactory->databaseContext(); - - const auto candidatesSrcGeod(findCandidateGeodCRSForDatum( - authFactory, geodSrc, - geodSrc->datumNonNull(dbContext.as_nullable()).get())); - const auto candidatesDstGeod(findCandidateGeodCRSForDatum( - authFactory, geodDst, - geodDst->datumNonNull(dbContext.as_nullable()).get())); - - const bool sourceAndTargetAre3D = - geodSrc->coordinateSystem()->axisList().size() == 3 && - geodDst->coordinateSystem()->axisList().size() == 3; - - auto createTransformations = [&](const crs::CRSNNPtr &candidateSrcGeod, - const crs::CRSNNPtr &candidateDstGeod, - const CoordinateOperationNNPtr &opFirst, - bool isNullFirst) { - const auto opsSecond = - createOperations(candidateSrcGeod, candidateDstGeod, context); - const auto opsThird = - createOperations(candidateDstGeod, targetCRS, context); - assert(!opsThird.empty()); - - for (auto &opSecond : opsSecond) { - // Check that it is not a transformation synthetized by - // ourselves - if (!hasIdentifiers(opSecond)) { - continue; - } - // And even if it is a referenced transformation, check that - // it is not a trivial one - auto so = dynamic_cast<const SingleOperation *>(opSecond.get()); - if (so && isAxisOrderReversal(so->method()->getEPSGCode())) { - continue; - } - - std::vector<CoordinateOperationNNPtr> subOps; - const bool isNullThird = - isNullTransformation(opsThird[0]->nameStr()); - CoordinateOperationNNPtr opSecondCloned( - (isNullFirst || isNullThird || sourceAndTargetAre3D) - ? opSecond->shallowClone() - : opSecond); - if (isNullFirst || isNullThird) { - if (opSecondCloned->identifiers().size() == 1 && - (*opSecondCloned->identifiers()[0]->codeSpace()) - .find("DERIVED_FROM") == std::string::npos) { - { - util::PropertyMap map; - addModifiedIdentifier(map, opSecondCloned.get(), false, - true); - opSecondCloned->setProperties(map); - } - auto invCO = dynamic_cast<InverseCoordinateOperation *>( - opSecondCloned.get()); - if (invCO) { - auto invCOForward = invCO->forwardOperation().get(); - if (invCOForward->identifiers().size() == 1 && - (*invCOForward->identifiers()[0]->codeSpace()) - .find("DERIVED_FROM") == - std::string::npos) { - util::PropertyMap map; - addModifiedIdentifier(map, invCOForward, false, - true); - invCOForward->setProperties(map); - } - } - } - } - if (sourceAndTargetAre3D) { - opSecondCloned->getPrivate()->use3DHelmert_ = true; - auto invCO = dynamic_cast<InverseCoordinateOperation *>( - opSecondCloned.get()); - if (invCO) { - auto invCOForward = invCO->forwardOperation().get(); - invCOForward->getPrivate()->use3DHelmert_ = true; - } - } - if (isNullFirst) { - auto oldTarget(NN_CHECK_ASSERT(opSecondCloned->targetCRS())); - setCRSs(opSecondCloned.get(), sourceCRS, oldTarget); - } else { - subOps.emplace_back(opFirst); - } - if (isNullThird) { - auto oldSource(NN_CHECK_ASSERT(opSecondCloned->sourceCRS())); - setCRSs(opSecondCloned.get(), oldSource, targetCRS); - subOps.emplace_back(opSecondCloned); - } else { - subOps.emplace_back(opSecondCloned); - subOps.emplace_back(opsThird[0]); - } -#ifdef TRACE_CREATE_OPERATIONS - std::string debugStr; - for (const auto &op : subOps) { - if (!debugStr.empty()) { - debugStr += " + "; - } - debugStr += objectAsStr(op.get()); - debugStr += " ("; - debugStr += objectAsStr(op->sourceCRS().get()); - debugStr += "->"; - debugStr += objectAsStr(op->targetCRS().get()); - debugStr += ")"; - } - logTrace("transformation " + debugStr); -#endif - try { - res.emplace_back(ConcatenatedOperation::createComputeMetadata( - subOps, disallowEmptyIntersection)); - } catch (const InvalidOperationEmptyIntersection &) { - } - } - }; - - // Start in priority with candidates that have exactly the same name as - // the sourcCRS and targetCRS. Typically for the case of init=IGNF:XXXX - - // Transformation from IGNF:NTFP to IGNF:RGF93G, - // using - // NTF geographiques Paris (gr) vers NTF GEOGRAPHIQUES GREENWICH (DMS) + - // NOUVELLE TRIANGULATION DE LA FRANCE (NTF) vers RGF93 (ETRS89) - // that is using ntf_r93.gsb, is horribly dependent - // of IGNF:RGF93G being returned before IGNF:RGF93GEO in candidatesDstGeod. - // If RGF93GEO is returned before then we go through WGS84 and use - // instead a Helmert transformation. - // The below logic is thus quite fragile, and attempts at changing it - // result in degraded results for other use cases... - - for (const auto &candidateSrcGeod : candidatesSrcGeod) { - if (candidateSrcGeod->nameStr() == sourceCRS->nameStr()) { - for (const auto &candidateDstGeod : candidatesDstGeod) { - if (candidateDstGeod->nameStr() == targetCRS->nameStr()) { -#ifdef TRACE_CREATE_OPERATIONS - ENTER_BLOCK("try " + objectAsStr(sourceCRS.get()) + "->" + - objectAsStr(candidateSrcGeod.get()) + "->" + - objectAsStr(candidateDstGeod.get()) + "->" + - objectAsStr(targetCRS.get()) + ")"); -#endif - const auto opsFirst = - createOperations(sourceCRS, candidateSrcGeod, context); - assert(!opsFirst.empty()); - const bool isNullFirst = - isNullTransformation(opsFirst[0]->nameStr()); - createTransformations(candidateSrcGeod, candidateDstGeod, - opsFirst[0], isNullFirst); - if (!res.empty()) { - if (hasResultSetOnlyResultsWithPROJStep(res)) { - continue; - } - return; - } - } - } - } - } - - for (const auto &candidateSrcGeod : candidatesSrcGeod) { - const bool bSameSrcName = - candidateSrcGeod->nameStr() == sourceCRS->nameStr(); -#ifdef TRACE_CREATE_OPERATIONS - ENTER_BLOCK(""); -#endif - const auto opsFirst = - createOperations(sourceCRS, candidateSrcGeod, context); - assert(!opsFirst.empty()); - const bool isNullFirst = isNullTransformation(opsFirst[0]->nameStr()); - - for (const auto &candidateDstGeod : candidatesDstGeod) { - if (bSameSrcName && - candidateDstGeod->nameStr() == targetCRS->nameStr()) { - continue; - } - -#ifdef TRACE_CREATE_OPERATIONS - ENTER_BLOCK("try " + objectAsStr(sourceCRS.get()) + "->" + - objectAsStr(candidateSrcGeod.get()) + "->" + - objectAsStr(candidateDstGeod.get()) + "->" + - objectAsStr(targetCRS.get()) + ")"); -#endif - createTransformations(candidateSrcGeod, candidateDstGeod, - opsFirst[0], isNullFirst); - if (!res.empty() && !hasResultSetOnlyResultsWithPROJStep(res)) { - return; - } - } - } -} - -// --------------------------------------------------------------------------- - -static CoordinateOperationNNPtr -createBallparkGeocentricTranslation(const crs::CRSNNPtr &sourceCRS, - const crs::CRSNNPtr &targetCRS) { - std::string name(BALLPARK_GEOCENTRIC_TRANSLATION); - name += " from "; - name += sourceCRS->nameStr(); - name += " to "; - name += targetCRS->nameStr(); - - return util::nn_static_pointer_cast<CoordinateOperation>( - Transformation::createGeocentricTranslations( - util::PropertyMap() - .set(common::IdentifiedObject::NAME_KEY, name) - .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - metadata::Extent::WORLD), - sourceCRS, targetCRS, 0.0, 0.0, 0.0, {})); -} - -// --------------------------------------------------------------------------- - -bool CoordinateOperationFactory::Private::hasPerfectAccuracyResult( - const std::vector<CoordinateOperationNNPtr> &res, const Context &context) { - auto resTmp = FilterResults(res, context.context, context.extent1, - context.extent2, true) - .getRes(); - for (const auto &op : resTmp) { - const double acc = getAccuracy(op); - if (acc == 0.0) { - return true; - } - } - return false; -} - -// --------------------------------------------------------------------------- - -std::vector<CoordinateOperationNNPtr> -CoordinateOperationFactory::Private::createOperations( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context) { - -#ifdef TRACE_CREATE_OPERATIONS - ENTER_BLOCK("createOperations(" + objectAsStr(sourceCRS.get()) + " --> " + - objectAsStr(targetCRS.get()) + ")"); -#endif - - std::vector<CoordinateOperationNNPtr> res; - - auto boundSrc = dynamic_cast<const crs::BoundCRS *>(sourceCRS.get()); - auto boundDst = dynamic_cast<const crs::BoundCRS *>(targetCRS.get()); - - const auto &sourceProj4Ext = boundSrc - ? boundSrc->baseCRS()->getExtensionProj4() - : sourceCRS->getExtensionProj4(); - const auto &targetProj4Ext = boundDst - ? boundDst->baseCRS()->getExtensionProj4() - : targetCRS->getExtensionProj4(); - if (!sourceProj4Ext.empty() || !targetProj4Ext.empty()) { - createOperationsFromProj4Ext(sourceCRS, targetCRS, boundSrc, boundDst, - res); - return res; - } - - auto geodSrc = dynamic_cast<const crs::GeodeticCRS *>(sourceCRS.get()); - auto geodDst = dynamic_cast<const crs::GeodeticCRS *>(targetCRS.get()); - auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); - auto geogDst = dynamic_cast<const crs::GeographicCRS *>(targetCRS.get()); - auto vertSrc = dynamic_cast<const crs::VerticalCRS *>(sourceCRS.get()); - auto vertDst = dynamic_cast<const crs::VerticalCRS *>(targetCRS.get()); - - // First look-up if the registry provide us with operations. - auto derivedSrc = dynamic_cast<const crs::DerivedCRS *>(sourceCRS.get()); - auto derivedDst = dynamic_cast<const crs::DerivedCRS *>(targetCRS.get()); - const auto &authFactory = context.context->getAuthorityFactory(); - if (authFactory && - (derivedSrc == nullptr || - !derivedSrc->baseCRS()->_isEquivalentTo( - targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) && - (derivedDst == nullptr || - !derivedDst->baseCRS()->_isEquivalentTo( - sourceCRS.get(), util::IComparable::Criterion::EQUIVALENT))) { - - if (createOperationsFromDatabase(sourceCRS, targetCRS, context, geodSrc, - geodDst, geogSrc, geogDst, vertSrc, - vertDst, res)) { - return res; - } - } - - // Special case if both CRS are geodetic - if (geodSrc && geodDst && !derivedSrc && !derivedDst) { - createOperationsGeodToGeod(sourceCRS, targetCRS, context, geodSrc, - geodDst, res); - return res; - } - - // If the source is a derived CRS, then chain the inverse of its - // deriving conversion, with transforms from its baseCRS to the - // targetCRS - if (derivedSrc) { - createOperationsDerivedTo(sourceCRS, targetCRS, context, derivedSrc, - res); - return res; - } - - // reverse of previous case - if (derivedDst) { - return applyInverse(createOperations(targetCRS, sourceCRS, context)); - } - - // Order of comparison between the geogDst vs geodDst is impotant - if (boundSrc && geogDst) { - createOperationsBoundToGeog(sourceCRS, targetCRS, context, boundSrc, - geogDst, res); - return res; - } else if (boundSrc && geodDst) { - createOperationsToGeod(sourceCRS, targetCRS, context, geodDst, res); - return res; - } - - // reverse of previous case - if (geodSrc && boundDst) { - return applyInverse(createOperations(targetCRS, sourceCRS, context)); - } - - // vertCRS (as boundCRS with transformation to target vertCRS) to - // vertCRS - if (boundSrc && vertDst) { - createOperationsBoundToVert(sourceCRS, targetCRS, context, boundSrc, - vertDst, res); - return res; - } - - // reverse of previous case - if (boundDst && vertSrc) { - return applyInverse(createOperations(targetCRS, sourceCRS, context)); - } - - if (vertSrc && vertDst) { - createOperationsVertToVert(sourceCRS, targetCRS, context, vertSrc, - vertDst, res); - return res; - } - - // A bit odd case as we are comparing apples to oranges, but in case - // the vertical unit differ, do something useful. - if (vertSrc && geogDst) { - createOperationsVertToGeog(sourceCRS, targetCRS, context, vertSrc, - geogDst, res); - return res; - } - - // reverse of previous case - if (vertDst && geogSrc) { - return applyInverse(createOperations(targetCRS, sourceCRS, context)); - } - - // boundCRS to boundCRS - if (boundSrc && boundDst) { - createOperationsBoundToBound(sourceCRS, targetCRS, context, boundSrc, - boundDst, res); - return res; - } - - auto compoundSrc = dynamic_cast<crs::CompoundCRS *>(sourceCRS.get()); - // Order of comparison between the geogDst vs geodDst is impotant - if (compoundSrc && geogDst) { - createOperationsCompoundToGeog(sourceCRS, targetCRS, context, - compoundSrc, geogDst, res); - return res; - } else if (compoundSrc && geodDst) { - createOperationsToGeod(sourceCRS, targetCRS, context, geodDst, res); - return res; - } - - // reverse of previous cases - auto compoundDst = dynamic_cast<const crs::CompoundCRS *>(targetCRS.get()); - if (geodSrc && compoundDst) { - return applyInverse(createOperations(targetCRS, sourceCRS, context)); - } - - if (compoundSrc && compoundDst) { - createOperationsCompoundToCompound(sourceCRS, targetCRS, context, - compoundSrc, compoundDst, res); - return res; - } - - // '+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +type=crs' to - // '+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@bar.gtx - // +type=crs' - if (boundSrc && compoundDst) { - createOperationsBoundToCompound(sourceCRS, targetCRS, context, boundSrc, - compoundDst, res); - return res; - } - - // reverse of previous case - if (boundDst && compoundSrc) { - return applyInverse(createOperations(targetCRS, sourceCRS, context)); - } - - return res; -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::createOperationsFromProj4Ext( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - const crs::BoundCRS *boundSrc, const crs::BoundCRS *boundDst, - std::vector<CoordinateOperationNNPtr> &res) { - - ENTER_FUNCTION(); - - auto sourceProjExportable = dynamic_cast<const io::IPROJStringExportable *>( - boundSrc ? boundSrc : sourceCRS.get()); - auto targetProjExportable = dynamic_cast<const io::IPROJStringExportable *>( - boundDst ? boundDst : targetCRS.get()); - if (!sourceProjExportable) { - throw InvalidOperation("Source CRS is not PROJ exportable"); - } - if (!targetProjExportable) { - throw InvalidOperation("Target CRS is not PROJ exportable"); - } - auto projFormatter = io::PROJStringFormatter::create(); - projFormatter->setCRSExport(true); - projFormatter->setLegacyCRSToCRSContext(true); - projFormatter->startInversion(); - sourceProjExportable->_exportToPROJString(projFormatter.get()); - auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); - if (geogSrc) { - auto tmpFormatter = io::PROJStringFormatter::create(); - geogSrc->addAngularUnitConvertAndAxisSwap(tmpFormatter.get()); - projFormatter->ingestPROJString(tmpFormatter->toString()); - } - - projFormatter->stopInversion(); - - targetProjExportable->_exportToPROJString(projFormatter.get()); - auto geogDst = dynamic_cast<const crs::GeographicCRS *>(targetCRS.get()); - if (geogDst) { - auto tmpFormatter = io::PROJStringFormatter::create(); - geogDst->addAngularUnitConvertAndAxisSwap(tmpFormatter.get()); - projFormatter->ingestPROJString(tmpFormatter->toString()); - } - - const auto PROJString = projFormatter->toString(); - auto properties = util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr())); - res.emplace_back(SingleOperation::createPROJBased( - properties, PROJString, sourceCRS, targetCRS, {})); -} - -// --------------------------------------------------------------------------- - -bool CoordinateOperationFactory::Private::createOperationsFromDatabase( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::GeodeticCRS *geodSrc, - const crs::GeodeticCRS *geodDst, const crs::GeographicCRS *geogSrc, - const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc, - const crs::VerticalCRS *vertDst, - std::vector<CoordinateOperationNNPtr> &res) { - - ENTER_FUNCTION(); - - if (geogSrc && vertDst) { - createOperationsFromDatabase(targetCRS, sourceCRS, context, geodDst, - geodSrc, geogDst, geogSrc, vertDst, - vertSrc, res); - res = applyInverse(res); - } else if (geogDst && vertSrc) { - res = applyInverse(createOperationsGeogToVertFromGeoid( - targetCRS, sourceCRS, vertSrc, context)); - if (!res.empty()) { - createOperationsVertToGeogBallpark(sourceCRS, targetCRS, context, - vertSrc, geogDst, res); - } - } - - if (!res.empty()) { - return true; - } - - bool resFindDirectNonEmptyBeforeFiltering = false; - res = findOpsInRegistryDirect(sourceCRS, targetCRS, context, - resFindDirectNonEmptyBeforeFiltering); - - // If we get at least a result with perfect accuracy, do not - // bother generating synthetic transforms. - if (hasPerfectAccuracyResult(res, context)) { - return true; - } - - bool doFilterAndCheckPerfectOp = false; - - bool sameGeodeticDatum = false; - - if (vertSrc || vertDst) { - if (res.empty()) { - if (geogSrc && - geogSrc->coordinateSystem()->axisList().size() == 2 && - vertDst) { - auto dbContext = - context.context->getAuthorityFactory()->databaseContext(); - auto resTmp = findOpsInRegistryDirect( - sourceCRS->promoteTo3D(std::string(), dbContext), targetCRS, - context, resFindDirectNonEmptyBeforeFiltering); - for (auto &op : resTmp) { - auto newOp = op->shallowClone(); - setCRSs(newOp.get(), sourceCRS, targetCRS); - res.emplace_back(newOp); - } - } else if (geogDst && - geogDst->coordinateSystem()->axisList().size() == 2 && - vertSrc) { - auto dbContext = - context.context->getAuthorityFactory()->databaseContext(); - auto resTmp = findOpsInRegistryDirect( - sourceCRS, targetCRS->promoteTo3D(std::string(), dbContext), - context, resFindDirectNonEmptyBeforeFiltering); - for (auto &op : resTmp) { - auto newOp = op->shallowClone(); - setCRSs(newOp.get(), sourceCRS, targetCRS); - res.emplace_back(newOp); - } - } - } - if (res.empty()) { - createOperationsFromDatabaseWithVertCRS(sourceCRS, targetCRS, - context, geogSrc, geogDst, - vertSrc, vertDst, res); - } - } else if (geodSrc && geodDst) { - - const auto &authFactory = context.context->getAuthorityFactory(); - const auto dbContext = authFactory->databaseContext().as_nullable(); - - const auto srcDatum = geodSrc->datumNonNull(dbContext); - const auto dstDatum = geodDst->datumNonNull(dbContext); - sameGeodeticDatum = srcDatum->_isEquivalentTo( - dstDatum.get(), util::IComparable::Criterion::EQUIVALENT); - - if (res.empty() && !sameGeodeticDatum && - !context.inCreateOperationsWithDatumPivotAntiRecursion) { - // If we still didn't find a transformation, and that the source - // and target are GeodeticCRS, then go through their underlying - // datum to find potential transformations between other - // GeodeticCRSs - // that are made of those datum - // The typical example is if transforming between two - // GeographicCRS, - // but transformations are only available between their - // corresponding geocentric CRS. - createOperationsWithDatumPivot(res, sourceCRS, targetCRS, geodSrc, - geodDst, context); - doFilterAndCheckPerfectOp = !res.empty(); - } - } - - bool foundInstantiableOp = false; - // FIXME: the limitation to .size() == 1 is just for the - // -s EPSG:4959+5759 -t "EPSG:4959+7839" case - // finding EPSG:7860 'NZVD2016 height to Auckland 1946 - // height (1)', which uses the EPSG:1071 'Vertical Offset by Grid - // Interpolation (NZLVD)' method which is not currently implemented by PROJ - // (cannot deal with .csv files) - // Initially the test was written to iterate over for all operations of a - // non-empty res, but this causes failures in the test suite when no grids - // are installed at all. Ideally we should tweak the test suite to be - // robust to that, or skip some tests. - if (res.size() == 1) { - try { - res.front()->exportToPROJString( - io::PROJStringFormatter::create().get()); - foundInstantiableOp = true; - } catch (const std::exception &) { - } - if (!foundInstantiableOp) { - resFindDirectNonEmptyBeforeFiltering = false; - } - } else if (res.size() > 1) { - foundInstantiableOp = true; - } - - // NAD27 to NAD83 has tens of results already. No need to look - // for a pivot - if (!sameGeodeticDatum && - (((res.empty() || !foundInstantiableOp) && - !resFindDirectNonEmptyBeforeFiltering && - context.context->getAllowUseIntermediateCRS() == - CoordinateOperationContext::IntermediateCRSUse:: - IF_NO_DIRECT_TRANSFORMATION) || - context.context->getAllowUseIntermediateCRS() == - CoordinateOperationContext::IntermediateCRSUse::ALWAYS || - getenv("PROJ_FORCE_SEARCH_PIVOT"))) { - auto resWithIntermediate = findsOpsInRegistryWithIntermediate( - sourceCRS, targetCRS, context, false); - res.insert(res.end(), resWithIntermediate.begin(), - resWithIntermediate.end()); - doFilterAndCheckPerfectOp = !res.empty(); - - } else if (!context.inCreateOperationsWithDatumPivotAntiRecursion && - !resFindDirectNonEmptyBeforeFiltering && geodSrc && geodDst && - !sameGeodeticDatum && - context.context->getIntermediateCRS().empty() && - context.context->getAllowUseIntermediateCRS() != - CoordinateOperationContext::IntermediateCRSUse::NEVER) { - - bool tryWithGeodeticDatumIntermediate = res.empty(); - if (!tryWithGeodeticDatumIntermediate) { - // This is in particular for the GDA94 to WGS 84 (G1762) case - // As we have a WGS 84 -> WGS 84 (G1762) null-transformation in the - // PROJ authority, previous steps might have use that WGS 84 - // intermediate directly. They might also have generated a path - // through ITRF2008, as there is a path - // GDA94 (geoc.) -> ITRF2008 (geoc.) -> WGS84 (G1762) (geoc.) - // But there's a better path using - // GDA94 (geog.) --> GDA2020 (geog.) and - // GDA2020 (geoc.) -> WGS84 (G1762) (geoc.) that requires to - // explore intermediates through their datum, and not directly - // trough the CRS code. - // Do that only if the number of results we got through other - // algorithms is small, or if all results we have go through an - // operation in the PROJ authority. - constexpr size_t ARBITRARY_SMALL_NUMBER = 5U; - tryWithGeodeticDatumIntermediate = - res.size() < ARBITRARY_SMALL_NUMBER || - hasResultSetOnlyResultsWithPROJStep(res); - } - if (tryWithGeodeticDatumIntermediate) { - auto resWithIntermediate = findsOpsInRegistryWithIntermediate( - sourceCRS, targetCRS, context, true); - res.insert(res.end(), resWithIntermediate.begin(), - resWithIntermediate.end()); - doFilterAndCheckPerfectOp = !res.empty(); - } - } - - if (doFilterAndCheckPerfectOp) { - // If we get at least a result with perfect accuracy, do not bother - // generating synthetic transforms. - if (hasPerfectAccuracyResult(res, context)) { - return true; - } - } - return false; -} - -// --------------------------------------------------------------------------- - -static std::vector<crs::CRSNNPtr> -findCandidateVertCRSForDatum(const io::AuthorityFactoryPtr &authFactory, - const datum::VerticalReferenceFrame *datum) { - std::vector<crs::CRSNNPtr> 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()) { - auto l_candidates = - authFactory->createVerticalCRSFromDatum(authName, code); - for (const auto &candidate : l_candidates) { - candidates.emplace_back(candidate); - } - } - } - } else if (datumName != "unknown" && datumName != "unnamed") { - auto matches = authFactory->createObjectsFromName( - datumName, - {io::AuthorityFactory::ObjectType::VERTICAL_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 findCandidateVertCRSForDatum( - authFactory, - dynamic_cast<const datum::VerticalReferenceFrame *>( - match.get())); - } - } - } - return candidates; -} - -// --------------------------------------------------------------------------- - -std::vector<CoordinateOperationNNPtr> -CoordinateOperationFactory::Private::createOperationsGeogToVertFromGeoid( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - const crs::VerticalCRS *vertDst, Private::Context &context) { - - ENTER_FUNCTION(); - - const auto useTransf = [&targetCRS, &context, - vertDst](const CoordinateOperationNNPtr &op) { - const auto targetOp = - dynamic_cast<const crs::VerticalCRS *>(op->targetCRS().get()); - assert(targetOp); - if (targetOp->_isEquivalentTo( - vertDst, util::IComparable::Criterion::EQUIVALENT)) { - return op; - } - std::vector<CoordinateOperationNNPtr> tmp; - createOperationsVertToVert(NN_NO_CHECK(op->targetCRS()), targetCRS, - context, targetOp, vertDst, tmp); - assert(!tmp.empty()); - auto ret = ConcatenatedOperation::createComputeMetadata( - {op, tmp.front()}, disallowEmptyIntersection); - return ret; - }; - - const auto getProjGeoidTransformation = [&sourceCRS, &targetCRS, &vertDst, - &context]( - const CoordinateOperationNNPtr &model, - const std::string &projFilename) { - - const auto getNameVertCRSMetre = [](const std::string &name) { - if (name.empty()) - return std::string("unnamed"); - auto ret(name); - bool haveOriginalUnit = false; - if (name.back() == ')') { - const auto pos = ret.rfind(" ("); - if (pos != std::string::npos) { - haveOriginalUnit = true; - ret = ret.substr(0, pos); - } - } - const auto pos = ret.rfind(" depth"); - if (pos != std::string::npos) { - ret = ret.substr(0, pos) + " height"; - } - if (!haveOriginalUnit) { - ret += " (metre)"; - } - return ret; - }; - - const auto &axis = vertDst->coordinateSystem()->axisList()[0]; - const auto geogSrcCRS = - dynamic_cast<crs::GeographicCRS *>(model->interpolationCRS().get()) - ? NN_NO_CHECK(model->interpolationCRS()) - : sourceCRS; - const auto vertCRSMetre = - axis->unit() == common::UnitOfMeasure::METRE && - axis->direction() == cs::AxisDirection::UP - ? targetCRS - : util::nn_static_pointer_cast<crs::CRS>( - crs::VerticalCRS::create( - util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - getNameVertCRSMetre(targetCRS->nameStr())), - vertDst->datum(), vertDst->datumEnsemble(), - cs::VerticalCS::createGravityRelatedHeight( - common::UnitOfMeasure::METRE))); - const auto properties = util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - buildOpName("Transformation", vertCRSMetre, geogSrcCRS)); - - // Try to find a representative value for the accuracy of this grid - // from the registered transformations. - std::vector<metadata::PositionalAccuracyNNPtr> accuracies; - const auto &modelAccuracies = model->coordinateOperationAccuracies(); - if (modelAccuracies.empty()) { - const auto &authFactory = context.context->getAuthorityFactory(); - if (authFactory) { - const auto transformationsForGrid = - io::DatabaseContext::getTransformationsForGridName( - authFactory->databaseContext(), projFilename); - double accuracy = -1; - for (const auto &transf : transformationsForGrid) { - accuracy = std::max(accuracy, getAccuracy(transf)); - } - if (accuracy >= 0) { - accuracies.emplace_back( - metadata::PositionalAccuracy::create( - toString(accuracy))); - } - } - } - - return Transformation::createGravityRelatedHeightToGeographic3D( - properties, vertCRSMetre, geogSrcCRS, nullptr, projFilename, - !modelAccuracies.empty() ? modelAccuracies : accuracies); - }; - - std::vector<CoordinateOperationNNPtr> res; - const auto &authFactory = context.context->getAuthorityFactory(); - if (authFactory) { - const auto &models = vertDst->geoidModel(); - for (const auto &model : models) { - const auto &modelName = model->nameStr(); - const auto transformations = - starts_with(modelName, "PROJ ") - ? std::vector< - CoordinateOperationNNPtr>{getProjGeoidTransformation( - model, modelName.substr(strlen("PROJ ")))} - : authFactory->getTransformationsForGeoid( - modelName, - context.context->getUsePROJAlternativeGridNames()); - for (const auto &transf : transformations) { - if (dynamic_cast<crs::GeographicCRS *>( - transf->sourceCRS().get()) && - dynamic_cast<crs::VerticalCRS *>( - transf->targetCRS().get())) { - res.push_back(useTransf(transf)); - } else if (dynamic_cast<crs::GeographicCRS *>( - transf->targetCRS().get()) && - dynamic_cast<crs::VerticalCRS *>( - transf->sourceCRS().get())) { - res.push_back(useTransf(transf->inverse())); - } - } - } - } - - return res; -} - -// --------------------------------------------------------------------------- - -std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private:: - createOperationsGeogToVertWithIntermediateVert( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - const crs::VerticalCRS *vertDst, Private::Context &context) { - - ENTER_FUNCTION(); - - std::vector<CoordinateOperationNNPtr> res; - - struct AntiRecursionGuard { - Context &context; - - explicit AntiRecursionGuard(Context &contextIn) : context(contextIn) { - assert(!context.inCreateOperationsGeogToVertWithIntermediateVert); - context.inCreateOperationsGeogToVertWithIntermediateVert = true; - } - - ~AntiRecursionGuard() { - context.inCreateOperationsGeogToVertWithIntermediateVert = false; - } - }; - AntiRecursionGuard guard(context); - const auto &authFactory = context.context->getAuthorityFactory(); - const auto dbContext = authFactory->databaseContext().as_nullable(); - - auto candidatesVert = findCandidateVertCRSForDatum( - authFactory, vertDst->datumNonNull(dbContext).get()); - for (const auto &candidateVert : candidatesVert) { - auto resTmp = createOperations(sourceCRS, candidateVert, context); - if (!resTmp.empty()) { - const auto opsSecond = - createOperations(candidateVert, targetCRS, context); - if (!opsSecond.empty()) { - // The transformation from candidateVert to targetCRS should - // be just a unit change typically, so take only the first one, - // which is likely/hopefully the only one. - for (const auto &opFirst : resTmp) { - if (hasIdentifiers(opFirst)) { - if (candidateVert->_isEquivalentTo( - targetCRS.get(), - util::IComparable::Criterion::EQUIVALENT)) { - res.emplace_back(opFirst); - } else { - res.emplace_back( - ConcatenatedOperation::createComputeMetadata( - {opFirst, opsSecond.front()}, - disallowEmptyIntersection)); - } - } - } - if (!res.empty()) - break; - } - } - } - - return res; -} - -// --------------------------------------------------------------------------- - -std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private:: - createOperationsGeogToVertWithAlternativeGeog( - const crs::CRSNNPtr & /*sourceCRS*/, // geographic CRS - const crs::CRSNNPtr &targetCRS, // vertical CRS - Private::Context &context) { - - ENTER_FUNCTION(); - - std::vector<CoordinateOperationNNPtr> res; - - struct AntiRecursionGuard { - Context &context; - - explicit AntiRecursionGuard(Context &contextIn) : context(contextIn) { - assert(!context.inCreateOperationsGeogToVertWithAlternativeGeog); - context.inCreateOperationsGeogToVertWithAlternativeGeog = true; - } - - ~AntiRecursionGuard() { - context.inCreateOperationsGeogToVertWithAlternativeGeog = false; - } - }; - AntiRecursionGuard guard(context); - - // Generally EPSG has operations from GeogCrs to VertCRS - auto ops = findOpsInRegistryDirectTo(targetCRS, context); - - for (const auto &op : ops) { - const auto tmpCRS = op->sourceCRS(); - if (tmpCRS && dynamic_cast<const crs::GeographicCRS *>(tmpCRS.get())) { - res.emplace_back(op); - } - } - - return res; -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private:: - createOperationsFromDatabaseWithVertCRS( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::GeographicCRS *geogSrc, - const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc, - const crs::VerticalCRS *vertDst, - std::vector<CoordinateOperationNNPtr> &res) { - - // Typically to transform from "NAVD88 height (ftUS)" to a geog CRS - // by using transformations of "NAVD88 height" (metre) to that geog CRS - if (res.empty() && - !context.inCreateOperationsGeogToVertWithIntermediateVert && geogSrc && - vertDst) { - res = createOperationsGeogToVertWithIntermediateVert( - sourceCRS, targetCRS, vertDst, context); - } else if (res.empty() && - !context.inCreateOperationsGeogToVertWithIntermediateVert && - geogDst && vertSrc) { - res = applyInverse(createOperationsGeogToVertWithIntermediateVert( - targetCRS, sourceCRS, vertSrc, context)); - } - - // NAD83 only exists in 2D version in EPSG, so if it has been - // promoted to 3D, when researching a vertical to geog - // transformation, try to down cast to 2D. - const auto geog3DToVertTryThroughGeog2D = [&res, &context]( - const crs::GeographicCRS *geogSrcIn, const crs::VerticalCRS *vertDstIn, - const crs::CRSNNPtr &targetCRSIn) { - if (res.empty() && geogSrcIn && vertDstIn && - geogSrcIn->coordinateSystem()->axisList().size() == 3) { - const auto &authFactory = context.context->getAuthorityFactory(); - const auto dbContext = - authFactory ? authFactory->databaseContext().as_nullable() - : nullptr; - const auto candidatesSrcGeod(findCandidateGeodCRSForDatum( - authFactory, geogSrcIn, - geogSrcIn->datumNonNull(dbContext).get())); - for (const auto &candidate : candidatesSrcGeod) { - auto geogCandidate = - util::nn_dynamic_pointer_cast<crs::GeographicCRS>( - candidate); - if (geogCandidate && - geogCandidate->coordinateSystem()->axisList().size() == 2) { - bool ignored; - res = - findOpsInRegistryDirect(NN_NO_CHECK(geogCandidate), - targetCRSIn, context, ignored); - break; - } - } - return true; - } - return false; - }; - - if (geog3DToVertTryThroughGeog2D(geogSrc, vertDst, targetCRS)) { - // do nothing - } else if (geog3DToVertTryThroughGeog2D(geogDst, vertSrc, sourceCRS)) { - res = applyInverse(res); - } - - // There's no direct transformation from NAVD88 height to WGS84, - // so try to research all transformations from NAVD88 to another - // intermediate GeographicCRS. - if (res.empty() && - !context.inCreateOperationsGeogToVertWithAlternativeGeog && geogSrc && - vertDst) { - res = createOperationsGeogToVertWithAlternativeGeog(sourceCRS, - targetCRS, context); - } else if (res.empty() && - !context.inCreateOperationsGeogToVertWithAlternativeGeog && - geogDst && vertSrc) { - res = applyInverse(createOperationsGeogToVertWithAlternativeGeog( - targetCRS, sourceCRS, context)); - } -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::createOperationsGeodToGeod( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::GeodeticCRS *geodSrc, - const crs::GeodeticCRS *geodDst, - std::vector<CoordinateOperationNNPtr> &res) { - - ENTER_FUNCTION(); - - if (geodSrc->ellipsoid()->celestialBody() != - geodDst->ellipsoid()->celestialBody()) { - throw util::UnsupportedOperationException( - "Source and target ellipsoid do not belong to the same " - "celestial body"); - } - - auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(geodSrc); - auto geogDst = dynamic_cast<const crs::GeographicCRS *>(geodDst); - - if (geogSrc && geogDst) { - createOperationsGeogToGeog(res, sourceCRS, targetCRS, context, geogSrc, - geogDst); - return; - } - - const bool isSrcGeocentric = geodSrc->isGeocentric(); - const bool isSrcGeographic = geogSrc != nullptr; - const bool isTargetGeocentric = geodDst->isGeocentric(); - const bool isTargetGeographic = geogDst != nullptr; - - const auto IsSameDatum = [&context, &geodSrc, &geodDst]() { - const auto &authFactory = context.context->getAuthorityFactory(); - const auto dbContext = - authFactory ? authFactory->databaseContext().as_nullable() - : nullptr; - - return geodSrc->datumNonNull(dbContext)->_isEquivalentTo( - geodDst->datumNonNull(dbContext).get(), - util::IComparable::Criterion::EQUIVALENT); - }; - - if (((isSrcGeocentric && isTargetGeographic) || - (isSrcGeographic && isTargetGeocentric))) { - - // Same datum ? - if (IsSameDatum()) { - res.emplace_back( - Conversion::createGeographicGeocentric(sourceCRS, targetCRS)); - } else if (isSrcGeocentric && geogDst) { - std::string interm_crs_name(geogDst->nameStr()); - interm_crs_name += " (geocentric)"; - auto interm_crs = - util::nn_static_pointer_cast<crs::CRS>(crs::GeodeticCRS::create( - addDomains(util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - interm_crs_name), - geogDst), - geogDst->datum(), geogDst->datumEnsemble(), - NN_CHECK_ASSERT( - util::nn_dynamic_pointer_cast<cs::CartesianCS>( - geodSrc->coordinateSystem())))); - auto opFirst = - createBallparkGeocentricTranslation(sourceCRS, interm_crs); - auto opSecond = - Conversion::createGeographicGeocentric(interm_crs, targetCRS); - res.emplace_back(ConcatenatedOperation::createComputeMetadata( - {opFirst, opSecond}, disallowEmptyIntersection)); - } else { - // Apply previous case in reverse way - std::vector<CoordinateOperationNNPtr> resTmp; - createOperationsGeodToGeod(targetCRS, sourceCRS, context, geodDst, - geodSrc, resTmp); - assert(resTmp.size() == 1); - res.emplace_back(resTmp.front()->inverse()); - } - - return; - } - - if (isSrcGeocentric && isTargetGeocentric) { - if (sourceCRS->_isEquivalentTo( - targetCRS.get(), util::IComparable::Criterion::EQUIVALENT) || - IsSameDatum()) { - std::string name(NULL_GEOCENTRIC_TRANSLATION); - name += " from "; - name += sourceCRS->nameStr(); - name += " to "; - name += targetCRS->nameStr(); - res.emplace_back(Transformation::createGeocentricTranslations( - util::PropertyMap() - .set(common::IdentifiedObject::NAME_KEY, name) - .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - metadata::Extent::WORLD), - sourceCRS, targetCRS, 0.0, 0.0, 0.0, - {metadata::PositionalAccuracy::create("0")})); - } else { - res.emplace_back( - createBallparkGeocentricTranslation(sourceCRS, targetCRS)); - } - return; - } - - // Transformation between two geodetic systems of unknown type - // This should normally not be triggered with "standard" CRS - res.emplace_back(createGeodToGeodPROJBased(sourceCRS, targetCRS)); -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::createOperationsDerivedTo( - const crs::CRSNNPtr & /*sourceCRS*/, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::DerivedCRS *derivedSrc, - std::vector<CoordinateOperationNNPtr> &res) { - - ENTER_FUNCTION(); - - auto opFirst = derivedSrc->derivingConversion()->inverse(); - // Small optimization if the targetCRS is the baseCRS of the source - // derivedCRS. - if (derivedSrc->baseCRS()->_isEquivalentTo( - targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { - res.emplace_back(opFirst); - return; - } - auto opsSecond = - createOperations(derivedSrc->baseCRS(), targetCRS, context); - for (const auto &opSecond : opsSecond) { - try { - res.emplace_back(ConcatenatedOperation::createComputeMetadata( - {opFirst, opSecond}, disallowEmptyIntersection)); - } catch (const InvalidOperationEmptyIntersection &) { - } - } -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::createOperationsBoundToGeog( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::BoundCRS *boundSrc, - const crs::GeographicCRS *geogDst, - std::vector<CoordinateOperationNNPtr> &res) { - - ENTER_FUNCTION(); - - const auto &hubSrc = boundSrc->hubCRS(); - auto hubSrcGeog = dynamic_cast<const crs::GeographicCRS *>(hubSrc.get()); - auto geogCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractGeographicCRS(); - { - // If geogCRSOfBaseOfBoundSrc is a DerivedGeographicCRS, use its base - // instead (if it is a GeographicCRS) - auto derivedGeogCRS = - std::dynamic_pointer_cast<crs::DerivedGeographicCRS>( - geogCRSOfBaseOfBoundSrc); - if (derivedGeogCRS) { - auto baseCRS = std::dynamic_pointer_cast<crs::GeographicCRS>( - derivedGeogCRS->baseCRS().as_nullable()); - if (baseCRS) { - geogCRSOfBaseOfBoundSrc = baseCRS; - } - } - } - - const auto &authFactory = context.context->getAuthorityFactory(); - const auto dbContext = - authFactory ? authFactory->databaseContext().as_nullable() : nullptr; - - const auto geogDstDatum = geogDst->datumNonNull(dbContext); - - // If the underlying datum of the source is the same as the target, do - // not consider the boundCRS at all, but just its base - if (geogCRSOfBaseOfBoundSrc) { - auto geogCRSOfBaseOfBoundSrcDatum = - geogCRSOfBaseOfBoundSrc->datumNonNull(dbContext); - if (geogCRSOfBaseOfBoundSrcDatum->_isEquivalentTo( - geogDstDatum.get(), util::IComparable::Criterion::EQUIVALENT)) { - res = createOperations(boundSrc->baseCRS(), targetCRS, context); - return; - } - } - - bool triedBoundCrsToGeogCRSSameAsHubCRS = false; - // Is it: boundCRS to a geogCRS that is the same as the hubCRS ? - if (hubSrcGeog && geogCRSOfBaseOfBoundSrc && - (hubSrcGeog->_isEquivalentTo( - geogDst, util::IComparable::Criterion::EQUIVALENT) || - hubSrcGeog->is2DPartOf3D(NN_NO_CHECK(geogDst), dbContext))) { - triedBoundCrsToGeogCRSSameAsHubCRS = true; - - CoordinateOperationPtr opIntermediate; - if (!geogCRSOfBaseOfBoundSrc->_isEquivalentTo( - boundSrc->transformation()->sourceCRS().get(), - util::IComparable::Criterion::EQUIVALENT)) { - auto opsIntermediate = createOperations( - NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), - boundSrc->transformation()->sourceCRS(), context); - assert(!opsIntermediate.empty()); - opIntermediate = opsIntermediate.front(); - } - - if (boundSrc->baseCRS() == geogCRSOfBaseOfBoundSrc) { - if (opIntermediate) { - try { - res.emplace_back( - ConcatenatedOperation::createComputeMetadata( - {NN_NO_CHECK(opIntermediate), - boundSrc->transformation()}, - disallowEmptyIntersection)); - } catch (const InvalidOperationEmptyIntersection &) { - } - } else { - // Optimization to avoid creating a useless concatenated - // operation - res.emplace_back(boundSrc->transformation()); - } - return; - } - auto opsFirst = createOperations( - boundSrc->baseCRS(), NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context); - if (!opsFirst.empty()) { - for (const auto &opFirst : opsFirst) { - try { - std::vector<CoordinateOperationNNPtr> subops; - subops.emplace_back(opFirst); - if (opIntermediate) { - subops.emplace_back(NN_NO_CHECK(opIntermediate)); - } - subops.emplace_back(boundSrc->transformation()); - res.emplace_back( - ConcatenatedOperation::createComputeMetadata( - subops, disallowEmptyIntersection)); - } catch (const InvalidOperationEmptyIntersection &) { - } - } - if (!res.empty()) { - return; - } - } - // If the datum are equivalent, this is also fine - } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog && - hubSrcGeog->datumNonNull(dbContext)->_isEquivalentTo( - geogDstDatum.get(), - util::IComparable::Criterion::EQUIVALENT)) { - auto opsFirst = createOperations( - boundSrc->baseCRS(), NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context); - auto opsLast = createOperations(hubSrc, targetCRS, context); - if (!opsFirst.empty() && !opsLast.empty()) { - CoordinateOperationPtr opIntermediate; - if (!geogCRSOfBaseOfBoundSrc->_isEquivalentTo( - boundSrc->transformation()->sourceCRS().get(), - util::IComparable::Criterion::EQUIVALENT)) { - auto opsIntermediate = createOperations( - NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), - boundSrc->transformation()->sourceCRS(), context); - assert(!opsIntermediate.empty()); - opIntermediate = opsIntermediate.front(); - } - for (const auto &opFirst : opsFirst) { - for (const auto &opLast : opsLast) { - try { - std::vector<CoordinateOperationNNPtr> subops; - subops.emplace_back(opFirst); - if (opIntermediate) { - subops.emplace_back(NN_NO_CHECK(opIntermediate)); - } - subops.emplace_back(boundSrc->transformation()); - subops.emplace_back(opLast); - res.emplace_back( - ConcatenatedOperation::createComputeMetadata( - subops, disallowEmptyIntersection)); - } catch (const InvalidOperationEmptyIntersection &) { - } - } - } - if (!res.empty()) { - return; - } - } - // Consider WGS 84 and NAD83 as equivalent in that context if the - // geogCRSOfBaseOfBoundSrc ellipsoid is Clarke66 (for NAD27) - // Case of "+proj=latlong +ellps=clrk66 - // +nadgrids=ntv1_can.dat,conus" - // to "+proj=latlong +datum=NAD83" - } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog && - geogCRSOfBaseOfBoundSrc->ellipsoid()->_isEquivalentTo( - datum::Ellipsoid::CLARKE_1866.get(), - util::IComparable::Criterion::EQUIVALENT) && - hubSrcGeog->datumNonNull(dbContext)->_isEquivalentTo( - datum::GeodeticReferenceFrame::EPSG_6326.get(), - util::IComparable::Criterion::EQUIVALENT) && - geogDstDatum->_isEquivalentTo( - datum::GeodeticReferenceFrame::EPSG_6269.get(), - util::IComparable::Criterion::EQUIVALENT)) { - auto nnGeogCRSOfBaseOfBoundSrc = NN_NO_CHECK(geogCRSOfBaseOfBoundSrc); - if (boundSrc->baseCRS()->_isEquivalentTo( - nnGeogCRSOfBaseOfBoundSrc.get(), - util::IComparable::Criterion::EQUIVALENT)) { - auto transf = boundSrc->transformation()->shallowClone(); - transf->setProperties(util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - buildTransfName(boundSrc->baseCRS()->nameStr(), - targetCRS->nameStr()))); - transf->setCRSs(boundSrc->baseCRS(), targetCRS, nullptr); - res.emplace_back(transf); - return; - } else { - auto opsFirst = createOperations( - boundSrc->baseCRS(), nnGeogCRSOfBaseOfBoundSrc, context); - auto transf = boundSrc->transformation()->shallowClone(); - transf->setProperties(util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - buildTransfName(nnGeogCRSOfBaseOfBoundSrc->nameStr(), - targetCRS->nameStr()))); - transf->setCRSs(nnGeogCRSOfBaseOfBoundSrc, targetCRS, nullptr); - if (!opsFirst.empty()) { - for (const auto &opFirst : opsFirst) { - try { - res.emplace_back( - ConcatenatedOperation::createComputeMetadata( - {opFirst, transf}, disallowEmptyIntersection)); - } catch (const InvalidOperationEmptyIntersection &) { - } - } - if (!res.empty()) { - return; - } - } - } - } - - if (hubSrcGeog && - hubSrcGeog->_isEquivalentTo(geogDst, - util::IComparable::Criterion::EQUIVALENT) && - dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get())) { - auto transfSrc = boundSrc->transformation()->sourceCRS(); - if (dynamic_cast<const crs::VerticalCRS *>(transfSrc.get()) && - !boundSrc->baseCRS()->_isEquivalentTo( - transfSrc.get(), util::IComparable::Criterion::EQUIVALENT)) { - auto opsFirst = - createOperations(boundSrc->baseCRS(), transfSrc, context); - for (const auto &opFirst : opsFirst) { - try { - res.emplace_back( - ConcatenatedOperation::createComputeMetadata( - {opFirst, boundSrc->transformation()}, - disallowEmptyIntersection)); - } catch (const InvalidOperationEmptyIntersection &) { - } - } - return; - } - - res.emplace_back(boundSrc->transformation()); - return; - } - - if (!triedBoundCrsToGeogCRSSameAsHubCRS && hubSrcGeog && - geogCRSOfBaseOfBoundSrc) { - // This one should go to the above 'Is it: boundCRS to a geogCRS - // that is the same as the hubCRS ?' case - auto opsFirst = createOperations(sourceCRS, hubSrc, context); - auto opsLast = createOperations(hubSrc, targetCRS, context); - if (!opsFirst.empty() && !opsLast.empty()) { - for (const auto &opFirst : opsFirst) { - for (const auto &opLast : opsLast) { - // Exclude artificial transformations from the hub - // to the target CRS, if it is the only one. - if (opsLast.size() > 1 || - !opLast->hasBallparkTransformation()) { - try { - res.emplace_back( - ConcatenatedOperation::createComputeMetadata( - {opFirst, opLast}, - disallowEmptyIntersection)); - } catch (const InvalidOperationEmptyIntersection &) { - } - } else { - // std::cerr << "excluded " << opLast->nameStr() << - // std::endl; - } - } - } - if (!res.empty()) { - return; - } - } - } - - auto vertCRSOfBaseOfBoundSrc = - dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get()); - if (vertCRSOfBaseOfBoundSrc && hubSrcGeog) { - auto opsFirst = createOperations(sourceCRS, hubSrc, context); - if (context.skipHorizontalTransformation) { - if (!opsFirst.empty()) { - const auto &hubAxisList = - hubSrcGeog->coordinateSystem()->axisList(); - const auto &targetAxisList = - geogDst->coordinateSystem()->axisList(); - if (hubAxisList.size() == 3 && targetAxisList.size() == 3 && - !hubAxisList[2]->_isEquivalentTo( - targetAxisList[2].get(), - util::IComparable::Criterion::EQUIVALENT)) { - - const auto &srcAxis = hubAxisList[2]; - const double convSrc = srcAxis->unit().conversionToSI(); - const auto &dstAxis = targetAxisList[2]; - const double convDst = dstAxis->unit().conversionToSI(); - const bool srcIsUp = - srcAxis->direction() == cs::AxisDirection::UP; - const bool srcIsDown = - srcAxis->direction() == cs::AxisDirection::DOWN; - const bool dstIsUp = - dstAxis->direction() == cs::AxisDirection::UP; - const bool dstIsDown = - dstAxis->direction() == cs::AxisDirection::DOWN; - const bool heightDepthReversal = - ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp)); - - const double factor = convSrc / convDst; - auto conv = Conversion::createChangeVerticalUnit( - util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - "Change of vertical unit"), - common::Scale(heightDepthReversal ? -factor : factor)); - - conv->setCRSs( - hubSrc, - hubSrc->demoteTo2D(std::string(), dbContext) - ->promoteTo3D(std::string(), dbContext, dstAxis), - nullptr); - - for (const auto &op : opsFirst) { - try { - res.emplace_back( - ConcatenatedOperation::createComputeMetadata( - {op, conv}, disallowEmptyIntersection)); - } catch (const InvalidOperationEmptyIntersection &) { - } - } - } else { - res = opsFirst; - } - } - return; - } else { - auto opsSecond = createOperations(hubSrc, targetCRS, context); - if (!opsFirst.empty() && !opsSecond.empty()) { - for (const auto &opFirst : opsFirst) { - for (const auto &opLast : opsSecond) { - // Exclude artificial transformations from the hub - // to the target CRS - if (!opLast->hasBallparkTransformation()) { - try { - res.emplace_back( - ConcatenatedOperation:: - createComputeMetadata( - {opFirst, opLast}, - disallowEmptyIntersection)); - } catch ( - const InvalidOperationEmptyIntersection &) { - } - } else { - // std::cerr << "excluded " << opLast->nameStr() << - // std::endl; - } - } - } - if (!res.empty()) { - return; - } - } - } - } - - res = createOperations(boundSrc->baseCRS(), targetCRS, context); -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::createOperationsBoundToVert( - const crs::CRSNNPtr & /*sourceCRS*/, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::BoundCRS *boundSrc, - const crs::VerticalCRS *vertDst, - std::vector<CoordinateOperationNNPtr> &res) { - - ENTER_FUNCTION(); - - auto baseSrcVert = - dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get()); - const auto &hubSrc = boundSrc->hubCRS(); - auto hubSrcVert = dynamic_cast<const crs::VerticalCRS *>(hubSrc.get()); - if (baseSrcVert && hubSrcVert && - vertDst->_isEquivalentTo(hubSrcVert, - util::IComparable::Criterion::EQUIVALENT)) { - res.emplace_back(boundSrc->transformation()); - return; - } - - res = createOperations(boundSrc->baseCRS(), targetCRS, context); -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::createOperationsVertToVert( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::VerticalCRS *vertSrc, - const crs::VerticalCRS *vertDst, - std::vector<CoordinateOperationNNPtr> &res) { - - ENTER_FUNCTION(); - - const auto &authFactory = context.context->getAuthorityFactory(); - const auto dbContext = - authFactory ? authFactory->databaseContext().as_nullable() : nullptr; - - const auto srcDatum = vertSrc->datumNonNull(dbContext); - const auto dstDatum = vertDst->datumNonNull(dbContext); - const bool equivalentVDatum = srcDatum->_isEquivalentTo( - dstDatum.get(), util::IComparable::Criterion::EQUIVALENT); - - const auto &srcAxis = vertSrc->coordinateSystem()->axisList()[0]; - const double convSrc = srcAxis->unit().conversionToSI(); - const auto &dstAxis = vertDst->coordinateSystem()->axisList()[0]; - const double convDst = dstAxis->unit().conversionToSI(); - const bool srcIsUp = srcAxis->direction() == cs::AxisDirection::UP; - const bool srcIsDown = srcAxis->direction() == cs::AxisDirection::DOWN; - const bool dstIsUp = dstAxis->direction() == cs::AxisDirection::UP; - const bool dstIsDown = dstAxis->direction() == cs::AxisDirection::DOWN; - const bool heightDepthReversal = - ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp)); - - const double factor = convSrc / convDst; - auto name = buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr()); - if (!equivalentVDatum) { - name += BALLPARK_VERTICAL_TRANSFORMATION; - auto conv = Transformation::createChangeVerticalUnit( - util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name), - sourceCRS, targetCRS, - // In case of a height depth reversal, we should probably have - // 2 steps instead of putting a negative factor... - common::Scale(heightDepthReversal ? -factor : factor), {}); - conv->setHasBallparkTransformation(true); - res.push_back(conv); - } else if (convSrc != convDst || !heightDepthReversal) { - auto conv = Conversion::createChangeVerticalUnit( - util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name), - // In case of a height depth reversal, we should probably have - // 2 steps instead of putting a negative factor... - common::Scale(heightDepthReversal ? -factor : factor)); - conv->setCRSs(sourceCRS, targetCRS, nullptr); - res.push_back(conv); - } else { - auto conv = Conversion::createHeightDepthReversal( - util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name)); - conv->setCRSs(sourceCRS, targetCRS, nullptr); - res.push_back(conv); - } -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::createOperationsVertToGeog( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::VerticalCRS *vertSrc, - const crs::GeographicCRS *geogDst, - std::vector<CoordinateOperationNNPtr> &res) { - - ENTER_FUNCTION(); - - if (vertSrc->identifiers().empty()) { - const auto &vertSrcName = vertSrc->nameStr(); - const auto &authFactory = context.context->getAuthorityFactory(); - if (authFactory != nullptr && vertSrcName != "unnamed" && - vertSrcName != "unknown") { - auto matches = authFactory->createObjectsFromName( - vertSrcName, {io::AuthorityFactory::ObjectType::VERTICAL_CRS}, - false, 2); - if (matches.size() == 1) { - const auto &match = matches.front(); - if (vertSrc->_isEquivalentTo( - match.get(), - util::IComparable::Criterion::EQUIVALENT) && - !match->identifiers().empty()) { - auto resTmp = createOperations( - NN_NO_CHECK( - util::nn_dynamic_pointer_cast<crs::VerticalCRS>( - match)), - targetCRS, context); - res.insert(res.end(), resTmp.begin(), resTmp.end()); - return; - } - } - } - } - - createOperationsVertToGeogBallpark(sourceCRS, targetCRS, context, vertSrc, - geogDst, res); -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::createOperationsVertToGeogBallpark( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &, const crs::VerticalCRS *vertSrc, - const crs::GeographicCRS *geogDst, - std::vector<CoordinateOperationNNPtr> &res) { - - ENTER_FUNCTION(); - - const auto &srcAxis = vertSrc->coordinateSystem()->axisList()[0]; - const double convSrc = srcAxis->unit().conversionToSI(); - double convDst = 1.0; - const auto &geogAxis = geogDst->coordinateSystem()->axisList(); - bool dstIsUp = true; - bool dstIsDown = false; - if (geogAxis.size() == 3) { - const auto &dstAxis = geogAxis[2]; - convDst = dstAxis->unit().conversionToSI(); - dstIsUp = dstAxis->direction() == cs::AxisDirection::UP; - dstIsDown = dstAxis->direction() == cs::AxisDirection::DOWN; - } - const bool srcIsUp = srcAxis->direction() == cs::AxisDirection::UP; - const bool srcIsDown = srcAxis->direction() == cs::AxisDirection::DOWN; - const bool heightDepthReversal = - ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp)); - - const double factor = convSrc / convDst; - - const auto &sourceCRSExtent = getExtent(sourceCRS); - const auto &targetCRSExtent = getExtent(targetCRS); - const bool sameExtent = - sourceCRSExtent && targetCRSExtent && - sourceCRSExtent->_isEquivalentTo( - targetCRSExtent.get(), util::IComparable::Criterion::EQUIVALENT); - - util::PropertyMap map; - map.set(common::IdentifiedObject::NAME_KEY, - buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr()) + - BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT) - .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - sameExtent ? NN_NO_CHECK(sourceCRSExtent) - : metadata::Extent::WORLD); - - auto conv = Transformation::createChangeVerticalUnit( - map, sourceCRS, targetCRS, - common::Scale(heightDepthReversal ? -factor : factor), {}); - conv->setHasBallparkTransformation(true); - res.push_back(conv); -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::createOperationsBoundToBound( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::BoundCRS *boundSrc, - const crs::BoundCRS *boundDst, std::vector<CoordinateOperationNNPtr> &res) { - - ENTER_FUNCTION(); - - // BoundCRS to BoundCRS of horizontal CRS using the same (geographic) hub - const auto &hubSrc = boundSrc->hubCRS(); - auto hubSrcGeog = dynamic_cast<const crs::GeographicCRS *>(hubSrc.get()); - const auto &hubDst = boundDst->hubCRS(); - auto hubDstGeog = dynamic_cast<const crs::GeographicCRS *>(hubDst.get()); - auto geogCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractGeographicCRS(); - auto geogCRSOfBaseOfBoundDst = boundDst->baseCRS()->extractGeographicCRS(); - if (hubSrcGeog && hubDstGeog && - hubSrcGeog->_isEquivalentTo(hubDstGeog, - util::IComparable::Criterion::EQUIVALENT) && - geogCRSOfBaseOfBoundSrc && geogCRSOfBaseOfBoundDst) { - const bool firstIsNoOp = geogCRSOfBaseOfBoundSrc->_isEquivalentTo( - boundSrc->baseCRS().get(), - util::IComparable::Criterion::EQUIVALENT); - const bool lastIsNoOp = geogCRSOfBaseOfBoundDst->_isEquivalentTo( - boundDst->baseCRS().get(), - util::IComparable::Criterion::EQUIVALENT); - auto opsFirst = createOperations( - boundSrc->baseCRS(), NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context); - auto opsLast = createOperations(NN_NO_CHECK(geogCRSOfBaseOfBoundDst), - boundDst->baseCRS(), context); - if (!opsFirst.empty() && !opsLast.empty()) { - const auto &opSecond = boundSrc->transformation(); - auto opThird = boundDst->transformation()->inverse(); - for (const auto &opFirst : opsFirst) { - for (const auto &opLast : opsLast) { - try { - std::vector<CoordinateOperationNNPtr> ops; - if (!firstIsNoOp) { - ops.push_back(opFirst); - } - ops.push_back(opSecond); - ops.push_back(opThird); - if (!lastIsNoOp) { - ops.push_back(opLast); - } - res.emplace_back( - ConcatenatedOperation::createComputeMetadata( - ops, disallowEmptyIntersection)); - } catch (const InvalidOperationEmptyIntersection &) { - } - } - } - if (!res.empty()) { - return; - } - } - } - - // BoundCRS to BoundCRS of vertical CRS using the same vertical datum - // ==> ignore the bound transformation - auto baseOfBoundSrcAsVertCRS = - dynamic_cast<crs::VerticalCRS *>(boundSrc->baseCRS().get()); - auto baseOfBoundDstAsVertCRS = - dynamic_cast<crs::VerticalCRS *>(boundDst->baseCRS().get()); - if (baseOfBoundSrcAsVertCRS && baseOfBoundDstAsVertCRS) { - - const auto &authFactory = context.context->getAuthorityFactory(); - const auto dbContext = - authFactory ? authFactory->databaseContext().as_nullable() - : nullptr; - - const auto datumSrc = baseOfBoundSrcAsVertCRS->datumNonNull(dbContext); - const auto datumDst = baseOfBoundDstAsVertCRS->datumNonNull(dbContext); - if (datumSrc->nameStr() == datumDst->nameStr() && - (datumSrc->nameStr() != "unknown" || - boundSrc->transformation()->_isEquivalentTo( - boundDst->transformation().get(), - util::IComparable::Criterion::EQUIVALENT))) { - res = createOperations(boundSrc->baseCRS(), boundDst->baseCRS(), - context); - return; - } - } - - // BoundCRS to BoundCRS of vertical CRS - auto vertCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractVerticalCRS(); - auto vertCRSOfBaseOfBoundDst = boundDst->baseCRS()->extractVerticalCRS(); - if (hubSrcGeog && hubDstGeog && - hubSrcGeog->_isEquivalentTo(hubDstGeog, - util::IComparable::Criterion::EQUIVALENT) && - vertCRSOfBaseOfBoundSrc && vertCRSOfBaseOfBoundDst) { - auto opsFirst = createOperations(sourceCRS, hubSrc, context); - auto opsLast = createOperations(hubSrc, targetCRS, context); - if (!opsFirst.empty() && !opsLast.empty()) { - for (const auto &opFirst : opsFirst) { - for (const auto &opLast : opsLast) { - try { - res.emplace_back( - ConcatenatedOperation::createComputeMetadata( - {opFirst, opLast}, disallowEmptyIntersection)); - } catch (const InvalidOperationEmptyIntersection &) { - } - } - } - if (!res.empty()) { - return; - } - } - } - - res = createOperations(boundSrc->baseCRS(), boundDst->baseCRS(), context); -} - -// --------------------------------------------------------------------------- - -static std::vector<CoordinateOperationNNPtr> -getOps(const CoordinateOperationNNPtr &op) { - auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get()); - if (concatenated) - return concatenated->operations(); - return {op}; -} - -// --------------------------------------------------------------------------- - -static bool useDifferentTransformationsForSameSourceTarget( - const CoordinateOperationNNPtr &opA, const CoordinateOperationNNPtr &opB) { - auto subOpsA = getOps(opA); - auto subOpsB = getOps(opB); - for (const auto &subOpA : subOpsA) { - if (!dynamic_cast<const Transformation *>(subOpA.get())) - continue; - if (subOpA->sourceCRS()->nameStr() == "unknown" || - subOpA->targetCRS()->nameStr() == "unknown") - continue; - for (const auto &subOpB : subOpsB) { - if (!dynamic_cast<const Transformation *>(subOpB.get())) - continue; - if (subOpB->sourceCRS()->nameStr() == "unknown" || - subOpB->targetCRS()->nameStr() == "unknown") - continue; - - if (subOpA->sourceCRS()->nameStr() == - subOpB->sourceCRS()->nameStr() && - subOpA->targetCRS()->nameStr() == - subOpB->targetCRS()->nameStr()) { - if (starts_with(subOpA->nameStr(), NULL_GEOGRAPHIC_OFFSET) && - starts_with(subOpB->nameStr(), NULL_GEOGRAPHIC_OFFSET)) { - continue; - } - - if (!subOpA->isEquivalentTo(subOpB.get())) { - return true; - } - } else if (subOpA->sourceCRS()->nameStr() == - subOpB->targetCRS()->nameStr() && - subOpA->targetCRS()->nameStr() == - subOpB->sourceCRS()->nameStr()) { - if (starts_with(subOpA->nameStr(), NULL_GEOGRAPHIC_OFFSET) && - starts_with(subOpB->nameStr(), NULL_GEOGRAPHIC_OFFSET)) { - continue; - } - - if (!subOpA->isEquivalentTo(subOpB->inverse().get())) { - return true; - } - } - } - } - return false; -} - -// --------------------------------------------------------------------------- - -static crs::GeographicCRSPtr -getInterpolationGeogCRS(const CoordinateOperationNNPtr &verticalTransform, - const io::DatabaseContextPtr &dbContext) { - crs::GeographicCRSPtr interpolationGeogCRS; - auto transformationVerticalTransform = - dynamic_cast<const Transformation *>(verticalTransform.get()); - if (transformationVerticalTransform == nullptr) { - const auto concat = dynamic_cast<const ConcatenatedOperation *>( - verticalTransform.get()); - if (concat) { - const auto &steps = concat->operations(); - // Is this change of unit and/or height depth reversal + - // transformation ? - for (const auto &step : steps) { - const auto transf = - dynamic_cast<const Transformation *>(step.get()); - if (transf) { - // Only support a single Transformation in the steps - if (transformationVerticalTransform != nullptr) { - transformationVerticalTransform = nullptr; - break; - } - transformationVerticalTransform = transf; - } - } - } - } - if (transformationVerticalTransform && - !transformationVerticalTransform->hasBallparkTransformation()) { - auto interpTransformCRS = - transformationVerticalTransform->interpolationCRS(); - if (interpTransformCRS) { - interpolationGeogCRS = - std::dynamic_pointer_cast<crs::GeographicCRS>( - interpTransformCRS); - } else { - // If no explicit interpolation CRS, then - // this will be the geographic CRS of the - // vertical to geog transformation - interpolationGeogCRS = - std::dynamic_pointer_cast<crs::GeographicCRS>( - transformationVerticalTransform->targetCRS().as_nullable()); - } - } - - if (interpolationGeogCRS) { - if (interpolationGeogCRS->coordinateSystem()->axisList().size() == 3) { - // We need to force the interpolation CRS, which - // will - // frequently be 3D, to 2D to avoid transformations - // between source CRS and interpolation CRS to have - // 3D terms. - interpolationGeogCRS = - interpolationGeogCRS->demoteTo2D(std::string(), dbContext) - .as_nullable(); - } - } - - return interpolationGeogCRS; -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::createOperationsCompoundToGeog( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::CompoundCRS *compoundSrc, - const crs::GeographicCRS *geogDst, - std::vector<CoordinateOperationNNPtr> &res) { - - ENTER_FUNCTION(); - - const auto &authFactory = context.context->getAuthorityFactory(); - const auto &componentsSrc = compoundSrc->componentReferenceSystems(); - if (!componentsSrc.empty()) { - - if (componentsSrc.size() == 2) { - auto derivedHSrc = - dynamic_cast<const crs::DerivedCRS *>(componentsSrc[0].get()); - if (derivedHSrc) { - std::vector<crs::CRSNNPtr> intermComponents{ - derivedHSrc->baseCRS(), componentsSrc[1]}; - auto properties = util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - intermComponents[0]->nameStr() + " + " + - intermComponents[1]->nameStr()); - auto intermCompound = - crs::CompoundCRS::create(properties, intermComponents); - auto opsFirst = - createOperations(sourceCRS, intermCompound, context); - assert(!opsFirst.empty()); - auto opsLast = - createOperations(intermCompound, targetCRS, context); - for (const auto &opLast : opsLast) { - try { - res.emplace_back( - ConcatenatedOperation::createComputeMetadata( - {opsFirst.front(), opLast}, - disallowEmptyIntersection)); - } catch (const std::exception &) { - } - } - return; - } - } - - std::vector<CoordinateOperationNNPtr> horizTransforms; - auto srcGeogCRS = componentsSrc[0]->extractGeographicCRS(); - if (srcGeogCRS) { - horizTransforms = - createOperations(componentsSrc[0], targetCRS, context); - } - std::vector<CoordinateOperationNNPtr> verticalTransforms; - - const auto dbContext = - authFactory ? authFactory->databaseContext().as_nullable() - : nullptr; - if (componentsSrc.size() >= 2 && - componentsSrc[1]->extractVerticalCRS()) { - - struct SetSkipHorizontalTransform { - Context &context; - - explicit SetSkipHorizontalTransform(Context &contextIn) - : context(contextIn) { - assert(!context.skipHorizontalTransformation); - context.skipHorizontalTransformation = true; - } - - ~SetSkipHorizontalTransform() { - context.skipHorizontalTransformation = false; - } - }; - SetSkipHorizontalTransform setSkipHorizontalTransform(context); - - verticalTransforms = createOperations( - componentsSrc[1], - targetCRS->promoteTo3D(std::string(), dbContext), context); - bool foundRegisteredTransformWithAllGridsAvailable = false; - const auto gridAvailabilityUse = - context.context->getGridAvailabilityUse(); - const bool ignoreMissingGrids = - gridAvailabilityUse == - CoordinateOperationContext::GridAvailabilityUse:: - IGNORE_GRID_AVAILABILITY; - for (const auto &op : verticalTransforms) { - if (hasIdentifiers(op) && dbContext) { - bool missingGrid = false; - if (!ignoreMissingGrids) { - const auto gridsNeeded = op->gridsNeeded( - dbContext, - gridAvailabilityUse == - CoordinateOperationContext:: - GridAvailabilityUse::KNOWN_AVAILABLE); - for (const auto &gridDesc : gridsNeeded) { - if (!gridDesc.available) { - missingGrid = true; - break; - } - } - } - if (!missingGrid) { - foundRegisteredTransformWithAllGridsAvailable = true; - break; - } - } - } - if (!foundRegisteredTransformWithAllGridsAvailable && srcGeogCRS && - !srcGeogCRS->_isEquivalentTo( - geogDst, util::IComparable::Criterion::EQUIVALENT) && - !srcGeogCRS->is2DPartOf3D(NN_NO_CHECK(geogDst), dbContext)) { - auto verticalTransformsTmp = createOperations( - componentsSrc[1], - NN_NO_CHECK(srcGeogCRS) - ->promoteTo3D(std::string(), dbContext), - context); - bool foundRegisteredTransform = false; - foundRegisteredTransformWithAllGridsAvailable = false; - for (const auto &op : verticalTransformsTmp) { - if (hasIdentifiers(op) && dbContext) { - bool missingGrid = false; - if (!ignoreMissingGrids) { - const auto gridsNeeded = op->gridsNeeded( - dbContext, - gridAvailabilityUse == - CoordinateOperationContext:: - GridAvailabilityUse::KNOWN_AVAILABLE); - for (const auto &gridDesc : gridsNeeded) { - if (!gridDesc.available) { - missingGrid = true; - break; - } - } - } - foundRegisteredTransform = true; - if (!missingGrid) { - foundRegisteredTransformWithAllGridsAvailable = - true; - break; - } - } - } - if (foundRegisteredTransformWithAllGridsAvailable) { - verticalTransforms = verticalTransformsTmp; - } else if (foundRegisteredTransform) { - verticalTransforms.insert(verticalTransforms.end(), - verticalTransformsTmp.begin(), - verticalTransformsTmp.end()); - } - } - } - - if (horizTransforms.empty() || verticalTransforms.empty()) { - res = horizTransforms; - return; - } - - typedef std::pair<std::vector<CoordinateOperationNNPtr>, - std::vector<CoordinateOperationNNPtr>> - PairOfTransforms; - std::map<std::string, PairOfTransforms> - cacheHorizToInterpAndInterpToTarget; - - for (const auto &verticalTransform : verticalTransforms) { -#ifdef TRACE_CREATE_OPERATIONS - ENTER_BLOCK("Considering vertical transform " + - objectAsStr(verticalTransform.get())); -#endif - crs::GeographicCRSPtr interpolationGeogCRS = - getInterpolationGeogCRS(verticalTransform, dbContext); - if (interpolationGeogCRS) { -#ifdef TRACE_CREATE_OPERATIONS - logTrace("Using " + objectAsStr(interpolationGeogCRS.get()) + - " as interpolation CRS"); -#endif - std::vector<CoordinateOperationNNPtr> srcToInterpOps; - std::vector<CoordinateOperationNNPtr> interpToTargetOps; - - std::string key; - const auto &ids = interpolationGeogCRS->identifiers(); - if (!ids.empty()) { - key = - (*ids.front()->codeSpace()) + ':' + ids.front()->code(); - } - - const auto computeOpsToInterp = - [&srcToInterpOps, &interpToTargetOps, &componentsSrc, - &interpolationGeogCRS, &targetCRS, &dbContext, - &context]() { - srcToInterpOps = createOperations( - componentsSrc[0], NN_NO_CHECK(interpolationGeogCRS), - context); - auto target2D = - targetCRS->demoteTo2D(std::string(), dbContext); - if (!componentsSrc[0]->isEquivalentTo( - target2D.get(), - util::IComparable::Criterion::EQUIVALENT)) { - // We do the transformation from the - // interpolationCRS - // to the target one in 3D (see #2225) - // But we don't do that between sourceCRS and - // interpolationCRS, as this would mess with an - // orthometric elevation. - auto interp3D = interpolationGeogCRS->promoteTo3D( - std::string(), dbContext); - interpToTargetOps = - createOperations(interp3D, targetCRS, context); - } - }; - - if (!key.empty()) { - auto iter = cacheHorizToInterpAndInterpToTarget.find(key); - if (iter == cacheHorizToInterpAndInterpToTarget.end()) { -#ifdef TRACE_CREATE_OPERATIONS - ENTER_BLOCK("looking for horizontal transformation " - "from source to interpCRS and interpCRS to " - "target"); -#endif - computeOpsToInterp(); - cacheHorizToInterpAndInterpToTarget[key] = - PairOfTransforms(srcToInterpOps, interpToTargetOps); - } else { - srcToInterpOps = iter->second.first; - interpToTargetOps = iter->second.second; - } - } else { -#ifdef TRACE_CREATE_OPERATIONS - ENTER_BLOCK("looking for horizontal transformation " - "from source to interpCRS and interpCRS to " - "target"); -#endif - computeOpsToInterp(); - } - -#ifdef TRACE_CREATE_OPERATIONS - ENTER_BLOCK("creating HorizVerticalHorizPROJBased operations"); -#endif - for (const auto &srcToInterp : srcToInterpOps) { - if (interpToTargetOps.empty()) { - try { - auto op = createHorizVerticalHorizPROJBased( - sourceCRS, targetCRS, srcToInterp, - verticalTransform, srcToInterp->inverse(), - interpolationGeogCRS, true); - res.emplace_back(op); - } catch (const std::exception &) { - } - } else { - for (const auto &interpToTarget : interpToTargetOps) { - - if (useDifferentTransformationsForSameSourceTarget( - srcToInterp, interpToTarget)) { - continue; - } - - try { - auto op = createHorizVerticalHorizPROJBased( - sourceCRS, targetCRS, srcToInterp, - verticalTransform, interpToTarget, - interpolationGeogCRS, true); - res.emplace_back(op); - } catch (const std::exception &) { - } - } - } - } - } else { - // This case is probably only correct if - // verticalTransform and horizTransform are independent - // and in particular that verticalTransform does not - // involve a grid, because of the rather arbitrary order - // horizontal then vertical applied - for (const auto &horizTransform : horizTransforms) { - try { - auto op = createHorizVerticalPROJBased( - sourceCRS, targetCRS, horizTransform, - verticalTransform, disallowEmptyIntersection); - res.emplace_back(op); - } catch (const std::exception &) { - } - } - } - } - } -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::createOperationsToGeod( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::GeodeticCRS *geodDst, - std::vector<CoordinateOperationNNPtr> &res) { - - auto cs = cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( - common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE); - auto intermGeog3DCRS = - util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create( - util::PropertyMap() - .set(common::IdentifiedObject::NAME_KEY, geodDst->nameStr()) - .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - metadata::Extent::WORLD), - geodDst->datum(), geodDst->datumEnsemble(), cs)); - auto sourceToGeog3DOps = - createOperations(sourceCRS, intermGeog3DCRS, context); - auto geog3DToTargetOps = - createOperations(intermGeog3DCRS, targetCRS, context); - if (!geog3DToTargetOps.empty()) { - for (const auto &op : sourceToGeog3DOps) { - auto newOp = op->shallowClone(); - setCRSs(newOp.get(), sourceCRS, intermGeog3DCRS); - try { - res.emplace_back(ConcatenatedOperation::createComputeMetadata( - {newOp, geog3DToTargetOps.front()}, - disallowEmptyIntersection)); - } catch (const InvalidOperationEmptyIntersection &) { - } - } - } -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::createOperationsCompoundToCompound( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::CompoundCRS *compoundSrc, - const crs::CompoundCRS *compoundDst, - std::vector<CoordinateOperationNNPtr> &res) { - - const auto &componentsSrc = compoundSrc->componentReferenceSystems(); - const auto &componentsDst = compoundDst->componentReferenceSystems(); - if (componentsSrc.empty() || componentsSrc.size() != componentsDst.size()) { - return; - } - const auto srcGeog = componentsSrc[0]->extractGeographicCRS(); - const auto dstGeog = componentsDst[0]->extractGeographicCRS(); - if (srcGeog == nullptr || dstGeog == nullptr) { - return; - } - - std::vector<CoordinateOperationNNPtr> verticalTransforms; - if (componentsSrc.size() >= 2 && componentsSrc[1]->extractVerticalCRS() && - componentsDst[1]->extractVerticalCRS()) { - if (!componentsSrc[1]->_isEquivalentTo(componentsDst[1].get())) { - verticalTransforms = - createOperations(componentsSrc[1], componentsDst[1], context); - } - } - - // 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 - // there is a geoid model for EGM96 referenced to WGS 84 - // and a geoid model for Belfast height referenced to ETRS89 - if (verticalTransforms.size() == 1 && - verticalTransforms.front()->hasBallparkTransformation()) { - auto dbContext = - context.context->getAuthorityFactory()->databaseContext(); - const auto intermGeogSrc = - srcGeog->promoteTo3D(std::string(), dbContext); - const bool intermGeogSrcIsSameAsIntermGeogDst = - srcGeog->_isEquivalentTo(dstGeog.get()); - const auto intermGeogDst = - intermGeogSrcIsSameAsIntermGeogDst - ? intermGeogSrc - : dstGeog->promoteTo3D(std::string(), dbContext); - const auto opsSrcToGeog = - createOperations(sourceCRS, intermGeogSrc, context); - const auto opsGeogToTarget = - createOperations(intermGeogDst, targetCRS, context); - const bool hasNonTrivalSrcTransf = - !opsSrcToGeog.empty() && - !opsSrcToGeog.front()->hasBallparkTransformation(); - const bool hasNonTrivialTargetTransf = - !opsGeogToTarget.empty() && - !opsGeogToTarget.front()->hasBallparkTransformation(); - if (hasNonTrivalSrcTransf && hasNonTrivialTargetTransf) { - const auto opsGeogSrcToGeogDst = - createOperations(intermGeogSrc, intermGeogDst, context); - for (const auto &op1 : opsSrcToGeog) { - if (op1->hasBallparkTransformation()) { - // std::cerr << "excluded " << op1->nameStr() << std::endl; - continue; - } - for (const auto &op2 : opsGeogSrcToGeogDst) { - for (const auto &op3 : opsGeogToTarget) { - if (op3->hasBallparkTransformation()) { - // std::cerr << "excluded " << op3->nameStr() << - // std::endl; - continue; - } - try { - res.emplace_back( - ConcatenatedOperation::createComputeMetadata( - intermGeogSrcIsSameAsIntermGeogDst - ? std::vector< - CoordinateOperationNNPtr>{op1, - op3} - : std::vector< - CoordinateOperationNNPtr>{op1, - op2, - op3}, - disallowEmptyIntersection)); - } catch (const std::exception &) { - } - } - } - } - } - if (!res.empty()) { - return; - } - } - - for (const auto &verticalTransform : verticalTransforms) { - auto interpolationGeogCRS = NN_NO_CHECK(srcGeog); - auto interpTransformCRS = verticalTransform->interpolationCRS(); - if (interpTransformCRS) { - auto nn_interpTransformCRS = NN_NO_CHECK(interpTransformCRS); - if (dynamic_cast<const crs::GeographicCRS *>( - nn_interpTransformCRS.get())) { - interpolationGeogCRS = NN_NO_CHECK( - util::nn_dynamic_pointer_cast<crs::GeographicCRS>( - nn_interpTransformCRS)); - } - } else { - auto compSrc0BoundCrs = - dynamic_cast<crs::BoundCRS *>(componentsSrc[0].get()); - auto compDst0BoundCrs = - dynamic_cast<crs::BoundCRS *>(componentsDst[0].get()); - if (compSrc0BoundCrs && compDst0BoundCrs && - dynamic_cast<crs::GeographicCRS *>( - compSrc0BoundCrs->hubCRS().get()) && - compSrc0BoundCrs->hubCRS()->_isEquivalentTo( - compDst0BoundCrs->hubCRS().get())) { - interpolationGeogCRS = NN_NO_CHECK( - util::nn_dynamic_pointer_cast<crs::GeographicCRS>( - compSrc0BoundCrs->hubCRS())); - } - } - auto opSrcCRSToGeogCRS = - createOperations(componentsSrc[0], interpolationGeogCRS, context); - auto opGeogCRStoDstCRS = - createOperations(interpolationGeogCRS, componentsDst[0], context); - for (const auto &opSrc : opSrcCRSToGeogCRS) { - for (const auto &opDst : opGeogCRStoDstCRS) { - - try { - auto op = createHorizVerticalHorizPROJBased( - sourceCRS, targetCRS, opSrc, verticalTransform, opDst, - interpolationGeogCRS, true); - res.emplace_back(op); - } catch (const InvalidOperationEmptyIntersection &) { - } catch (const io::FormattingException &) { - } - } - } - } - - if (verticalTransforms.empty()) { - auto resTmp = - createOperations(componentsSrc[0], componentsDst[0], context); - for (const auto &op : resTmp) { - auto opClone = op->shallowClone(); - setCRSs(opClone.get(), sourceCRS, targetCRS); - res.emplace_back(opClone); - } - } -} - -// --------------------------------------------------------------------------- - -void CoordinateOperationFactory::Private::createOperationsBoundToCompound( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context &context, const crs::BoundCRS *boundSrc, - const crs::CompoundCRS *compoundDst, - std::vector<CoordinateOperationNNPtr> &res) { - - const auto &authFactory = context.context->getAuthorityFactory(); - const auto dbContext = - authFactory ? authFactory->databaseContext().as_nullable() : nullptr; - - const auto &componentsDst = compoundDst->componentReferenceSystems(); - if (!componentsDst.empty()) { - auto compDst0BoundCrs = - dynamic_cast<crs::BoundCRS *>(componentsDst[0].get()); - if (compDst0BoundCrs) { - auto boundSrcHubAsGeogCRS = - dynamic_cast<crs::GeographicCRS *>(boundSrc->hubCRS().get()); - auto compDst0BoundCrsHubAsGeogCRS = - dynamic_cast<crs::GeographicCRS *>( - compDst0BoundCrs->hubCRS().get()); - if (boundSrcHubAsGeogCRS && compDst0BoundCrsHubAsGeogCRS) { - const auto boundSrcHubAsGeogCRSDatum = - boundSrcHubAsGeogCRS->datumNonNull(dbContext); - const auto compDst0BoundCrsHubAsGeogCRSDatum = - compDst0BoundCrsHubAsGeogCRS->datumNonNull(dbContext); - if (boundSrcHubAsGeogCRSDatum->_isEquivalentTo( - compDst0BoundCrsHubAsGeogCRSDatum.get())) { - auto cs = cs::EllipsoidalCS:: - createLatitudeLongitudeEllipsoidalHeight( - common::UnitOfMeasure::DEGREE, - common::UnitOfMeasure::METRE); - auto intermGeog3DCRS = util::nn_static_pointer_cast< - crs::CRS>(crs::GeographicCRS::create( - util::PropertyMap() - .set(common::IdentifiedObject::NAME_KEY, - boundSrcHubAsGeogCRS->nameStr()) - .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - metadata::Extent::WORLD), - boundSrcHubAsGeogCRS->datum(), - boundSrcHubAsGeogCRS->datumEnsemble(), cs)); - auto sourceToGeog3DOps = - createOperations(sourceCRS, intermGeog3DCRS, context); - auto geog3DToTargetOps = - createOperations(intermGeog3DCRS, targetCRS, context); - for (const auto &opSrc : sourceToGeog3DOps) { - for (const auto &opDst : geog3DToTargetOps) { - if (opSrc->targetCRS() && opDst->sourceCRS() && - !opSrc->targetCRS()->_isEquivalentTo( - opDst->sourceCRS().get())) { - // Shouldn't happen normally, but typically - // one of them can be 2D and the other 3D - // due to above createOperations() not - // exactly setting the expected source and - // target CRS. - // So create an adapter operation... - auto intermOps = createOperations( - NN_NO_CHECK(opSrc->targetCRS()), - NN_NO_CHECK(opDst->sourceCRS()), context); - if (!intermOps.empty()) { - res.emplace_back( - ConcatenatedOperation:: - createComputeMetadata( - {opSrc, intermOps.front(), - opDst}, - disallowEmptyIntersection)); - } - } else { - res.emplace_back( - ConcatenatedOperation:: - createComputeMetadata( - {opSrc, opDst}, - disallowEmptyIntersection)); - } - } - } - return; - } - } - } - } - - // There might be better things to do, but for now just ignore the - // transformation of the bound CRS - res = createOperations(boundSrc->baseCRS(), targetCRS, context); -} -//! @endcond - -// --------------------------------------------------------------------------- - -/** \brief Find a list of CoordinateOperation from sourceCRS to targetCRS. - * - * The operations are sorted with the most relevant ones first: by - * descending - * area (intersection of the transformation area with the area of interest, - * or intersection of the transformation with the area of use of the CRS), - * and - * by increasing accuracy. Operations with unknown accuracy are sorted last, - * whatever their area. - * - * When one of the source or target CRS has a vertical component but not the - * other one, the one that has no vertical component is automatically promoted - * to a 3D version, where its vertical axis is the ellipsoidal height in metres, - * using the ellipsoid of the base geodetic CRS. - * - * @param sourceCRS source CRS. - * @param targetCRS target CRS. - * @param context Search context. - * @return a list - */ -std::vector<CoordinateOperationNNPtr> -CoordinateOperationFactory::createOperations( - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - const CoordinateOperationContextNNPtr &context) const { - -#ifdef TRACE_CREATE_OPERATIONS - ENTER_FUNCTION(); -#endif - // Look if we are called on CRS that have a link to a 'canonical' - // BoundCRS - // If so, use that one as input - const auto &srcBoundCRS = sourceCRS->canonicalBoundCRS(); - const auto &targetBoundCRS = targetCRS->canonicalBoundCRS(); - auto l_sourceCRS = srcBoundCRS ? NN_NO_CHECK(srcBoundCRS) : sourceCRS; - auto l_targetCRS = targetBoundCRS ? NN_NO_CHECK(targetBoundCRS) : targetCRS; - const auto &authFactory = context->getAuthorityFactory(); - - metadata::ExtentPtr sourceCRSExtent; - auto l_resolvedSourceCRS = - crs::CRS::getResolvedCRS(l_sourceCRS, authFactory, sourceCRSExtent); - metadata::ExtentPtr targetCRSExtent; - auto l_resolvedTargetCRS = - crs::CRS::getResolvedCRS(l_targetCRS, authFactory, targetCRSExtent); - Private::Context contextPrivate(sourceCRSExtent, targetCRSExtent, context); - - if (context->getSourceAndTargetCRSExtentUse() == - CoordinateOperationContext::SourceTargetCRSExtentUse::INTERSECTION) { - if (sourceCRSExtent && targetCRSExtent && - !sourceCRSExtent->intersects(NN_NO_CHECK(targetCRSExtent))) { - return std::vector<CoordinateOperationNNPtr>(); - } - } - - return filterAndSort(Private::createOperations(l_resolvedSourceCRS, - l_resolvedTargetCRS, - contextPrivate), - context, sourceCRSExtent, targetCRSExtent); -} - -// --------------------------------------------------------------------------- - -/** \brief Instantiate a CoordinateOperationFactory. - */ -CoordinateOperationFactoryNNPtr CoordinateOperationFactory::create() { - return NN_NO_CHECK( - CoordinateOperationFactory::make_unique<CoordinateOperationFactory>()); -} - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -InverseCoordinateOperation::~InverseCoordinateOperation() = default; - -// --------------------------------------------------------------------------- - -InverseCoordinateOperation::InverseCoordinateOperation( - const CoordinateOperationNNPtr &forwardOperationIn, - bool wktSupportsInversion) - : forwardOperation_(forwardOperationIn), - wktSupportsInversion_(wktSupportsInversion) {} - -// --------------------------------------------------------------------------- - -void InverseCoordinateOperation::setPropertiesFromForward() { - setProperties( - createPropertiesForInverse(forwardOperation_.get(), false, false)); - setAccuracies(forwardOperation_->coordinateOperationAccuracies()); - if (forwardOperation_->sourceCRS() && forwardOperation_->targetCRS()) { - setCRSs(forwardOperation_.get(), true); - } - setHasBallparkTransformation( - forwardOperation_->hasBallparkTransformation()); -} - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr InverseCoordinateOperation::inverse() const { - return forwardOperation_; -} - -// --------------------------------------------------------------------------- - -void InverseCoordinateOperation::_exportToPROJString( - io::PROJStringFormatter *formatter) const { - formatter->startInversion(); - forwardOperation_->_exportToPROJString(formatter); - formatter->stopInversion(); -} - -// --------------------------------------------------------------------------- - -bool InverseCoordinateOperation::_isEquivalentTo( - const util::IComparable *other, util::IComparable::Criterion criterion, - const io::DatabaseContextPtr &dbContext) const { - auto otherICO = dynamic_cast<const InverseCoordinateOperation *>(other); - if (otherICO == nullptr || - !ObjectUsage::_isEquivalentTo(other, criterion, dbContext)) { - return false; - } - return inverse()->_isEquivalentTo(otherICO->inverse().get(), criterion, - dbContext); -} - -// --------------------------------------------------------------------------- - -PROJBasedOperation::~PROJBasedOperation() = default; - -// --------------------------------------------------------------------------- - -PROJBasedOperation::PROJBasedOperation(const OperationMethodNNPtr &methodIn) - : SingleOperation(methodIn) {} - -// --------------------------------------------------------------------------- - -PROJBasedOperationNNPtr PROJBasedOperation::create( - const util::PropertyMap &properties, const std::string &PROJString, - const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { - auto method = OperationMethod::create( - util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, - "PROJ-based operation method: " + PROJString), - std::vector<GeneralOperationParameterNNPtr>{}); - auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(method); - op->assignSelf(op); - op->projString_ = PROJString; - if (sourceCRS && targetCRS) { - op->setCRSs(NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), nullptr); - } - op->setProperties( - addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation")); - op->setAccuracies(accuracies); - return op; -} - -// --------------------------------------------------------------------------- - -PROJBasedOperationNNPtr PROJBasedOperation::create( - const util::PropertyMap &properties, - const io::IPROJStringExportableNNPtr &projExportable, bool inverse, - const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - const crs::CRSPtr &interpolationCRS, - const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies, - bool hasBallparkTransformation) { - - auto formatter = io::PROJStringFormatter::create(); - if (inverse) { - formatter->startInversion(); - } - projExportable->_exportToPROJString(formatter.get()); - if (inverse) { - formatter->stopInversion(); - } - auto projString = formatter->toString(); - - auto method = OperationMethod::create( - util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, - "PROJ-based operation method (approximate): " + - projString), - std::vector<GeneralOperationParameterNNPtr>{}); - auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(method); - op->assignSelf(op); - op->projString_ = projString; - op->setCRSs(sourceCRS, targetCRS, interpolationCRS); - op->setProperties( - addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation")); - op->setAccuracies(accuracies); - op->projStringExportable_ = projExportable.as_nullable(); - op->inverse_ = inverse; - op->setHasBallparkTransformation(hasBallparkTransformation); - return op; -} - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr PROJBasedOperation::inverse() const { - - if (projStringExportable_ && sourceCRS() && targetCRS()) { - return util::nn_static_pointer_cast<CoordinateOperation>( - PROJBasedOperation::create( - createPropertiesForInverse(this, false, false), - NN_NO_CHECK(projStringExportable_), !inverse_, - NN_NO_CHECK(targetCRS()), NN_NO_CHECK(sourceCRS()), - interpolationCRS(), coordinateOperationAccuracies(), - hasBallparkTransformation())); - } - - auto formatter = io::PROJStringFormatter::create(); - formatter->startInversion(); - try { - formatter->ingestPROJString(projString_); - } catch (const io::ParsingException &e) { - throw util::UnsupportedOperationException( - std::string("PROJBasedOperation::inverse() failed: ") + e.what()); - } - formatter->stopInversion(); - - auto op = PROJBasedOperation::create( - createPropertiesForInverse(this, false, false), formatter->toString(), - targetCRS(), sourceCRS(), coordinateOperationAccuracies()); - if (sourceCRS() && targetCRS()) { - op->setCRSs(NN_NO_CHECK(targetCRS()), NN_NO_CHECK(sourceCRS()), - interpolationCRS()); - } - op->setHasBallparkTransformation(hasBallparkTransformation()); - return util::nn_static_pointer_cast<CoordinateOperation>(op); -} - -// --------------------------------------------------------------------------- - -void PROJBasedOperation::_exportToWKT(io::WKTFormatter *formatter) const { - - if (sourceCRS() && targetCRS()) { - exportTransformationToWKT(formatter); - return; - } - - const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; - if (!isWKT2) { - throw io::FormattingException( - "PROJBasedOperation can only be exported to WKT2"); - } - - formatter->startNode(io::WKTConstants::CONVERSION, false); - formatter->addQuotedString(nameStr()); - method()->_exportToWKT(formatter); - - for (const auto ¶mValue : parameterValues()) { - paramValue->_exportToWKT(formatter); - } - formatter->endNode(); -} - -// --------------------------------------------------------------------------- - -void PROJBasedOperation::_exportToJSON( - io::JSONFormatter *formatter) const // throw(FormattingException) -{ - auto writer = formatter->writer(); - auto objectContext(formatter->MakeObjectContext( - (sourceCRS() && targetCRS()) ? "Transformation" : "Conversion", - !identifiers().empty())); - - writer->AddObjKey("name"); - auto l_name = nameStr(); - if (l_name.empty()) { - writer->Add("unnamed"); - } else { - writer->Add(l_name); - } - - if (sourceCRS() && targetCRS()) { - writer->AddObjKey("source_crs"); - formatter->setAllowIDInImmediateChild(); - sourceCRS()->_exportToJSON(formatter); - - writer->AddObjKey("target_crs"); - formatter->setAllowIDInImmediateChild(); - targetCRS()->_exportToJSON(formatter); - } - - writer->AddObjKey("method"); - formatter->setOmitTypeInImmediateChild(); - formatter->setAllowIDInImmediateChild(); - method()->_exportToJSON(formatter); - - const auto &l_parameterValues = parameterValues(); - if (!l_parameterValues.empty()) { - writer->AddObjKey("parameters"); - { - auto parametersContext(writer->MakeArrayContext(false)); - for (const auto &genOpParamvalue : l_parameterValues) { - formatter->setAllowIDInImmediateChild(); - formatter->setOmitTypeInImmediateChild(); - genOpParamvalue->_exportToJSON(formatter); - } - } - } -} - -// --------------------------------------------------------------------------- - -void PROJBasedOperation::_exportToPROJString( - io::PROJStringFormatter *formatter) const { - if (projStringExportable_) { - if (inverse_) { - formatter->startInversion(); - } - projStringExportable_->_exportToPROJString(formatter); - if (inverse_) { - formatter->stopInversion(); - } - return; - } - - try { - formatter->ingestPROJString(projString_); - } catch (const io::ParsingException &e) { - throw io::FormattingException( - std::string("PROJBasedOperation::exportToPROJString() failed: ") + - e.what()); - } -} - -// --------------------------------------------------------------------------- - -CoordinateOperationNNPtr PROJBasedOperation::_shallowClone() const { - auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(*this); - op->assignSelf(op); - op->setCRSs(this, false); - return util::nn_static_pointer_cast<CoordinateOperation>(op); -} - -// --------------------------------------------------------------------------- - -std::set<GridDescription> -PROJBasedOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext, - bool considerKnownGridsAsAvailable) const { - std::set<GridDescription> res; - - try { - auto formatterOut = io::PROJStringFormatter::create(); - auto formatter = io::PROJStringFormatter::create(); - formatter->ingestPROJString(exportToPROJString(formatterOut.get())); - const auto usedGridNames = formatter->getUsedGridNames(); - for (const auto &shortName : usedGridNames) { - GridDescription desc; - desc.shortName = shortName; - if (databaseContext) { - databaseContext->lookForGridInfo( - desc.shortName, considerKnownGridsAsAvailable, - desc.fullName, desc.packageName, desc.url, - desc.directDownload, desc.openLicense, desc.available); - } - res.insert(desc); - } - } catch (const io::ParsingException &) { - } - - return res; -} - -//! @endcond - -// --------------------------------------------------------------------------- - -} // namespace operation - -namespace crs { -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress - -crs::CRSNNPtr CRS::getResolvedCRS(const crs::CRSNNPtr &crs, - const io::AuthorityFactoryPtr &authFactory, - metadata::ExtentPtr &extentOut) { - const auto &ids = crs->identifiers(); - const auto &name = crs->nameStr(); - - bool approxExtent; - extentOut = getExtentPossiblySynthetized(crs, approxExtent); - - // We try to "identify" the provided CRS with the ones of the database, - // but in a more restricted way that what identify() does. - // If we get a match from id in priority, and from name as a fallback, and - // that they are equivalent to the input CRS, then use the identified CRS. - // Even if they aren't equivalent, we update extentOut with the one of the - // identified CRS if our input one is absent/not reliable. - - const auto tryToIdentifyByName = [&crs, &name, &authFactory, approxExtent, - &extentOut]( - io::AuthorityFactory::ObjectType objectType) { - if (name != "unknown" && name != "unnamed") { - auto matches = authFactory->createObjectsFromName( - name, {objectType}, false, 2); - if (matches.size() == 1) { - const auto match = - util::nn_static_pointer_cast<crs::CRS>(matches.front()); - if (approxExtent || !extentOut) { - extentOut = getExtent(match); - } - if (match->isEquivalentTo( - crs.get(), util::IComparable::Criterion::EQUIVALENT)) { - return match; - } - } - } - return crs; - }; - - auto geogCRS = dynamic_cast<crs::GeographicCRS *>(crs.get()); - if (geogCRS && authFactory) { - if (!ids.empty()) { - const auto tmpAuthFactory = io::AuthorityFactory::create( - authFactory->databaseContext(), *ids.front()->codeSpace()); - try { - auto resolvedCrs( - tmpAuthFactory->createGeographicCRS(ids.front()->code())); - if (approxExtent || !extentOut) { - extentOut = getExtent(resolvedCrs); - } - if (resolvedCrs->isEquivalentTo( - crs.get(), util::IComparable::Criterion::EQUIVALENT)) { - return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs); - } - } catch (const std::exception &) { - } - } else { - return tryToIdentifyByName( - geogCRS->coordinateSystem()->axisList().size() == 2 - ? io::AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS - : io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS); - } - } - - auto projectedCrs = dynamic_cast<crs::ProjectedCRS *>(crs.get()); - if (projectedCrs && authFactory) { - if (!ids.empty()) { - const auto tmpAuthFactory = io::AuthorityFactory::create( - authFactory->databaseContext(), *ids.front()->codeSpace()); - try { - auto resolvedCrs( - tmpAuthFactory->createProjectedCRS(ids.front()->code())); - if (approxExtent || !extentOut) { - extentOut = getExtent(resolvedCrs); - } - if (resolvedCrs->isEquivalentTo( - crs.get(), util::IComparable::Criterion::EQUIVALENT)) { - return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs); - } - } catch (const std::exception &) { - } - } else { - return tryToIdentifyByName( - io::AuthorityFactory::ObjectType::PROJECTED_CRS); - } - } - - auto compoundCrs = dynamic_cast<crs::CompoundCRS *>(crs.get()); - if (compoundCrs && authFactory) { - if (!ids.empty()) { - const auto tmpAuthFactory = io::AuthorityFactory::create( - authFactory->databaseContext(), *ids.front()->codeSpace()); - try { - auto resolvedCrs( - tmpAuthFactory->createCompoundCRS(ids.front()->code())); - if (approxExtent || !extentOut) { - extentOut = getExtent(resolvedCrs); - } - if (resolvedCrs->isEquivalentTo( - crs.get(), util::IComparable::Criterion::EQUIVALENT)) { - return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs); - } - } catch (const std::exception &) { - } - } else { - auto outCrs = tryToIdentifyByName( - io::AuthorityFactory::ObjectType::COMPOUND_CRS); - const auto &components = compoundCrs->componentReferenceSystems(); - if (outCrs.get() != crs.get()) { - bool hasGeoid = false; - if (components.size() == 2) { - auto vertCRS = - dynamic_cast<crs::VerticalCRS *>(components[1].get()); - if (vertCRS && !vertCRS->geoidModel().empty()) { - hasGeoid = true; - } - } - if (!hasGeoid) { - return outCrs; - } - } - if (approxExtent || !extentOut) { - // If we still did not get a reliable extent, then try to - // resolve the components of the compoundCRS, and take the - // intersection of their extent. - extentOut = metadata::ExtentPtr(); - for (const auto &component : components) { - metadata::ExtentPtr componentExtent; - getResolvedCRS(component, authFactory, componentExtent); - if (extentOut && componentExtent) - extentOut = extentOut->intersection( - NN_NO_CHECK(componentExtent)); - else if (componentExtent) - extentOut = componentExtent; - } - } - } - } - return crs; -} - -//! @endcond - -} // namespace crs -NS_PROJ_END diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index 573dd6db..9d58f733 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -5188,7 +5188,8 @@ BoundCRSNNPtr BoundCRS::createFromNadgrids(const CRSNNPtr &baseCRSIn, " (with Greenwich prime meridian)"), sourceGeographicCRS->datumNonNull(nullptr)->ellipsoid(), util::optional<std::string>(), datum::PrimeMeridian::GREENWICH), - sourceGeographicCRS->coordinateSystem()); + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE)); } std::string transformationName = transformationSourceCRS->nameStr(); transformationName += " to WGS84"; diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp index 2a03fd4e..0a5f58a4 100644 --- a/src/iso19111/factory.cpp +++ b/src/iso19111/factory.cpp @@ -39,12 +39,14 @@ #include "proj/metadata.hpp" #include "proj/util.hpp" -#include "proj/internal/coordinateoperation_internal.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "proj/internal/lru_cache.hpp" #include "proj/internal/tracing.hpp" +#include "operation/coordinateoperation_internal.hpp" +#include "operation/parammappings.hpp" + #include "sqlite3_utils.hpp" #include <cmath> diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index 8a42e7ee..dc51c5d9 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -53,7 +53,11 @@ #include "proj/metadata.hpp" #include "proj/util.hpp" -#include "proj/internal/coordinateoperation_internal.hpp" +#include "operation/coordinateoperation_internal.hpp" +#include "operation/esriparammappings.hpp" +#include "operation/oputils.hpp" +#include "operation/parammappings.hpp" + #include "proj/internal/coordinatesystem_internal.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" @@ -4234,7 +4238,8 @@ createBoundCRSSourceTransformationCRS(const crs::CRSPtr &sourceCRS, sourceGeographicCRS->datum()->ellipsoid(), util::optional<std::string>(), datum::PrimeMeridian::GREENWICH), - sourceGeographicCRS->coordinateSystem()) + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE)) .as_nullable(); } } else { diff --git a/src/iso19111/operation/concatenatedoperation.cpp b/src/iso19111/operation/concatenatedoperation.cpp new file mode 100644 index 00000000..ce4b015a --- /dev/null +++ b/src/iso19111/operation/concatenatedoperation.cpp @@ -0,0 +1,710 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/common.hpp" +#include "proj/coordinateoperation.hpp" +#include "proj/crs.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include "coordinateoperation_internal.hpp" +#include "oputils.hpp" + +// PROJ include order is sensitive +// clang-format off +#include "proj.h" +#include "proj_internal.h" // M_PI +// clang-format on + +#include "proj_json_streaming_writer.hpp" + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <cstring> +#include <memory> +#include <set> +#include <string> +#include <vector> + +using namespace NS_PROJ::internal; + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ConcatenatedOperation::Private { + std::vector<CoordinateOperationNNPtr> operations_{}; + bool computedName_ = false; + + explicit Private(const std::vector<CoordinateOperationNNPtr> &operationsIn) + : operations_(operationsIn) {} + Private(const Private &) = default; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ConcatenatedOperation::~ConcatenatedOperation() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ConcatenatedOperation::ConcatenatedOperation(const ConcatenatedOperation &other) + : CoordinateOperation(other), + d(internal::make_unique<Private>(*(other.d))) {} +//! @endcond + +// --------------------------------------------------------------------------- + +ConcatenatedOperation::ConcatenatedOperation( + const std::vector<CoordinateOperationNNPtr> &operationsIn) + : CoordinateOperation(), d(internal::make_unique<Private>(operationsIn)) {} + +// --------------------------------------------------------------------------- + +/** \brief Return the operation steps of the concatenated operation. + * + * @return the operation steps. + */ +const std::vector<CoordinateOperationNNPtr> & +ConcatenatedOperation::operations() const { + return d->operations_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static bool compareStepCRS(const crs::CRS *a, const crs::CRS *b) { + const auto &aIds = a->identifiers(); + const auto &bIds = b->identifiers(); + if (aIds.size() == 1 && bIds.size() == 1 && + aIds[0]->code() == bIds[0]->code() && + *aIds[0]->codeSpace() == *bIds[0]->codeSpace()) { + return true; + } + return a->_isEquivalentTo(b, util::IComparable::Criterion::EQUIVALENT); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a ConcatenatedOperation + * + * @param properties See \ref general_properties. At minimum the name should + * be + * defined. + * @param operationsIn Vector of the CoordinateOperation steps. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + * @throws InvalidOperation + */ +ConcatenatedOperationNNPtr ConcatenatedOperation::create( + const util::PropertyMap &properties, + const std::vector<CoordinateOperationNNPtr> &operationsIn, + const std::vector<metadata::PositionalAccuracyNNPtr> + &accuracies) // throw InvalidOperation +{ + if (operationsIn.size() < 2) { + throw InvalidOperation( + "ConcatenatedOperation must have at least 2 operations"); + } + crs::CRSPtr lastTargetCRS; + + crs::CRSPtr interpolationCRS; + bool interpolationCRSValid = true; + for (size_t i = 0; i < operationsIn.size(); i++) { + auto l_sourceCRS = operationsIn[i]->sourceCRS(); + auto l_targetCRS = operationsIn[i]->targetCRS(); + + if (interpolationCRSValid) { + auto subOpInterpCRS = operationsIn[i]->interpolationCRS(); + if (interpolationCRS == nullptr) + interpolationCRS = subOpInterpCRS; + else if (subOpInterpCRS == nullptr || + !(subOpInterpCRS->isEquivalentTo( + interpolationCRS.get(), + util::IComparable::Criterion::EQUIVALENT))) { + interpolationCRS = nullptr; + interpolationCRSValid = false; + } + } + + if (l_sourceCRS == nullptr || l_targetCRS == nullptr) { + throw InvalidOperation("At least one of the operation lacks a " + "source and/or target CRS"); + } + if (i >= 1) { + if (!compareStepCRS(l_sourceCRS.get(), lastTargetCRS.get())) { +#ifdef DEBUG_CONCATENATED_OPERATION + std::cerr << "Step " << i - 1 << ": " + << operationsIn[i - 1]->nameStr() << std::endl; + std::cerr << "Step " << i << ": " << operationsIn[i]->nameStr() + << std::endl; + { + auto f(io::WKTFormatter::create( + io::WKTFormatter::Convention::WKT2_2019)); + std::cerr << "Source CRS of step " << i << ":" << std::endl; + std::cerr << l_sourceCRS->exportToWKT(f.get()) << std::endl; + } + { + auto f(io::WKTFormatter::create( + io::WKTFormatter::Convention::WKT2_2019)); + std::cerr << "Target CRS of step " << i - 1 << ":" + << std::endl; + std::cerr << lastTargetCRS->exportToWKT(f.get()) + << std::endl; + } +#endif + throw InvalidOperation( + "Inconsistent chaining of CRS in operations"); + } + } + lastTargetCRS = l_targetCRS; + } + auto op = ConcatenatedOperation::nn_make_shared<ConcatenatedOperation>( + operationsIn); + op->assignSelf(op); + op->setProperties(properties); + op->setCRSs(NN_NO_CHECK(operationsIn[0]->sourceCRS()), + NN_NO_CHECK(operationsIn.back()->targetCRS()), + interpolationCRS); + op->setAccuracies(accuracies); +#ifdef DEBUG_CONCATENATED_OPERATION + { + auto f( + io::WKTFormatter::create(io::WKTFormatter::Convention::WKT2_2019)); + std::cerr << "ConcatenatedOperation::create()" << std::endl; + std::cerr << op->exportToWKT(f.get()) << std::endl; + } +#endif + return op; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +void ConcatenatedOperation::fixStepsDirection( + const crs::CRSNNPtr &concatOpSourceCRS, + const crs::CRSNNPtr &concatOpTargetCRS, + std::vector<CoordinateOperationNNPtr> &operationsInOut) { + + // Set of heuristics to assign CRS to steps, and possibly reverse them. + + const auto isGeographic = [](const crs::CRS *crs) -> bool { + return dynamic_cast<const crs::GeographicCRS *>(crs) != nullptr; + }; + + const auto isGeocentric = [](const crs::CRS *crs) -> bool { + auto geodCRS = dynamic_cast<const crs::GeodeticCRS *>(crs); + if (geodCRS && geodCRS->coordinateSystem()->axisList().size() == 3) + return true; + return false; + }; + + for (size_t i = 0; i < operationsInOut.size(); ++i) { + auto &op = operationsInOut[i]; + auto l_sourceCRS = op->sourceCRS(); + auto l_targetCRS = op->targetCRS(); + auto conv = dynamic_cast<const Conversion *>(op.get()); + if (conv && i == 0 && !l_sourceCRS && !l_targetCRS) { + auto derivedCRS = + dynamic_cast<const crs::DerivedCRS *>(concatOpSourceCRS.get()); + if (derivedCRS) { + if (i + 1 < operationsInOut.size()) { + // use the sourceCRS of the next operation as our target CRS + l_targetCRS = operationsInOut[i + 1]->sourceCRS(); + // except if it looks like the next operation should + // actually be reversed !!! + if (l_targetCRS && + !compareStepCRS(l_targetCRS.get(), + derivedCRS->baseCRS().get()) && + operationsInOut[i + 1]->targetCRS() && + compareStepCRS( + operationsInOut[i + 1]->targetCRS().get(), + derivedCRS->baseCRS().get())) { + l_targetCRS = operationsInOut[i + 1]->targetCRS(); + } + } + if (!l_targetCRS) { + l_targetCRS = derivedCRS->baseCRS().as_nullable(); + } + auto invConv = + util::nn_dynamic_pointer_cast<InverseConversion>(op); + auto nn_targetCRS = NN_NO_CHECK(l_targetCRS); + if (invConv) { + invConv->inverse()->setCRSs(nn_targetCRS, concatOpSourceCRS, + nullptr); + op->setCRSs(concatOpSourceCRS, nn_targetCRS, nullptr); + } else { + op->setCRSs(nn_targetCRS, concatOpSourceCRS, nullptr); + op = op->inverse(); + } + } else if (i + 1 < operationsInOut.size()) { + /* coverity[copy_paste_error] */ + l_targetCRS = operationsInOut[i + 1]->sourceCRS(); + if (l_targetCRS) { + op->setCRSs(concatOpSourceCRS, NN_NO_CHECK(l_targetCRS), + nullptr); + } + } + } else if (conv && i + 1 == operationsInOut.size() && !l_sourceCRS && + !l_targetCRS) { + auto derivedCRS = + dynamic_cast<const crs::DerivedCRS *>(concatOpTargetCRS.get()); + if (derivedCRS) { + if (i >= 1) { + // use the sourceCRS of the previous operation as our source + // CRS + l_sourceCRS = operationsInOut[i - 1]->targetCRS(); + // except if it looks like the previous operation should + // actually be reversed !!! + if (l_sourceCRS && + !compareStepCRS(l_sourceCRS.get(), + derivedCRS->baseCRS().get()) && + operationsInOut[i - 1]->sourceCRS() && + compareStepCRS( + operationsInOut[i - 1]->sourceCRS().get(), + derivedCRS->baseCRS().get())) { + l_targetCRS = operationsInOut[i - 1]->sourceCRS(); + } + } + if (!l_sourceCRS) { + l_sourceCRS = derivedCRS->baseCRS().as_nullable(); + } + op->setCRSs(NN_NO_CHECK(l_sourceCRS), concatOpTargetCRS, + nullptr); + } else if (i >= 1) { + l_sourceCRS = operationsInOut[i - 1]->targetCRS(); + if (l_sourceCRS) { + derivedCRS = dynamic_cast<const crs::DerivedCRS *>( + l_sourceCRS.get()); + if (conv->isEquivalentTo( + derivedCRS->derivingConversion().get(), + util::IComparable::Criterion::EQUIVALENT)) { + op->setCRSs(concatOpTargetCRS, NN_NO_CHECK(l_sourceCRS), + nullptr); + op = op->inverse(); + } + op->setCRSs(NN_NO_CHECK(l_sourceCRS), concatOpTargetCRS, + nullptr); + } + } + } else if (conv && i > 0 && i < operationsInOut.size() - 1) { + // For an intermediate conversion, use the target CRS of the + // previous step and the source CRS of the next step + l_sourceCRS = operationsInOut[i - 1]->targetCRS(); + l_targetCRS = operationsInOut[i + 1]->sourceCRS(); + if (l_sourceCRS && l_targetCRS) { + op->setCRSs(NN_NO_CHECK(l_sourceCRS), NN_NO_CHECK(l_targetCRS), + nullptr); + } + } else if (!conv && l_sourceCRS && l_targetCRS) { + + // Transformations might be mentioned in their forward directions, + // whereas we should instead use the reverse path. + auto prevOpTarget = (i == 0) ? concatOpSourceCRS.as_nullable() + : operationsInOut[i - 1]->targetCRS(); + if (compareStepCRS(l_sourceCRS.get(), prevOpTarget.get())) { + // do nothing + } else if (compareStepCRS(l_targetCRS.get(), prevOpTarget.get())) { + op = op->inverse(); + } + // Below is needed for EPSG:9103 which chains NAD83(2011) geographic + // 2D with NAD83(2011) geocentric + else if (l_sourceCRS->nameStr() == prevOpTarget->nameStr() && + ((isGeographic(l_sourceCRS.get()) && + isGeocentric(prevOpTarget.get())) || + (isGeocentric(l_sourceCRS.get()) && + isGeographic(prevOpTarget.get())))) { + auto newOp(Conversion::createGeographicGeocentric( + NN_NO_CHECK(prevOpTarget), NN_NO_CHECK(l_sourceCRS))); + operationsInOut.insert(operationsInOut.begin() + i, newOp); + } else if (l_targetCRS->nameStr() == prevOpTarget->nameStr() && + ((isGeographic(l_targetCRS.get()) && + isGeocentric(prevOpTarget.get())) || + (isGeocentric(l_targetCRS.get()) && + isGeographic(prevOpTarget.get())))) { + auto newOp(Conversion::createGeographicGeocentric( + NN_NO_CHECK(prevOpTarget), NN_NO_CHECK(l_targetCRS))); + operationsInOut.insert(operationsInOut.begin() + i, newOp); + } + } + } + + if (!operationsInOut.empty()) { + auto l_sourceCRS = operationsInOut.front()->sourceCRS(); + if (l_sourceCRS && + !compareStepCRS(l_sourceCRS.get(), concatOpSourceCRS.get())) { + throw InvalidOperation("The source CRS of the first step of " + "concatenated operation is not the same " + "as the source CRS of the concatenated " + "operation itself"); + } + + auto l_targetCRS = operationsInOut.back()->targetCRS(); + if (l_targetCRS && + !compareStepCRS(l_targetCRS.get(), concatOpTargetCRS.get())) { + if (l_targetCRS->nameStr() == concatOpTargetCRS->nameStr() && + ((isGeographic(l_targetCRS.get()) && + isGeocentric(concatOpTargetCRS.get())) || + (isGeocentric(l_targetCRS.get()) && + isGeographic(concatOpTargetCRS.get())))) { + auto newOp(Conversion::createGeographicGeocentric( + NN_NO_CHECK(l_targetCRS), concatOpTargetCRS)); + operationsInOut.push_back(newOp); + } else { + throw InvalidOperation("The target CRS of the last step of " + "concatenated operation is not the same " + "as the target CRS of the concatenated " + "operation itself"); + } + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a ConcatenatedOperation, or return a single + * coordinate + * operation. + * + * This computes its accuracy from the sum of its member operations, its + * extent + * + * @param operationsIn Vector of the CoordinateOperation steps. + * @param checkExtent Whether we should check the non-emptyness of the + * intersection + * of the extents of the operations + * @throws InvalidOperation + */ +CoordinateOperationNNPtr ConcatenatedOperation::createComputeMetadata( + const std::vector<CoordinateOperationNNPtr> &operationsIn, + bool checkExtent) // throw InvalidOperation +{ + util::PropertyMap properties; + + if (operationsIn.size() == 1) { + return operationsIn[0]; + } + + std::vector<CoordinateOperationNNPtr> flattenOps; + bool hasBallparkTransformation = false; + for (const auto &subOp : operationsIn) { + hasBallparkTransformation |= subOp->hasBallparkTransformation(); + auto subOpConcat = + dynamic_cast<const ConcatenatedOperation *>(subOp.get()); + if (subOpConcat) { + auto subOps = subOpConcat->operations(); + for (const auto &subSubOp : subOps) { + flattenOps.emplace_back(subSubOp); + } + } else { + flattenOps.emplace_back(subOp); + } + } + + // Remove consecutive inverse operations + if (flattenOps.size() > 2) { + std::vector<size_t> indices; + for (size_t i = 0; i < flattenOps.size(); ++i) + indices.push_back(i); + while (true) { + bool bHasChanged = false; + for (size_t i = 0; i + 1 < indices.size(); ++i) { + if (flattenOps[indices[i]]->_isEquivalentTo( + flattenOps[indices[i + 1]]->inverse().get(), + util::IComparable::Criterion::EQUIVALENT) && + flattenOps[indices[i]]->sourceCRS()->_isEquivalentTo( + flattenOps[indices[i + 1]]->targetCRS().get(), + util::IComparable::Criterion::EQUIVALENT)) { + indices.erase(indices.begin() + i, indices.begin() + i + 2); + bHasChanged = true; + break; + } + } + // We bail out if indices.size() == 2, because potentially + // the last 2 remaining ones could auto-cancel, and we would have + // to have a special case for that (and this happens in practice). + if (!bHasChanged || indices.size() <= 2) + break; + } + if (indices.size() < flattenOps.size()) { + std::vector<CoordinateOperationNNPtr> flattenOpsNew; + for (size_t i = 0; i < indices.size(); ++i) { + flattenOpsNew.emplace_back(flattenOps[indices[i]]); + } + flattenOps = std::move(flattenOpsNew); + } + } + + if (flattenOps.size() == 1) { + return flattenOps[0]; + } + + properties.set(common::IdentifiedObject::NAME_KEY, + computeConcatenatedName(flattenOps)); + + bool emptyIntersection = false; + auto extent = getExtent(flattenOps, false, emptyIntersection); + if (checkExtent && emptyIntersection) { + std::string msg( + "empty intersection of area of validity of concatenated " + "operations"); + throw InvalidOperationEmptyIntersection(msg); + } + if (extent) { + properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + NN_NO_CHECK(extent)); + } + + std::vector<metadata::PositionalAccuracyNNPtr> accuracies; + const double accuracy = getAccuracy(flattenOps); + if (accuracy >= 0.0) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create(toString(accuracy))); + } + + auto op = create(properties, flattenOps, accuracies); + op->setHasBallparkTransformation(hasBallparkTransformation); + op->d->computedName_ = true; + return op; +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr ConcatenatedOperation::inverse() const { + std::vector<CoordinateOperationNNPtr> inversedOperations; + auto l_operations = operations(); + inversedOperations.reserve(l_operations.size()); + for (const auto &operation : l_operations) { + inversedOperations.emplace_back(operation->inverse()); + } + std::reverse(inversedOperations.begin(), inversedOperations.end()); + + auto properties = createPropertiesForInverse(this, false, false); + if (d->computedName_) { + properties.set(common::IdentifiedObject::NAME_KEY, + computeConcatenatedName(inversedOperations)); + } + + auto op = + create(properties, inversedOperations, coordinateOperationAccuracies()); + op->d->computedName_ = d->computedName_; + op->setHasBallparkTransformation(hasBallparkTransformation()); + return op; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ConcatenatedOperation::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2 || !formatter->use2019Keywords()) { + throw io::FormattingException( + "Transformation can only be exported to WKT2:2019"); + } + + formatter->startNode(io::WKTConstants::CONCATENATEDOPERATION, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + if (formatter->use2019Keywords()) { + const auto &version = operationVersion(); + if (version.has_value()) { + formatter->startNode(io::WKTConstants::VERSION, false); + formatter->addQuotedString(*version); + formatter->endNode(); + } + } + + exportSourceCRSAndTargetCRSToWKT(this, formatter); + + const bool canExportOperationId = + !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId()); + + const bool hasDomains = !domains().empty(); + if (hasDomains) { + formatter->pushDisableUsage(); + } + + for (const auto &operation : operations()) { + formatter->startNode(io::WKTConstants::STEP, false); + if (canExportOperationId && !operation->identifiers().empty()) { + // fake that top node has no id, so that the operation id is + // considered + formatter->pushHasId(false); + operation->_exportToWKT(formatter); + formatter->popHasId(); + } else { + operation->_exportToWKT(formatter); + } + formatter->endNode(); + } + + if (hasDomains) { + formatter->popDisableUsage(); + } + + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ConcatenatedOperation::_exportToJSON( + io::JSONFormatter *formatter) const // throw(FormattingException) +{ + auto writer = formatter->writer(); + auto objectContext(formatter->MakeObjectContext("ConcatenatedOperation", + !identifiers().empty())); + + writer->AddObjKey("name"); + auto l_name = nameStr(); + if (l_name.empty()) { + writer->Add("unnamed"); + } else { + writer->Add(l_name); + } + + writer->AddObjKey("source_crs"); + formatter->setAllowIDInImmediateChild(); + sourceCRS()->_exportToJSON(formatter); + + writer->AddObjKey("target_crs"); + formatter->setAllowIDInImmediateChild(); + targetCRS()->_exportToJSON(formatter); + + writer->AddObjKey("steps"); + { + auto parametersContext(writer->MakeArrayContext(false)); + for (const auto &operation : operations()) { + formatter->setAllowIDInImmediateChild(); + operation->_exportToJSON(formatter); + } + } + + ObjectUsage::baseExportToJSON(formatter); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateOperationNNPtr ConcatenatedOperation::_shallowClone() const { + auto op = + ConcatenatedOperation::nn_make_shared<ConcatenatedOperation>(*this); + std::vector<CoordinateOperationNNPtr> ops; + for (const auto &subOp : d->operations_) { + ops.emplace_back(subOp->shallowClone()); + } + op->d->operations_ = ops; + op->assignSelf(op); + op->setCRSs(this, false); + return util::nn_static_pointer_cast<CoordinateOperation>(op); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void ConcatenatedOperation::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(FormattingException) +{ + for (const auto &operation : operations()) { + operation->_exportToPROJString(formatter); + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool ConcatenatedOperation::_isEquivalentTo( + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { + auto otherCO = dynamic_cast<const ConcatenatedOperation *>(other); + if (otherCO == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { + return false; + } + const auto &steps = operations(); + const auto &otherSteps = otherCO->operations(); + if (steps.size() != otherSteps.size()) { + return false; + } + for (size_t i = 0; i < steps.size(); i++) { + if (!steps[i]->_isEquivalentTo(otherSteps[i].get(), criterion, + dbContext)) { + return false; + } + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +std::set<GridDescription> ConcatenatedOperation::gridsNeeded( + const io::DatabaseContextPtr &databaseContext, + bool considerKnownGridsAsAvailable) const { + std::set<GridDescription> res; + for (const auto &operation : operations()) { + const auto l_gridsNeeded = operation->gridsNeeded( + databaseContext, considerKnownGridsAsAvailable); + for (const auto &gridDesc : l_gridsNeeded) { + res.insert(gridDesc); + } + } + return res; +} + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END diff --git a/src/iso19111/operation/conversion.cpp b/src/iso19111/operation/conversion.cpp new file mode 100644 index 00000000..1808dbe7 --- /dev/null +++ b/src/iso19111/operation/conversion.cpp @@ -0,0 +1,3955 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/common.hpp" +#include "proj/coordinateoperation.hpp" +#include "proj/crs.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" +#include "proj/internal/tracing.hpp" + +#include "coordinateoperation_internal.hpp" +#include "esriparammappings.hpp" +#include "operationmethod_private.hpp" +#include "oputils.hpp" +#include "parammappings.hpp" +#include "vectorofvaluesparams.hpp" + +// PROJ include order is sensitive +// clang-format off +#include "proj.h" +#include "proj_internal.h" // M_PI +// clang-format on +#include "proj_constants.h" + +#include "proj_json_streaming_writer.hpp" + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <cstring> +#include <memory> +#include <set> +#include <string> +#include <vector> + +using namespace NS_PROJ::internal; + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +//! @cond Doxygen_Suppress +constexpr double UTM_LATITUDE_OF_NATURAL_ORIGIN = 0.0; +constexpr double UTM_SCALE_FACTOR = 0.9996; +constexpr double UTM_FALSE_EASTING = 500000.0; +constexpr double UTM_NORTH_FALSE_NORTHING = 0.0; +constexpr double UTM_SOUTH_FALSE_NORTHING = 10000000.0; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Conversion::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +Conversion::Conversion(const OperationMethodNNPtr &methodIn, + const std::vector<GeneralParameterValueNNPtr> &values) + : SingleOperation(methodIn), d(nullptr) { + setParameterValues(values); +} + +// --------------------------------------------------------------------------- + +Conversion::Conversion(const Conversion &other) + : CoordinateOperation(other), SingleOperation(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Conversion::~Conversion() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ConversionNNPtr Conversion::shallowClone() const { + auto conv = Conversion::nn_make_shared<Conversion>(*this); + conv->assignSelf(conv); + conv->setCRSs(this, false); + return conv; +} + +CoordinateOperationNNPtr Conversion::_shallowClone() const { + return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ConversionNNPtr +Conversion::alterParametersLinearUnit(const common::UnitOfMeasure &unit, + bool convertToNewUnit) const { + + std::vector<GeneralParameterValueNNPtr> newValues; + bool changesDone = false; + for (const auto &genOpParamvalue : parameterValues()) { + bool updated = false; + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶mValue = opParamvalue->parameterValue(); + if (paramValue->type() == ParameterValue::Type::MEASURE) { + const auto &measure = paramValue->value(); + if (measure.unit().type() == + common::UnitOfMeasure::Type::LINEAR) { + if (!measure.unit()._isEquivalentTo( + unit, util::IComparable::Criterion::EQUIVALENT)) { + const double newValue = + convertToNewUnit ? measure.convertToUnit(unit) + : measure.value(); + newValues.emplace_back(OperationParameterValue::create( + opParamvalue->parameter(), + ParameterValue::create( + common::Measure(newValue, unit)))); + updated = true; + } + } + } + } + if (updated) { + changesDone = true; + } else { + newValues.emplace_back(genOpParamvalue); + } + } + if (changesDone) { + auto conv = create(util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, "unknown"), + method(), newValues); + conv->setCRSs(this, false); + return conv; + } else { + return NN_NO_CHECK( + util::nn_dynamic_pointer_cast<Conversion>(shared_from_this())); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a Conversion from a vector of GeneralParameterValue. + * + * @param properties See \ref general_properties. At minimum the name should be + * defined. + * @param methodIn the operation method. + * @param values the values. + * @return a new Conversion. + * @throws InvalidOperation + */ +ConversionNNPtr Conversion::create(const util::PropertyMap &properties, + const OperationMethodNNPtr &methodIn, + const std::vector<GeneralParameterValueNNPtr> + &values) // throw InvalidOperation +{ + if (methodIn->parameters().size() != values.size()) { + throw InvalidOperation( + "Inconsistent number of parameters and parameter values"); + } + auto conv = Conversion::nn_make_shared<Conversion>(methodIn, values); + conv->assignSelf(conv); + conv->setProperties(properties); + return conv; +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a Conversion and its OperationMethod + * + * @param propertiesConversion See \ref general_properties of the conversion. + * At minimum the name should be defined. + * @param propertiesOperationMethod See \ref general_properties of the operation + * method. At minimum the name should be defined. + * @param parameters the operation parameters. + * @param values the operation values. Constraint: + * values.size() == parameters.size() + * @return a new Conversion. + * @throws InvalidOperation + */ +ConversionNNPtr Conversion::create( + const util::PropertyMap &propertiesConversion, + const util::PropertyMap &propertiesOperationMethod, + const std::vector<OperationParameterNNPtr> ¶meters, + const std::vector<ParameterValueNNPtr> &values) // throw InvalidOperation +{ + OperationMethodNNPtr op( + OperationMethod::create(propertiesOperationMethod, parameters)); + + if (parameters.size() != values.size()) { + throw InvalidOperation( + "Inconsistent number of parameters and parameter values"); + } + std::vector<GeneralParameterValueNNPtr> generalParameterValues; + generalParameterValues.reserve(values.size()); + for (size_t i = 0; i < values.size(); i++) { + generalParameterValues.push_back( + OperationParameterValue::create(parameters[i], values[i])); + } + return create(propertiesConversion, op, generalParameterValues); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +static util::PropertyMap +getUTMConversionProperty(const util::PropertyMap &properties, int zone, + bool north) { + if (!properties.get(common::IdentifiedObject::NAME_KEY)) { + std::string conversionName("UTM zone "); + conversionName += toString(zone); + conversionName += (north ? 'N' : 'S'); + + return createMapNameEPSGCode(conversionName, + (north ? 16000 : 17000) + zone); + } else { + return properties; + } +} + +// --------------------------------------------------------------------------- + +static ConversionNNPtr +createConversion(const util::PropertyMap &properties, + const MethodMapping *mapping, + const std::vector<ParameterValueNNPtr> &values) { + + std::vector<OperationParameterNNPtr> parameters; + for (int i = 0; mapping->params[i] != nullptr; i++) { + const auto *param = mapping->params[i]; + auto paramProperties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, param->wkt2_name); + if (param->epsg_code != 0) { + paramProperties + .set(metadata::Identifier::CODESPACE_KEY, + metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, param->epsg_code); + } + auto parameter = OperationParameter::create(paramProperties); + parameters.push_back(parameter); + } + + auto methodProperties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, mapping->wkt2_name); + if (mapping->epsg_code != 0) { + methodProperties + .set(metadata::Identifier::CODESPACE_KEY, + metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, mapping->epsg_code); + } + return Conversion::create( + addDefaultNameIfNeeded(properties, mapping->wkt2_name), + methodProperties, parameters, values); +} +//! @endcond + +// --------------------------------------------------------------------------- + +ConversionNNPtr +Conversion::create(const util::PropertyMap &properties, int method_epsg_code, + const std::vector<ParameterValueNNPtr> &values) { + const MethodMapping *mapping = getMapping(method_epsg_code); + assert(mapping); + return createConversion(properties, mapping, values); +} + +// --------------------------------------------------------------------------- + +ConversionNNPtr +Conversion::create(const util::PropertyMap &properties, + const char *method_wkt2_name, + const std::vector<ParameterValueNNPtr> &values) { + const MethodMapping *mapping = getMapping(method_wkt2_name); + assert(mapping); + return createConversion(properties, mapping, values); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a [Universal Transverse Mercator] + *(https://proj.org/operations/projections/utm.html) conversion. + * + * UTM is a family of conversions, of EPSG codes from 16001 to 16060 for the + * northern hemisphere, and 17001 to 17060 for the southern hemisphere, + * based on the Transverse Mercator projection method. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param zone UTM zone number between 1 and 60. + * @param north true for UTM northern hemisphere, false for UTM southern + * hemisphere. + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createUTM(const util::PropertyMap &properties, + int zone, bool north) { + return create( + getUTMConversionProperty(properties, zone, north), + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, + createParams(common::Angle(UTM_LATITUDE_OF_NATURAL_ORIGIN), + common::Angle(zone * 6.0 - 183.0), + common::Scale(UTM_SCALE_FACTOR), + common::Length(UTM_FALSE_EASTING), + common::Length(north ? UTM_NORTH_FALSE_NORTHING + : UTM_SOUTH_FALSE_NORTHING))); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Transverse Mercator] + *(https://proj.org/operations/projections/tmerc.html) projection method. + * + * This method is defined as [EPSG:9807] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9807) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createTransverseMercator( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Gauss Schreiber Transverse + *Mercator] + *(https://proj.org/operations/projections/gstmerc.html) projection method. + * + * This method is also known as Gauss-Laborde Reunion. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createGaussSchreiberTransverseMercator( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, + PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Transverse Mercator South + *Orientated] + *(https://proj.org/operations/projections/tmerc.html) projection method. + * + * This method is defined as [EPSG:9808] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9808) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createTransverseMercatorSouthOriented( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Two Point Equidistant] + *(https://proj.org/operations/projections/tpeqd.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFirstPoint Latitude of first point. + * @param longitudeFirstPoint Longitude of first point. + * @param latitudeSecondPoint Latitude of second point. + * @param longitudeSeconPoint Longitude of second point. + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr +Conversion::createTwoPointEquidistant(const util::PropertyMap &properties, + const common::Angle &latitudeFirstPoint, + const common::Angle &longitudeFirstPoint, + const common::Angle &latitudeSecondPoint, + const common::Angle &longitudeSeconPoint, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, + createParams(latitudeFirstPoint, longitudeFirstPoint, + latitudeSecondPoint, longitudeSeconPoint, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the Tunisia Mapping Grid projection + * method. + * + * This method is defined as [EPSG:9816] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9816) + * + * \note There is currently no implementation of the method formulas in PROJ. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createTunisiaMappingGrid( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, EPSG_CODE_METHOD_TUNISIA_MAPPING_GRID, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Albers Conic Equal Area] + *(https://proj.org/operations/projections/aea.html) projection method. + * + * This method is defined as [EPSG:9822] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9822) + * + * @note the order of arguments is conformant with the corresponding EPSG + * mode and different than OGRSpatialReference::setACEA() of GDAL <= 2.3 + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFalseOrigin See \ref latitude_false_origin + * @param longitudeFalseOrigin See \ref longitude_false_origin + * @param latitudeFirstParallel See \ref latitude_first_std_parallel + * @param latitudeSecondParallel See \ref latitude_second_std_parallel + * @param eastingFalseOrigin See \ref easting_false_origin + * @param northingFalseOrigin See \ref northing_false_origin + * @return a new Conversion. + */ +ConversionNNPtr +Conversion::createAlbersEqualArea(const util::PropertyMap &properties, + const common::Angle &latitudeFalseOrigin, + const common::Angle &longitudeFalseOrigin, + const common::Angle &latitudeFirstParallel, + const common::Angle &latitudeSecondParallel, + const common::Length &eastingFalseOrigin, + const common::Length &northingFalseOrigin) { + return create(properties, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA, + createParams(latitudeFalseOrigin, longitudeFalseOrigin, + latitudeFirstParallel, latitudeSecondParallel, + eastingFalseOrigin, northingFalseOrigin)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Lambert Conic Conformal 1SP] + *(https://proj.org/operations/projections/lcc.html) projection method. + * + * This method is defined as [EPSG:9801] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9801) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLambertConicConformal_1SP( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Lambert Conic Conformal (2SP)] + *(https://proj.org/operations/projections/lcc.html) projection method. + * + * This method is defined as [EPSG:9802] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9802) + * + * @note the order of arguments is conformant with the corresponding EPSG + * mode and different than OGRSpatialReference::setLCC() of GDAL <= 2.3 + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFalseOrigin See \ref latitude_false_origin + * @param longitudeFalseOrigin See \ref longitude_false_origin + * @param latitudeFirstParallel See \ref latitude_first_std_parallel + * @param latitudeSecondParallel See \ref latitude_second_std_parallel + * @param eastingFalseOrigin See \ref easting_false_origin + * @param northingFalseOrigin See \ref northing_false_origin + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLambertConicConformal_2SP( + const util::PropertyMap &properties, + const common::Angle &latitudeFalseOrigin, + const common::Angle &longitudeFalseOrigin, + const common::Angle &latitudeFirstParallel, + const common::Angle &latitudeSecondParallel, + const common::Length &eastingFalseOrigin, + const common::Length &northingFalseOrigin) { + return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + createParams(latitudeFalseOrigin, longitudeFalseOrigin, + latitudeFirstParallel, latitudeSecondParallel, + eastingFalseOrigin, northingFalseOrigin)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Lambert Conic Conformal (2SP + *Michigan)] + *(https://proj.org/operations/projections/lcc.html) projection method. + * + * This method is defined as [EPSG:1051] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1051) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFalseOrigin See \ref latitude_false_origin + * @param longitudeFalseOrigin See \ref longitude_false_origin + * @param latitudeFirstParallel See \ref latitude_first_std_parallel + * @param latitudeSecondParallel See \ref latitude_second_std_parallel + * @param eastingFalseOrigin See \ref easting_false_origin + * @param northingFalseOrigin See \ref northing_false_origin + * @param ellipsoidScalingFactor Ellipsoid scaling factor. + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLambertConicConformal_2SP_Michigan( + const util::PropertyMap &properties, + const common::Angle &latitudeFalseOrigin, + const common::Angle &longitudeFalseOrigin, + const common::Angle &latitudeFirstParallel, + const common::Angle &latitudeSecondParallel, + const common::Length &eastingFalseOrigin, + const common::Length &northingFalseOrigin, + const common::Scale &ellipsoidScalingFactor) { + return create(properties, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, + createParams(latitudeFalseOrigin, longitudeFalseOrigin, + latitudeFirstParallel, latitudeSecondParallel, + eastingFalseOrigin, northingFalseOrigin, + ellipsoidScalingFactor)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Lambert Conic Conformal (2SP + *Belgium)] + *(https://proj.org/operations/projections/lcc.html) projection method. + * + * This method is defined as [EPSG:9803] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9803) + * + * \warning The formulas used currently in PROJ are, incorrectly, the ones of + * the regular LCC_2SP method. + * + * @note the order of arguments is conformant with the corresponding EPSG + * mode and different than OGRSpatialReference::setLCCB() of GDAL <= 2.3 + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFalseOrigin See \ref latitude_false_origin + * @param longitudeFalseOrigin See \ref longitude_false_origin + * @param latitudeFirstParallel See \ref latitude_first_std_parallel + * @param latitudeSecondParallel See \ref latitude_second_std_parallel + * @param eastingFalseOrigin See \ref easting_false_origin + * @param northingFalseOrigin See \ref northing_false_origin + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLambertConicConformal_2SP_Belgium( + const util::PropertyMap &properties, + const common::Angle &latitudeFalseOrigin, + const common::Angle &longitudeFalseOrigin, + const common::Angle &latitudeFirstParallel, + const common::Angle &latitudeSecondParallel, + const common::Length &eastingFalseOrigin, + const common::Length &northingFalseOrigin) { + + return create(properties, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM, + createParams(latitudeFalseOrigin, longitudeFalseOrigin, + latitudeFirstParallel, latitudeSecondParallel, + eastingFalseOrigin, northingFalseOrigin)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Modified Azimuthal + *Equidistant] + *(https://proj.org/operations/projections/aeqd.html) projection method. + * + * This method is defined as [EPSG:9832] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9832) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeNatOrigin See \ref center_latitude + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createAzimuthalEquidistant( + const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, + const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, + createParams(latitudeNatOrigin, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Guam Projection] + *(https://proj.org/operations/projections/aeqd.html) projection method. + * + * This method is defined as [EPSG:9831] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9831) + * + * @param properties See \ref general_properties of the conversion. If the name + *is + * not provided, it is automatically set. + * @param latitudeNatOrigin See \ref center_latitude + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createGuamProjection( + const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, + const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_GUAM_PROJECTION, + createParams(latitudeNatOrigin, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Bonne] + *(https://proj.org/operations/projections/bonne.html) projection method. + * + * This method is defined as [EPSG:9827] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9827) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeNatOrigin See \ref center_latitude . PROJ calls its the + * standard parallel 1. + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createBonne(const util::PropertyMap &properties, + const common::Angle &latitudeNatOrigin, + const common::Angle &longitudeNatOrigin, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_BONNE, + createParams(latitudeNatOrigin, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Lambert Cylindrical Equal Area + *(Spherical)] + *(https://proj.org/operations/projections/cea.html) projection method. + * + * This method is defined as [EPSG:9834] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9834) + * + * \warning The PROJ cea computation code would select the ellipsoidal form if + * a non-spherical ellipsoid is used for the base GeographicCRS. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFirstParallel See \ref latitude_first_std_parallel. + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLambertCylindricalEqualAreaSpherical( + const util::PropertyMap &properties, + const common::Angle &latitudeFirstParallel, + const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, + EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL, + createParams(latitudeFirstParallel, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Lambert Cylindrical Equal Area + *(ellipsoidal form)] + *(https://proj.org/operations/projections/cea.html) projection method. + * + * This method is defined as [EPSG:9835] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9835) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFirstParallel See \ref latitude_first_std_parallel. + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLambertCylindricalEqualArea( + const util::PropertyMap &properties, + const common::Angle &latitudeFirstParallel, + const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, + createParams(latitudeFirstParallel, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Cassini-Soldner] + * (https://proj.org/operations/projections/cass.html) projection method. + * + * This method is defined as [EPSG:9806] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9806) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createCassiniSoldner( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, EPSG_CODE_METHOD_CASSINI_SOLDNER, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Equidistant Conic] + *(https://proj.org/operations/projections/eqdc.html) projection method. + * + * There is no equivalent in EPSG. + * + * @note Although not found in EPSG, the order of arguments is conformant with + * the "spirit" of EPSG and different than OGRSpatialReference::setEC() of GDAL + *<= 2.3 * @param properties See \ref general_properties of the conversion. + *If the name + * is not provided, it is automatically set. + * + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param latitudeFirstParallel See \ref latitude_first_std_parallel + * @param latitudeSecondParallel See \ref latitude_second_std_parallel + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEquidistantConic( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Angle &latitudeFirstParallel, + const common::Angle &latitudeSecondParallel, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC, + createParams(centerLat, centerLong, latitudeFirstParallel, + latitudeSecondParallel, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Eckert I] + * (https://proj.org/operations/projections/eck1.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEckertI(const util::PropertyMap &properties, + const common::Angle ¢erLong, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_I, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Eckert II] + * (https://proj.org/operations/projections/eck2.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEckertII( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_II, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Eckert III] + * (https://proj.org/operations/projections/eck3.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEckertIII( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_III, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Eckert IV] + * (https://proj.org/operations/projections/eck4.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEckertIV( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_IV, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Eckert V] + * (https://proj.org/operations/projections/eck5.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEckertV(const util::PropertyMap &properties, + const common::Angle ¢erLong, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_V, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Eckert VI] + * (https://proj.org/operations/projections/eck6.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEckertVI( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_VI, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Equidistant Cylindrical] + *(https://proj.org/operations/projections/eqc.html) projection method. + * + * This is also known as the Equirectangular method, and in the particular case + * where the latitude of first parallel is 0. + * + * This method is defined as [EPSG:1028] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1028) + * + * @note This is the equivalent OGRSpatialReference::SetEquirectangular2( + * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL <= 2.3, + * where the lat_0 / center_latitude parameter is forced to 0. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFirstParallel See \ref latitude_first_std_parallel. + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEquidistantCylindrical( + const util::PropertyMap &properties, + const common::Angle &latitudeFirstParallel, + const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, + createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Equidistant Cylindrical + *(Spherical)] + *(https://proj.org/operations/projections/eqc.html) projection method. + * + * This is also known as the Equirectangular method, and in the particular case + * where the latitude of first parallel is 0. + * + * This method is defined as [EPSG:1029] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1029) + * + * @note This is the equivalent OGRSpatialReference::SetEquirectangular2( + * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL <= 2.3, + * where the lat_0 / center_latitude parameter is forced to 0. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFirstParallel See \ref latitude_first_std_parallel. + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEquidistantCylindricalSpherical( + const util::PropertyMap &properties, + const common::Angle &latitudeFirstParallel, + const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, + createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Gall (Stereographic)] + * (https://proj.org/operations/projections/gall.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createGall(const util::PropertyMap &properties, + const common::Angle ¢erLong, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Goode Homolosine] + * (https://proj.org/operations/projections/goode.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createGoodeHomolosine( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Interrupted Goode Homolosine] + * (https://proj.org/operations/projections/igh.html) projection method. + * + * There is no equivalent in EPSG. + * + * @note OGRSpatialReference::SetIGH() of GDAL <= 2.3 assumes the 3 + * projection + * parameters to be zero and this is the nominal case. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createInterruptedGoodeHomolosine( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, + PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Geostationary Satellite View] + * (https://proj.org/operations/projections/geos.html) projection method, + * with the sweep angle axis of the viewing instrument being x + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param height Height of the view point above the Earth. + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createGeostationarySatelliteSweepX( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &height, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X, + createParams(centerLong, height, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Geostationary Satellite View] + * (https://proj.org/operations/projections/geos.html) projection method, + * with the sweep angle axis of the viewing instrument being y. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param height Height of the view point above the Earth. + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createGeostationarySatelliteSweepY( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &height, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y, + createParams(centerLong, height, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Gnomonic] + *(https://proj.org/operations/projections/gnom.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createGnomonic( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, PROJ_WKT2_NAME_METHOD_GNOMONIC, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Hotine Oblique Mercator + *(Variant A)] + *(https://proj.org/operations/projections/omerc.html) projection method + * + * This is the variant with the no_uoff parameter, which corresponds to + * GDAL >=2.3 Hotine_Oblique_Mercator projection. + * In this variant, the false grid coordinates are + * defined at the intersection of the initial line and the aposphere (the + * equator on one of the intermediate surfaces inherent in the method), that is + * at the natural origin of the coordinate system). + * + * This method is defined as [EPSG:9812] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9812) + * + * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid = + *90deg, + * this maps to the [Swiss Oblique Mercator] + *(https://proj.org/operations/projections/somerc.html) formulas. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeProjectionCentre See \ref latitude_projection_centre + * @param longitudeProjectionCentre See \ref longitude_projection_centre + * @param azimuthInitialLine See \ref azimuth_initial_line + * @param angleFromRectifiedToSkrewGrid See + * \ref angle_from_recitfied_to_skrew_grid + * @param scale See \ref scale_factor_initial_line + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createHotineObliqueMercatorVariantA( + const util::PropertyMap &properties, + const common::Angle &latitudeProjectionCentre, + const common::Angle &longitudeProjectionCentre, + const common::Angle &azimuthInitialLine, + const common::Angle &angleFromRectifiedToSkrewGrid, + const common::Scale &scale, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, + createParams(latitudeProjectionCentre, longitudeProjectionCentre, + azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Hotine Oblique Mercator + *(Variant B)] + *(https://proj.org/operations/projections/omerc.html) projection method + * + * This is the variant without the no_uoff parameter, which corresponds to + * GDAL >=2.3 Hotine_Oblique_Mercator_Azimuth_Center projection. + * In this variant, the false grid coordinates are defined at the projection + *centre. + * + * This method is defined as [EPSG:9815] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9815) + * + * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid = + *90deg, + * this maps to the [Swiss Oblique Mercator] + *(https://proj.org/operations/projections/somerc.html) formulas. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeProjectionCentre See \ref latitude_projection_centre + * @param longitudeProjectionCentre See \ref longitude_projection_centre + * @param azimuthInitialLine See \ref azimuth_initial_line + * @param angleFromRectifiedToSkrewGrid See + * \ref angle_from_recitfied_to_skrew_grid + * @param scale See \ref scale_factor_initial_line + * @param eastingProjectionCentre See \ref easting_projection_centre + * @param northingProjectionCentre See \ref northing_projection_centre + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createHotineObliqueMercatorVariantB( + const util::PropertyMap &properties, + const common::Angle &latitudeProjectionCentre, + const common::Angle &longitudeProjectionCentre, + const common::Angle &azimuthInitialLine, + const common::Angle &angleFromRectifiedToSkrewGrid, + const common::Scale &scale, const common::Length &eastingProjectionCentre, + const common::Length &northingProjectionCentre) { + return create( + properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, + createParams(latitudeProjectionCentre, longitudeProjectionCentre, + azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale, + eastingProjectionCentre, northingProjectionCentre)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Hotine Oblique Mercator Two + *Point Natural Origin] + *(https://proj.org/operations/projections/omerc.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeProjectionCentre See \ref latitude_projection_centre + * @param latitudePoint1 Latitude of point 1. + * @param longitudePoint1 Latitude of point 1. + * @param latitudePoint2 Latitude of point 2. + * @param longitudePoint2 Longitude of point 2. + * @param scale See \ref scale_factor_initial_line + * @param eastingProjectionCentre See \ref easting_projection_centre + * @param northingProjectionCentre See \ref northing_projection_centre + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin( + const util::PropertyMap &properties, + const common::Angle &latitudeProjectionCentre, + const common::Angle &latitudePoint1, const common::Angle &longitudePoint1, + const common::Angle &latitudePoint2, const common::Angle &longitudePoint2, + const common::Scale &scale, const common::Length &eastingProjectionCentre, + const common::Length &northingProjectionCentre) { + return create( + properties, + PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, + { + ParameterValue::create(latitudeProjectionCentre), + ParameterValue::create(latitudePoint1), + ParameterValue::create(longitudePoint1), + ParameterValue::create(latitudePoint2), + ParameterValue::create(longitudePoint2), + ParameterValue::create(scale), + ParameterValue::create(eastingProjectionCentre), + ParameterValue::create(northingProjectionCentre), + }); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Laborde Oblique Mercator] + *(https://proj.org/operations/projections/labrd.html) projection method. + * + * This method is defined as [EPSG:9813] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9813) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeProjectionCentre See \ref latitude_projection_centre + * @param longitudeProjectionCentre See \ref longitude_projection_centre + * @param azimuthInitialLine See \ref azimuth_initial_line + * @param scale See \ref scale_factor_initial_line + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLabordeObliqueMercator( + const util::PropertyMap &properties, + const common::Angle &latitudeProjectionCentre, + const common::Angle &longitudeProjectionCentre, + const common::Angle &azimuthInitialLine, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR, + createParams(latitudeProjectionCentre, + longitudeProjectionCentre, azimuthInitialLine, + scale, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [International Map of the World + *Polyconic] + *(https://proj.org/operations/projections/imw_p.html) projection method. + * + * There is no equivalent in EPSG. + * + * @note the order of arguments is conformant with the corresponding EPSG + * mode and different than OGRSpatialReference::SetIWMPolyconic() of GDAL <= + *2.3 + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param latitudeFirstParallel See \ref latitude_first_std_parallel + * @param latitudeSecondParallel See \ref latitude_second_std_parallel + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createInternationalMapWorldPolyconic( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Angle &latitudeFirstParallel, + const common::Angle &latitudeSecondParallel, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC, + createParams(centerLong, latitudeFirstParallel, + latitudeSecondParallel, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Krovak (north oriented)] + *(https://proj.org/operations/projections/krovak.html) projection method. + * + * This method is defined as [EPSG:1041] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1041) + * + * The coordinates are returned in the "GIS friendly" order: easting, northing. + * This method is similar to createKrovak(), except that the later returns + * projected values as southing, westing, where + * southing(Krovak) = -northing(Krovak_North) and + * westing(Krovak) = -easting(Krovak_North). + * + * @note The current PROJ implementation of Krovak hard-codes + * colatitudeConeAxis = 30deg17'17.30311" + * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for + * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221). + * It also hard-codes the parameters of the Bessel ellipsoid typically used for + * Krovak. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeProjectionCentre See \ref latitude_projection_centre + * @param longitudeOfOrigin See \ref longitude_of_origin + * @param colatitudeConeAxis See \ref colatitude_cone_axis + * @param latitudePseudoStandardParallel See \ref + *latitude_pseudo_standard_parallel + * @param scaleFactorPseudoStandardParallel See \ref + *scale_factor_pseudo_standard_parallel + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createKrovakNorthOriented( + const util::PropertyMap &properties, + const common::Angle &latitudeProjectionCentre, + const common::Angle &longitudeOfOrigin, + const common::Angle &colatitudeConeAxis, + const common::Angle &latitudePseudoStandardParallel, + const common::Scale &scaleFactorPseudoStandardParallel, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED, + createParams(latitudeProjectionCentre, longitudeOfOrigin, + colatitudeConeAxis, + latitudePseudoStandardParallel, + scaleFactorPseudoStandardParallel, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Krovak] + *(https://proj.org/operations/projections/krovak.html) projection method. + * + * This method is defined as [EPSG:9819] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9819) + * + * The coordinates are returned in the historical order: southing, westing + * This method is similar to createKrovakNorthOriented(), except that the later + *returns + * projected values as easting, northing, where + * easting(Krovak_North) = -westing(Krovak) and + * northing(Krovak_North) = -southing(Krovak). + * + * @note The current PROJ implementation of Krovak hard-codes + * colatitudeConeAxis = 30deg17'17.30311" + * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for + * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221). + * It also hard-codes the parameters of the Bessel ellipsoid typically used for + * Krovak. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeProjectionCentre See \ref latitude_projection_centre + * @param longitudeOfOrigin See \ref longitude_of_origin + * @param colatitudeConeAxis See \ref colatitude_cone_axis + * @param latitudePseudoStandardParallel See \ref + *latitude_pseudo_standard_parallel + * @param scaleFactorPseudoStandardParallel See \ref + *scale_factor_pseudo_standard_parallel + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr +Conversion::createKrovak(const util::PropertyMap &properties, + const common::Angle &latitudeProjectionCentre, + const common::Angle &longitudeOfOrigin, + const common::Angle &colatitudeConeAxis, + const common::Angle &latitudePseudoStandardParallel, + const common::Scale &scaleFactorPseudoStandardParallel, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_KROVAK, + createParams(latitudeProjectionCentre, longitudeOfOrigin, + colatitudeConeAxis, + latitudePseudoStandardParallel, + scaleFactorPseudoStandardParallel, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Lambert Azimuthal Equal Area] + *(https://proj.org/operations/projections/laea.html) projection method. + * + * This method is defined as [EPSG:9820] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9820) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeNatOrigin See \ref center_latitude + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLambertAzimuthalEqualArea( + const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, + const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, + createParams(latitudeNatOrigin, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Miller Cylindrical] + *(https://proj.org/operations/projections/mill.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createMillerCylindrical( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Mercator] + *(https://proj.org/operations/projections/merc.html) projection method. + * + * This is the variant, also known as Mercator (1SP), defined with the scale + * factor. Note that latitude of natural origin (centerLat) is a parameter, + * but unused in the transformation formulas. + * + * This method is defined as [EPSG:9804] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9804) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude . Should be 0. + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createMercatorVariantA( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_A, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Mercator] + *(https://proj.org/operations/projections/merc.html) projection method. + * + * This is the variant, also known as Mercator (2SP), defined with the latitude + * of the first standard parallel (the second standard parallel is implicitly + * the opposite value). The latitude of natural origin is fixed to zero. + * + * This method is defined as [EPSG:9805] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9805) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFirstParallel See \ref latitude_first_std_parallel + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createMercatorVariantB( + const util::PropertyMap &properties, + const common::Angle &latitudeFirstParallel, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_B, + createParams(latitudeFirstParallel, centerLong, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Popular Visualisation Pseudo + *Mercator] + *(https://proj.org/operations/projections/webmerc.html) projection method. + * + * Also known as WebMercator. Mostly/only used for Projected CRS EPSG:3857 + * (WGS 84 / Pseudo-Mercator) + * + * This method is defined as [EPSG:1024] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1024) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude . Usually 0 + * @param centerLong See \ref center_longitude . Usually 0 + * @param falseEasting See \ref false_easting . Usually 0 + * @param falseNorthing See \ref false_northing . Usually 0 + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createPopularVisualisationPseudoMercator( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Mollweide] + * (https://proj.org/operations/projections/moll.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createMollweide( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_MOLLWEIDE, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [New Zealand Map Grid] + * (https://proj.org/operations/projections/nzmg.html) projection method. + * + * This method is defined as [EPSG:9811] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9811) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createNewZealandMappingGrid( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, EPSG_CODE_METHOD_NZMG, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Oblique Stereographic + *(Alternative)] + *(https://proj.org/operations/projections/sterea.html) projection method. + * + * This method is defined as [EPSG:9809] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9809) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createObliqueStereographic( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Orthographic] + *(https://proj.org/operations/projections/ortho.html) projection method. + * + * This method is defined as [EPSG:9840] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9840) + * + * \note Before PROJ 7.2, only the spherical formulation was implemented. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createOrthographic( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, EPSG_CODE_METHOD_ORTHOGRAPHIC, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [American Polyconic] + *(https://proj.org/operations/projections/poly.html) projection method. + * + * This method is defined as [EPSG:9818] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9818) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createAmericanPolyconic( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, EPSG_CODE_METHOD_AMERICAN_POLYCONIC, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Polar Stereographic (Variant + *A)] + *(https://proj.org/operations/projections/stere.html) projection method. + * + * This method is defined as [EPSG:9810] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9810) + * + * This is the variant of polar stereographic defined with a scale factor. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude . Should be 90 deg ou -90 deg. + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createPolarStereographicVariantA( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Polar Stereographic (Variant + *B)] + *(https://proj.org/operations/projections/stere.html) projection method. + * + * This method is defined as [EPSG:9829] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9829) + * + * This is the variant of polar stereographic defined with a latitude of + * standard parallel. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeStandardParallel See \ref latitude_std_parallel + * @param longitudeOfOrigin See \ref longitude_of_origin + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createPolarStereographicVariantB( + const util::PropertyMap &properties, + const common::Angle &latitudeStandardParallel, + const common::Angle &longitudeOfOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, + createParams(latitudeStandardParallel, longitudeOfOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Robinson] + * (https://proj.org/operations/projections/robin.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createRobinson( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_ROBINSON, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Sinusoidal] + * (https://proj.org/operations/projections/sinu.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createSinusoidal( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_SINUSOIDAL, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Stereographic] + *(https://proj.org/operations/projections/stere.html) projection method. + * + * There is no equivalent in EPSG. This method implements the original "Oblique + * Stereographic" method described in "Snyder's Map Projections - A Working + *manual", + * which is different from the "Oblique Stereographic (alternative") method + * implemented in createObliqueStereographic(). + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createStereographic( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Van der Grinten] + * (https://proj.org/operations/projections/vandg.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createVanDerGrinten( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Wagner I] + * (https://proj.org/operations/projections/wag1.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createWagnerI(const util::PropertyMap &properties, + const common::Angle ¢erLong, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_I, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Wagner II] + * (https://proj.org/operations/projections/wag2.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createWagnerII( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_II, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Wagner III] + * (https://proj.org/operations/projections/wag3.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeTrueScale Latitude of true scale. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createWagnerIII( + const util::PropertyMap &properties, const common::Angle &latitudeTrueScale, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_III, + createParams(latitudeTrueScale, centerLong, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Wagner IV] + * (https://proj.org/operations/projections/wag4.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createWagnerIV( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_IV, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Wagner V] + * (https://proj.org/operations/projections/wag5.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createWagnerV(const util::PropertyMap &properties, + const common::Angle ¢erLong, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_V, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Wagner VI] + * (https://proj.org/operations/projections/wag6.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createWagnerVI( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VI, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Wagner VII] + * (https://proj.org/operations/projections/wag7.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createWagnerVII( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VII, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Quadrilateralized Spherical + *Cube] + *(https://proj.org/operations/projections/qsc.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createQuadrilateralizedSphericalCube( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Spherical Cross-Track Height] + *(https://proj.org/operations/projections/sch.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param pegPointLat Peg point latitude. + * @param pegPointLong Peg point longitude. + * @param pegPointHeading Peg point heading. + * @param pegPointHeight Peg point height. + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createSphericalCrossTrackHeight( + const util::PropertyMap &properties, const common::Angle &pegPointLat, + const common::Angle &pegPointLong, const common::Angle &pegPointHeading, + const common::Length &pegPointHeight) { + return create(properties, + PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT, + createParams(pegPointLat, pegPointLong, pegPointHeading, + pegPointHeight)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Equal Earth] + * (https://proj.org/operations/projections/eqearth.html) projection method. + * + * This method is defined as [EPSG:1078] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1078) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEqualEarth( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_EQUAL_EARTH, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the [Vertical Perspective] + * (https://proj.org/operations/projections/nsper.html) projection method. + * + * This method is defined as [EPSG:9838] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9838) + * + * The PROJ implementation of the EPSG Vertical Perspective has the current + * limitations with respect to the method described in EPSG: + * <ul> + * <li> it is a 2D-only method, ignoring the ellipsoidal height of the point to + * project.</li> + * <li> it has only a spherical development.</li> + * <li> the height of the topocentric origin is ignored, and thus assumed to be + * 0.</li> + * </ul> + * + * For completeness, PROJ adds the falseEasting and falseNorthing parameter, + * which are not described in EPSG. They should usually be set to 0. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param topoOriginLat Latitude of topocentric origin + * @param topoOriginLong Longitude of topocentric origin + * @param topoOriginHeight Ellipsoidal height of topocentric origin. Ignored by + * PROJ (that is assumed to be 0) + * @param viewPointHeight Viewpoint height with respect to the + * topocentric/mapping plane. In the case where topoOriginHeight = 0, this is + * the height above the ellipsoid surface at topoOriginLat, topoOriginLong. + * @param falseEasting See \ref false_easting . (not in EPSG) + * @param falseNorthing See \ref false_northing . (not in EPSG) + * @return a new Conversion. + * + * @since 6.3 + */ +ConversionNNPtr Conversion::createVerticalPerspective( + const util::PropertyMap &properties, const common::Angle &topoOriginLat, + const common::Angle &topoOriginLong, const common::Length &topoOriginHeight, + const common::Length &viewPointHeight, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE, + createParams(topoOriginLat, topoOriginLong, topoOriginHeight, + viewPointHeight, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the Pole Rotation method, using + * the conventions of the GRIB 1 and GRIB 2 data formats. + * + * Those are mentioned in the Note 2 of + * https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_temp3-1.shtml + * + * Several conventions for the pole rotation method exists. + * The parameters provided in this method are remapped to the PROJ ob_tran + * operation with: + * <pre> + * +proj=ob_tran +o_proj=longlat +o_lon_p=-rotationAngle + * +o_lat_p=-southPoleLatInUnrotatedCRS + * +lon_0=southPoleLongInUnrotatedCRS + * </pre> + * + * Another implementation of that convention is also in the netcdf-java library: + * https://github.com/Unidata/netcdf-java/blob/3ce72c0cd167609ed8c69152bb4a004d1daa9273/cdm/core/src/main/java/ucar/unidata/geoloc/projection/RotatedLatLon.java + * + * The PROJ implementation of this method assumes a spherical ellipsoid. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param southPoleLatInUnrotatedCRS Latitude of the point from the unrotated + * CRS, expressed in the unrotated CRS, that will become the south pole of the + * rotated CRS. + * @param southPoleLongInUnrotatedCRS Longitude of the point from the unrotated + * CRS, expressed in the unrotated CRS, that will become the south pole of the + * rotated CRS. + * @param axisRotation The angle of rotation about the new polar + * axis (measured clockwise when looking from the southern to the northern pole) + * of the coordinate system, assuming the new axis to have been obtained by + * first rotating the sphere through southPoleLongInUnrotatedCRS degrees about + * the geographic polar axis and then rotating through + * (90 + southPoleLatInUnrotatedCRS) degrees so that the southern pole moved + * along the (previously rotated) Greenwich meridian. + * @return a new Conversion. + * + * @since 7.0 + */ +ConversionNNPtr Conversion::createPoleRotationGRIBConvention( + const util::PropertyMap &properties, + const common::Angle &southPoleLatInUnrotatedCRS, + const common::Angle &southPoleLongInUnrotatedCRS, + const common::Angle &axisRotation) { + return create(properties, + PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION, + createParams(southPoleLatInUnrotatedCRS, + southPoleLongInUnrotatedCRS, axisRotation)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the Change of Vertical Unit + * method. + * + * This method is defined as [EPSG:1069] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1069) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param factor Conversion factor + * @return a new Conversion. + */ +ConversionNNPtr +Conversion::createChangeVerticalUnit(const util::PropertyMap &properties, + const common::Scale &factor) { + return create(properties, createMethodMapNameEPSGCode( + EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT), + VectorOfParameters{ + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR), + }, + VectorOfValues{ + factor, + }); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the Height Depth Reversal + * method. + * + * This method is defined as [EPSG:1068] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1068) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @return a new Conversion. + * @since 6.3 + */ +ConversionNNPtr +Conversion::createHeightDepthReversal(const util::PropertyMap &properties) { + return create(properties, createMethodMapNameEPSGCode( + EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL), + {}, {}); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the Axis order reversal method + * + * This swaps the longitude, latitude axis. + * + * This method is defined as [EPSG:9843] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9843), + * or for 3D as [EPSG:9844] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9844) + * + * @param is3D Whether this should apply on 3D geographicCRS + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createAxisOrderReversal(bool is3D) { + if (is3D) { + return create(createMapNameEPSGCode(AXIS_ORDER_CHANGE_3D_NAME, 15499), + createMethodMapNameEPSGCode( + EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D), + {}, {}); + } else { + return create(createMapNameEPSGCode(AXIS_ORDER_CHANGE_2D_NAME, 15498), + createMethodMapNameEPSGCode( + EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D), + {}, {}); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the Geographic/Geocentric method. + * + * This method is defined as [EPSG:9602] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9602), + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @return a new Conversion. + */ +ConversionNNPtr +Conversion::createGeographicGeocentric(const util::PropertyMap &properties) { + return create(properties, createMethodMapNameEPSGCode( + EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC), + {}, {}); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +ConversionNNPtr +Conversion::createGeographicGeocentric(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS) { + auto properties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + buildOpName("Conversion", sourceCRS, targetCRS)); + auto conv = createGeographicGeocentric(properties); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + return conv; +} + +// --------------------------------------------------------------------------- + +InverseConversion::InverseConversion(const ConversionNNPtr &forward) + : Conversion( + OperationMethod::create(createPropertiesForInverse(forward->method()), + forward->method()->parameters()), + forward->parameterValues()), + InverseCoordinateOperation(forward, true) { + setPropertiesFromForward(); +} + +// --------------------------------------------------------------------------- + +InverseConversion::~InverseConversion() = default; + +// --------------------------------------------------------------------------- + +ConversionNNPtr InverseConversion::inverseAsConversion() const { + return NN_NO_CHECK( + util::nn_dynamic_pointer_cast<Conversion>(forwardOperation_)); +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr +InverseConversion::create(const ConversionNNPtr &forward) { + auto conv = util::nn_make_shared<InverseConversion>(forward); + conv->assignSelf(conv); + return conv; +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr InverseConversion::_shallowClone() const { + auto op = InverseConversion::nn_make_shared<InverseConversion>( + inverseAsConversion()->shallowClone()); + op->assignSelf(op); + op->setCRSs(this, false); + return util::nn_static_pointer_cast<CoordinateOperation>(op); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static bool isAxisOrderReversal2D(int methodEPSGCode) { + return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D; +} + +static bool isAxisOrderReversal3D(int methodEPSGCode) { + return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D; +} + +bool isAxisOrderReversal(int methodEPSGCode) { + return isAxisOrderReversal2D(methodEPSGCode) || + isAxisOrderReversal3D(methodEPSGCode); +} +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr Conversion::inverse() const { + const int methodEPSGCode = method()->getEPSGCode(); + + if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) { + const double convFactor = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); + auto conv = createChangeVerticalUnit( + createPropertiesForInverse(this, false, false), + common::Scale(1.0 / convFactor)); + conv->setCRSs(this, true); + return conv; + } + + const bool l_isAxisOrderReversal2D = isAxisOrderReversal2D(methodEPSGCode); + const bool l_isAxisOrderReversal3D = isAxisOrderReversal3D(methodEPSGCode); + if (l_isAxisOrderReversal2D || l_isAxisOrderReversal3D) { + auto conv = createAxisOrderReversal(l_isAxisOrderReversal3D); + conv->setCRSs(this, true); + return conv; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) { + + auto conv = createGeographicGeocentric( + createPropertiesForInverse(this, false, false)); + conv->setCRSs(this, true); + return conv; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { + + auto conv = createHeightDepthReversal( + createPropertiesForInverse(this, false, false)); + conv->setCRSs(this, true); + return conv; + } + + return InverseConversion::create(NN_NO_CHECK( + util::nn_dynamic_pointer_cast<Conversion>(shared_from_this()))); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static double msfn(double phi, double e2) { + const double sinphi = std::sin(phi); + const double cosphi = std::cos(phi); + return pj_msfn(sinphi, cosphi, e2); +} + +// --------------------------------------------------------------------------- + +static double tsfn(double phi, double ec) { + const double sinphi = std::sin(phi); + return pj_tsfn(phi, sinphi, ec); +} + +// --------------------------------------------------------------------------- + +// Function whose zeroes are the sin of the standard parallels of LCC_2SP +static double lcc_1sp_to_2sp_f(double sinphi, double K, double ec, double n) { + const double x = sinphi; + const double ecx = ec * x; + return (1 - x * x) / (1 - ecx * ecx) - + K * K * std::pow((1.0 - x) / (1.0 + x) * + std::pow((1.0 + ecx) / (1.0 - ecx), ec), + n); +} + +// --------------------------------------------------------------------------- + +// Find the sin of the standard parallels of LCC_2SP +static double find_zero_lcc_1sp_to_2sp_f(double sinphi0, bool bNorth, double K, + double ec) { + double a, b; + double f_a; + if (bNorth) { + // Look for zero above phi0 + a = sinphi0; + b = 1.0; // sin(North pole) + f_a = 1.0; // some positive value, but we only care about the sign + } else { + // Look for zero below phi0 + a = -1.0; // sin(South pole) + b = sinphi0; + f_a = -1.0; // minus infinity in fact, but we only care about the sign + } + // We use dichotomy search. lcc_1sp_to_2sp_f() is positive at sinphi_init, + // has a zero in ]-1,sinphi0[ and ]sinphi0,1[ ranges + for (int N = 0; N < 100; N++) { + double c = (a + b) / 2; + double f_c = lcc_1sp_to_2sp_f(c, K, ec, sinphi0); + if (f_c == 0.0 || (b - a) < 1e-18) { + return c; + } + if ((f_c > 0 && f_a > 0) || (f_c < 0 && f_a < 0)) { + a = c; + f_a = f_c; + } else { + b = c; + } + } + return (a + b) / 2; +} + +static inline double DegToRad(double x) { return x / 180.0 * M_PI; } +static inline double RadToDeg(double x) { return x / M_PI * 180.0; } + +//! @endcond + +// --------------------------------------------------------------------------- + +/** + * \brief Return an equivalent projection. + * + * Currently implemented: + * <ul> + * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP) to + * EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP)</li> + * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP) to + * EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP)</li> + * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP to + * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP</li> + * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP to + * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP</li> + * </ul> + * + * @param targetEPSGCode EPSG code of the target method. + * @return new conversion, or nullptr + */ +ConversionPtr Conversion::convertToOtherMethod(int targetEPSGCode) const { + const int current_epsg_code = method()->getEPSGCode(); + if (current_epsg_code == targetEPSGCode) { + return util::nn_dynamic_pointer_cast<Conversion>(shared_from_this()); + } + + auto geogCRS = dynamic_cast<crs::GeodeticCRS *>(sourceCRS().get()); + if (!geogCRS) { + return nullptr; + } + + const double e2 = geogCRS->ellipsoid()->squaredEccentricity(); + if (e2 < 0) { + return nullptr; + } + + if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_A && + targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && + parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) { + const double k0 = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN); + if (!(k0 > 0 && k0 <= 1.0 + 1e-10)) + return nullptr; + const double dfStdP1Lat = + (k0 >= 1.0) + ? 0.0 + : std::acos(std::sqrt((1.0 - e2) / ((1.0 / (k0 * k0)) - e2))); + auto latitudeFirstParallel = common::Angle( + common::Angle(dfStdP1Lat, common::UnitOfMeasure::RADIAN) + .convertToUnit(common::UnitOfMeasure::DEGREE), + common::UnitOfMeasure::DEGREE); + auto conv = createMercatorVariantB( + util::PropertyMap(), latitudeFirstParallel, + common::Angle(parameterValueMeasure( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), + common::Length( + parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), + common::Length( + parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING))); + conv->setCRSs(this, false); + return conv.as_nullable(); + } + + if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && + targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) { + const double phi1 = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL); + if (!(std::fabs(phi1) < M_PI / 2)) + return nullptr; + const double k0 = msfn(phi1, e2); + auto conv = createMercatorVariantA( + util::PropertyMap(), + common::Angle(0.0, common::UnitOfMeasure::DEGREE), + common::Angle(parameterValueMeasure( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), + common::Scale(k0, common::UnitOfMeasure::SCALE_UNITY), + common::Length( + parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), + common::Length( + parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING))); + conv->setCRSs(this, false); + return conv.as_nullable(); + } + + if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP && + targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { + // Notations m0, t0, n, m1, t1, F are those of the EPSG guidance + // "1.3.1.1 Lambert Conic Conformal (2SP)" and + // "1.3.1.2 Lambert Conic Conformal (1SP)" and + // or Snyder pages 106-109 + auto latitudeOfOrigin = common::Angle(parameterValueMeasure( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN)); + const double phi0 = latitudeOfOrigin.getSIValue(); + const double k0 = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN); + if (!(std::fabs(phi0) < M_PI / 2)) + return nullptr; + if (!(k0 > 0 && k0 <= 1.0 + 1e-10)) + return nullptr; + const double ec = std::sqrt(e2); + const double m0 = msfn(phi0, e2); + const double t0 = tsfn(phi0, ec); + const double n = sin(phi0); + if (std::fabs(n) < 1e-10) + return nullptr; + if (fabs(k0 - 1.0) <= 1e-10) { + auto conv = createLambertConicConformal_2SP( + util::PropertyMap(), latitudeOfOrigin, + common::Angle(parameterValueMeasure( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), + latitudeOfOrigin, latitudeOfOrigin, + common::Length( + parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), + common::Length( + parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING))); + conv->setCRSs(this, false); + return conv.as_nullable(); + } else { + const double K = k0 * m0 / std::pow(t0, n); + const double phi1 = + std::asin(find_zero_lcc_1sp_to_2sp_f(n, true, K, ec)); + const double phi2 = + std::asin(find_zero_lcc_1sp_to_2sp_f(n, false, K, ec)); + double phi1Deg = RadToDeg(phi1); + double phi2Deg = RadToDeg(phi2); + + // Try to round to hundreth of degree if very close to it + if (std::fabs(phi1Deg * 1000 - std::floor(phi1Deg * 1000 + 0.5)) < + 1e-8) + phi1Deg = floor(phi1Deg * 1000 + 0.5) / 1000; + if (std::fabs(phi2Deg * 1000 - std::floor(phi2Deg * 1000 + 0.5)) < + 1e-8) + phi2Deg = std::floor(phi2Deg * 1000 + 0.5) / 1000; + + // The following improvement is too turn the LCC1SP equivalent of + // EPSG:2154 to the real LCC2SP + // If the computed latitude of origin is close to .0 or .5 degrees + // then check if rounding it to it will get a false northing + // close to an integer + const double FN = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING); + const double latitudeOfOriginDeg = + latitudeOfOrigin.convertToUnit(common::UnitOfMeasure::DEGREE); + if (std::fabs(latitudeOfOriginDeg * 2 - + std::floor(latitudeOfOriginDeg * 2 + 0.5)) < 0.2) { + const double dfRoundedLatOfOrig = + std::floor(latitudeOfOriginDeg * 2 + 0.5) / 2; + const double m1 = msfn(phi1, e2); + const double t1 = tsfn(phi1, ec); + const double F = m1 / (n * std::pow(t1, n)); + const double a = + geogCRS->ellipsoid()->semiMajorAxis().getSIValue(); + const double tRoundedLatOfOrig = + tsfn(DegToRad(dfRoundedLatOfOrig), ec); + const double FN_correction = + a * F * (std::pow(tRoundedLatOfOrig, n) - std::pow(t0, n)); + const double FN_corrected = FN - FN_correction; + const double FN_corrected_rounded = + std::floor(FN_corrected + 0.5); + if (std::fabs(FN_corrected - FN_corrected_rounded) < 1e-8) { + auto conv = createLambertConicConformal_2SP( + util::PropertyMap(), + common::Angle(dfRoundedLatOfOrig, + common::UnitOfMeasure::DEGREE), + common::Angle(parameterValueMeasure( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), + common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE), + common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE), + common::Length(parameterValueMeasure( + EPSG_CODE_PARAMETER_FALSE_EASTING)), + common::Length(FN_corrected_rounded)); + conv->setCRSs(this, false); + return conv.as_nullable(); + } + } + + auto conv = createLambertConicConformal_2SP( + util::PropertyMap(), latitudeOfOrigin, + common::Angle(parameterValueMeasure( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), + common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE), + common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE), + common::Length( + parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), + common::Length(FN)); + conv->setCRSs(this, false); + return conv.as_nullable(); + } + } + + if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP && + targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP) { + // Notations m0, t0, m1, t1, m2, t2 n, F are those of the EPSG guidance + // "1.3.1.1 Lambert Conic Conformal (2SP)" and + // "1.3.1.2 Lambert Conic Conformal (1SP)" and + // or Snyder pages 106-109 + const double phiF = + parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN) + .getSIValue(); + const double phi1 = + parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) + .getSIValue(); + const double phi2 = + parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) + .getSIValue(); + if (!(std::fabs(phiF) < M_PI / 2)) + return nullptr; + if (!(std::fabs(phi1) < M_PI / 2)) + return nullptr; + if (!(std::fabs(phi2) < M_PI / 2)) + return nullptr; + const double ec = std::sqrt(e2); + const double m1 = msfn(phi1, e2); + const double m2 = msfn(phi2, e2); + const double t1 = tsfn(phi1, ec); + const double t2 = tsfn(phi2, ec); + const double n_denom = std::log(t1) - std::log(t2); + const double n = (std::fabs(n_denom) < 1e-10) + ? std::sin(phi1) + : (std::log(m1) - std::log(m2)) / n_denom; + if (std::fabs(n) < 1e-10) + return nullptr; + const double F = m1 / (n * std::pow(t1, n)); + const double phi0 = std::asin(n); + const double m0 = msfn(phi0, e2); + const double t0 = tsfn(phi0, ec); + const double F0 = m0 / (n * std::pow(t0, n)); + const double k0 = F / F0; + const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue(); + const double tF = tsfn(phiF, ec); + const double FN_correction = + a * F * (std::pow(tF, n) - std::pow(t0, n)); + + double phi0Deg = RadToDeg(phi0); + // Try to round to thousandth of degree if very close to it + if (std::fabs(phi0Deg * 1000 - std::floor(phi0Deg * 1000 + 0.5)) < 1e-8) + phi0Deg = std::floor(phi0Deg * 1000 + 0.5) / 1000; + + auto conv = createLambertConicConformal_1SP( + util::PropertyMap(), + common::Angle(phi0Deg, common::UnitOfMeasure::DEGREE), + common::Angle(parameterValueMeasure( + EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN)), + common::Scale(k0), common::Length(parameterValueMeasure( + EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN)), + common::Length( + parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN) + + (std::fabs(FN_correction) > 1e-8 ? FN_correction : 0))); + conv->setCRSs(this, false); + return conv.as_nullable(); + } + + return nullptr; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static const ESRIMethodMapping *getESRIMapping(const std::string &wkt2_name, + int epsg_code) { + size_t nEsriMappings = 0; + const auto esriMappings = getEsriMappings(nEsriMappings); + for (size_t i = 0; i < nEsriMappings; ++i) { + const auto &mapping = esriMappings[i]; + if ((epsg_code != 0 && mapping.epsg_code == epsg_code) || + ci_equal(wkt2_name, mapping.wkt2_name)) { + return &mapping; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +static void getESRIMethodNameAndParams(const Conversion *conv, + const std::string &methodName, + int methodEPSGCode, + const char *&esriMethodName, + const ESRIParamMapping *&esriParams) { + esriParams = nullptr; + esriMethodName = nullptr; + const auto *esriMapping = getESRIMapping(methodName, methodEPSGCode); + const auto l_targetCRS = conv->targetCRS(); + if (esriMapping) { + esriParams = esriMapping->params; + esriMethodName = esriMapping->esri_name; + if (esriMapping->epsg_code == + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL || + esriMapping->epsg_code == + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) { + if (l_targetCRS && + ci_find(l_targetCRS->nameStr(), "Plate Carree") != + std::string::npos && + conv->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) { + esriParams = paramsESRI_Plate_Carree; + esriMethodName = "Plate_Carree"; + } else { + esriParams = paramsESRI_Equidistant_Cylindrical; + esriMethodName = "Equidistant_Cylindrical"; + } + } else if (esriMapping->epsg_code == + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { + if (ci_find(conv->nameStr(), "Gauss Kruger") != std::string::npos || + (l_targetCRS && (ci_find(l_targetCRS->nameStr(), "Gauss") != + std::string::npos || + ci_find(l_targetCRS->nameStr(), "GK_") != + std::string::npos))) { + esriParams = paramsESRI_Gauss_Kruger; + esriMethodName = "Gauss_Kruger"; + } else { + esriParams = paramsESRI_Transverse_Mercator; + esriMethodName = "Transverse_Mercator"; + } + } else if (esriMapping->epsg_code == + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) { + if (std::abs( + conv->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) - + conv->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) < + 1e-15) { + esriParams = + paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin; + esriMethodName = + "Hotine_Oblique_Mercator_Azimuth_Natural_Origin"; + } else { + esriParams = + paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin; + esriMethodName = "Rectified_Skew_Orthomorphic_Natural_Origin"; + } + } else if (esriMapping->epsg_code == + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) { + if (std::abs( + conv->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) - + conv->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) < + 1e-15) { + esriParams = paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center; + esriMethodName = "Hotine_Oblique_Mercator_Azimuth_Center"; + } else { + esriParams = paramsESRI_Rectified_Skew_Orthomorphic_Center; + esriMethodName = "Rectified_Skew_Orthomorphic_Center"; + } + } else if (esriMapping->epsg_code == + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) { + if (conv->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL) > 0) { + esriMethodName = "Stereographic_North_Pole"; + } else { + esriMethodName = "Stereographic_South_Pole"; + } + } + } +} + +// --------------------------------------------------------------------------- + +const char *Conversion::getESRIMethodName() const { + const auto &l_method = method(); + const auto &methodName = l_method->nameStr(); + const auto methodEPSGCode = l_method->getEPSGCode(); + const ESRIParamMapping *esriParams = nullptr; + const char *esriMethodName = nullptr; + getESRIMethodNameAndParams(this, methodName, methodEPSGCode, esriMethodName, + esriParams); + return esriMethodName; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const char *Conversion::getWKT1GDALMethodName() const { + const auto &l_method = method(); + const auto methodEPSGCode = l_method->getEPSGCode(); + if (methodEPSGCode == + EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) { + return "Mercator_1SP"; + } + const MethodMapping *mapping = getMapping(l_method.get()); + return mapping ? mapping->wkt1_name : nullptr; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +void Conversion::_exportToWKT(io::WKTFormatter *formatter) const { + const auto &l_method = method(); + const auto &methodName = l_method->nameStr(); + const auto methodEPSGCode = l_method->getEPSGCode(); + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + + if (!isWKT2 && formatter->useESRIDialect()) { + if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) { + auto eqConv = + convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B); + if (eqConv) { + eqConv->_exportToWKT(formatter); + return; + } + } + } + + if (isWKT2) { + formatter->startNode(formatter->useDerivingConversion() + ? io::WKTConstants::DERIVINGCONVERSION + : io::WKTConstants::CONVERSION, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + } else { + formatter->enter(); + formatter->pushOutputUnit(false); + formatter->pushOutputId(false); + } + +#ifdef DEBUG_CONVERSION_ID + if (sourceCRS() && targetCRS()) { + formatter->startNode("SOURCECRS_ID", false); + sourceCRS()->formatID(formatter); + formatter->endNode(); + formatter->startNode("TARGETCRS_ID", false); + targetCRS()->formatID(formatter); + formatter->endNode(); + } +#endif + + bool bAlreadyWritten = false; + if (!isWKT2 && formatter->useESRIDialect()) { + const ESRIParamMapping *esriParams = nullptr; + const char *esriMethodName = nullptr; + getESRIMethodNameAndParams(this, methodName, methodEPSGCode, + esriMethodName, esriParams); + if (esriMethodName && esriParams) { + formatter->startNode(io::WKTConstants::PROJECTION, false); + formatter->addQuotedString(esriMethodName); + formatter->endNode(); + + for (int i = 0; esriParams[i].esri_name != nullptr; i++) { + const auto &esriParam = esriParams[i]; + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString(esriParam.esri_name); + if (esriParam.wkt2_name) { + const auto &pv = parameterValue(esriParam.wkt2_name, + esriParam.epsg_code); + if (pv && pv->type() == ParameterValue::Type::MEASURE) { + const auto &v = pv->value(); + // as we don't output the natural unit, output + // to the registered linear / angular unit. + const auto &unitType = v.unit().type(); + if (unitType == common::UnitOfMeasure::Type::LINEAR) { + formatter->add(v.convertToUnit( + *(formatter->axisLinearUnit()))); + } else if (unitType == + common::UnitOfMeasure::Type::ANGULAR) { + const auto &angUnit = + *(formatter->axisAngularUnit()); + double val = v.convertToUnit(angUnit); + if (angUnit == common::UnitOfMeasure::DEGREE) { + if (val > 180.0) { + val -= 360.0; + } else if (val < -180.0) { + val += 360.0; + } + } + formatter->add(val); + } else { + formatter->add(v.getSIValue()); + } + } else if (ci_find(esriParam.esri_name, "scale") != + std::string::npos) { + formatter->add(1.0); + } else { + formatter->add(0.0); + } + } else { + formatter->add(esriParam.fixed_value); + } + formatter->endNode(); + } + bAlreadyWritten = true; + } + } else if (!isWKT2) { + if (methodEPSGCode == + EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) { + const double latitudeOrigin = parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + common::UnitOfMeasure::DEGREE); + if (latitudeOrigin != 0) { + throw io::FormattingException( + std::string("Unsupported value for ") + + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); + } + + bAlreadyWritten = true; + formatter->startNode(io::WKTConstants::PROJECTION, false); + formatter->addQuotedString("Mercator_1SP"); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("central_meridian"); + const double centralMeridian = parameterValueNumeric( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + common::UnitOfMeasure::DEGREE); + formatter->add(centralMeridian); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("scale_factor"); + formatter->add(1.0); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("false_easting"); + const double falseEasting = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING); + formatter->add(falseEasting); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("false_northing"); + const double falseNorthing = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING); + formatter->add(falseNorthing); + formatter->endNode(); + } else if (starts_with(methodName, "PROJ ")) { + bAlreadyWritten = true; + formatter->startNode(io::WKTConstants::PROJECTION, false); + formatter->addQuotedString("custom_proj4"); + formatter->endNode(); + } + } + + if (!bAlreadyWritten) { + l_method->_exportToWKT(formatter); + + const MethodMapping *mapping = + !isWKT2 ? getMapping(l_method.get()) : nullptr; + for (const auto &genOpParamvalue : parameterValues()) { + + // EPSG has normally no Latitude of natural origin for Equidistant + // Cylindrical but PROJ can handle it, so output the parameter if + // not zero + if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL || + methodEPSGCode == + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) { + auto opParamvalue = + dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue && + opParamvalue->parameter()->getEPSGCode() == + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) { + const auto ¶mValue = opParamvalue->parameterValue(); + if (paramValue->type() == ParameterValue::Type::MEASURE) { + const auto &measure = paramValue->value(); + if (measure.getSIValue() == 0) { + continue; + } + } + } + } + // Same for false easting / false northing for Vertical Perspective + else if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE) { + auto opParamvalue = + dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto paramEPSGCode = + opParamvalue->parameter()->getEPSGCode(); + if (paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_EASTING || + paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_NORTHING) { + const auto ¶mValue = opParamvalue->parameterValue(); + if (paramValue->type() == + ParameterValue::Type::MEASURE) { + const auto &measure = paramValue->value(); + if (measure.getSIValue() == 0) { + continue; + } + } + } + } + } + genOpParamvalue->_exportToWKT(formatter, mapping); + } + } + + if (isWKT2) { + if (formatter->outputId()) { + formatID(formatter); + } + formatter->endNode(); + } else { + formatter->popOutputUnit(); + formatter->popOutputId(); + formatter->leave(); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Conversion::_exportToJSON( + io::JSONFormatter *formatter) const // throw(FormattingException) +{ + auto writer = formatter->writer(); + auto objectContext( + formatter->MakeObjectContext("Conversion", !identifiers().empty())); + + writer->AddObjKey("name"); + auto l_name = nameStr(); + if (l_name.empty()) { + writer->Add("unnamed"); + } else { + writer->Add(l_name); + } + + writer->AddObjKey("method"); + formatter->setOmitTypeInImmediateChild(); + formatter->setAllowIDInImmediateChild(); + method()->_exportToJSON(formatter); + + const auto &l_parameterValues = parameterValues(); + if (!l_parameterValues.empty()) { + writer->AddObjKey("parameters"); + { + auto parametersContext(writer->MakeArrayContext(false)); + for (const auto &genOpParamvalue : l_parameterValues) { + formatter->setAllowIDInImmediateChild(); + formatter->setOmitTypeInImmediateChild(); + genOpParamvalue->_exportToJSON(formatter); + } + } + } + + if (formatter->outputId()) { + formatID(formatter); + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static bool createPROJ4WebMercator(const Conversion *conv, + io::PROJStringFormatter *formatter) { + const double centralMeridian = conv->parameterValueNumeric( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + common::UnitOfMeasure::DEGREE); + + const double falseEasting = + conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING); + + const double falseNorthing = + conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING); + + auto sourceCRS = conv->sourceCRS(); + auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); + if (!geogCRS) { + return false; + } + + std::string units("m"); + auto targetCRS = conv->targetCRS(); + auto targetProjCRS = + dynamic_cast<const crs::ProjectedCRS *>(targetCRS.get()); + if (targetProjCRS) { + const auto &axisList = targetProjCRS->coordinateSystem()->axisList(); + const auto &unit = axisList[0]->unit(); + if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE, + util::IComparable::Criterion::EQUIVALENT)) { + auto projUnit = unit.exportToPROJString(); + if (!projUnit.empty()) { + units = projUnit; + } else { + return false; + } + } + } + + formatter->addStep("merc"); + const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue(); + formatter->addParam("a", a); + formatter->addParam("b", a); + formatter->addParam("lat_ts", 0.0); + formatter->addParam("lon_0", centralMeridian); + formatter->addParam("x_0", falseEasting); + formatter->addParam("y_0", falseNorthing); + formatter->addParam("k", 1.0); + formatter->addParam("units", units); + formatter->addParam("nadgrids", "@null"); + formatter->addParam("wktext"); + formatter->addParam("no_defs"); + return true; +} + +// --------------------------------------------------------------------------- + +static bool +createPROJExtensionFromCustomProj(const Conversion *conv, + io::PROJStringFormatter *formatter, + bool forExtensionNode) { + const auto &methodName = conv->method()->nameStr(); + assert(starts_with(methodName, "PROJ ")); + auto tokens = split(methodName, ' '); + + formatter->addStep(tokens[1]); + + if (forExtensionNode) { + auto sourceCRS = conv->sourceCRS(); + auto geogCRS = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); + if (!geogCRS) { + return false; + } + geogCRS->addDatumInfoToPROJString(formatter); + } + + for (size_t i = 2; i < tokens.size(); i++) { + auto kv = split(tokens[i], '='); + if (kv.size() == 2) { + formatter->addParam(kv[0], kv[1]); + } else { + formatter->addParam(tokens[i]); + } + } + + for (const auto &genOpParamvalue : conv->parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶mValue = opParamvalue->parameterValue(); + if (paramValue->type() == ParameterValue::Type::MEASURE) { + const auto &measure = paramValue->value(); + const auto unitType = measure.unit().type(); + if (unitType == common::UnitOfMeasure::Type::LINEAR) { + formatter->addParam(paramName, measure.getSIValue()); + } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) { + formatter->addParam( + paramName, + measure.convertToUnit(common::UnitOfMeasure::DEGREE)); + } else { + formatter->addParam(paramName, measure.value()); + } + } + } + } + + if (forExtensionNode) { + formatter->addParam("wktext"); + formatter->addParam("no_defs"); + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool Conversion::addWKTExtensionNode(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + const auto &l_method = method(); + const auto &methodName = l_method->nameStr(); + const int methodEPSGCode = l_method->getEPSGCode(); + if (l_method->getPrivate()->projMethodOverride_ == "tmerc approx" || + l_method->getPrivate()->projMethodOverride_ == "utm approx") { + auto projFormatter = io::PROJStringFormatter::create(); + projFormatter->setCRSExport(true); + projFormatter->setUseApproxTMerc(true); + formatter->startNode(io::WKTConstants::EXTENSION, false); + formatter->addQuotedString("PROJ4"); + _exportToPROJString(projFormatter.get()); + projFormatter->addParam("no_defs"); + formatter->addQuotedString(projFormatter->toString()); + formatter->endNode(); + return true; + } else if (methodEPSGCode == + EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR || + nameStr() == "Popular Visualisation Mercator") { + + auto projFormatter = io::PROJStringFormatter::create(); + projFormatter->setCRSExport(true); + if (createPROJ4WebMercator(this, projFormatter.get())) { + formatter->startNode(io::WKTConstants::EXTENSION, false); + formatter->addQuotedString("PROJ4"); + formatter->addQuotedString(projFormatter->toString()); + formatter->endNode(); + return true; + } + } else if (starts_with(methodName, "PROJ ")) { + auto projFormatter = io::PROJStringFormatter::create(); + projFormatter->setCRSExport(true); + if (createPROJExtensionFromCustomProj(this, projFormatter.get(), + true)) { + formatter->startNode(io::WKTConstants::EXTENSION, false); + formatter->addQuotedString("PROJ4"); + formatter->addQuotedString(projFormatter->toString()); + formatter->endNode(); + return true; + } + } else if (methodName == + PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) { + auto projFormatter = io::PROJStringFormatter::create(); + projFormatter->setCRSExport(true); + formatter->startNode(io::WKTConstants::EXTENSION, false); + formatter->addQuotedString("PROJ4"); + _exportToPROJString(projFormatter.get()); + projFormatter->addParam("no_defs"); + formatter->addQuotedString(projFormatter->toString()); + formatter->endNode(); + return true; + } + } + return false; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Conversion::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(FormattingException) +{ + const auto &l_method = method(); + const auto &methodName = l_method->nameStr(); + const int methodEPSGCode = l_method->getEPSGCode(); + const bool isZUnitConversion = + methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT; + const bool isAffineParametric = + methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION; + const bool isGeographicGeocentric = + methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC; + const bool isHeightDepthReversal = + methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL; + const bool applySourceCRSModifiers = + !isZUnitConversion && !isAffineParametric && + !isAxisOrderReversal(methodEPSGCode) && !isGeographicGeocentric && + !isHeightDepthReversal; + bool applyTargetCRSModifiers = applySourceCRSModifiers; + + if (formatter->getCRSExport()) { + if (methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TOPOCENTRIC || + methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) { + throw io::FormattingException("Transformation cannot be exported " + "as a PROJ.4 string (but can be part " + "of a PROJ pipeline)"); + } + } + + auto l_sourceCRS = sourceCRS(); + crs::GeographicCRSPtr srcGeogCRS; + if (!formatter->getCRSExport() && l_sourceCRS && applySourceCRSModifiers) { + + crs::CRSPtr horiz = l_sourceCRS; + const auto compound = + dynamic_cast<const crs::CompoundCRS *>(l_sourceCRS.get()); + if (compound) { + const auto &components = compound->componentReferenceSystems(); + if (!components.empty()) { + horiz = components.front().as_nullable(); + } + } + + srcGeogCRS = std::dynamic_pointer_cast<crs::GeographicCRS>(horiz); + if (srcGeogCRS) { + formatter->setOmitProjLongLatIfPossible(true); + formatter->startInversion(); + srcGeogCRS->_exportToPROJString(formatter); + formatter->stopInversion(); + formatter->setOmitProjLongLatIfPossible(false); + } + + auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(horiz.get()); + if (projCRS) { + formatter->startInversion(); + formatter->pushOmitZUnitConversion(); + projCRS->addUnitConvertAndAxisSwap(formatter, false); + formatter->popOmitZUnitConversion(); + formatter->stopInversion(); + } + } + + const auto &convName = nameStr(); + bool bConversionDone = false; + bool bEllipsoidParametersDone = false; + bool useApprox = false; + if (methodEPSGCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { + // Check for UTM + int zone = 0; + bool north = true; + useApprox = + formatter->getUseApproxTMerc() || + l_method->getPrivate()->projMethodOverride_ == "tmerc approx" || + l_method->getPrivate()->projMethodOverride_ == "utm approx"; + if (isUTM(zone, north)) { + bConversionDone = true; + formatter->addStep("utm"); + if (useApprox) { + formatter->addParam("approx"); + } + formatter->addParam("zone", zone); + if (!north) { + formatter->addParam("south"); + } + } + } else if (methodEPSGCode == + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) { + const double azimuth = + parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, + common::UnitOfMeasure::DEGREE); + const double angleRectifiedToSkewGrid = parameterValueNumeric( + EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, + common::UnitOfMeasure::DEGREE); + // Map to Swiss Oblique Mercator / somerc + if (std::fabs(azimuth - 90) < 1e-4 && + std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) { + bConversionDone = true; + formatter->addStep("somerc"); + formatter->addParam( + "lat_0", parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, + common::UnitOfMeasure::DEGREE)); + formatter->addParam( + "lon_0", parameterValueNumeric( + EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + common::UnitOfMeasure::DEGREE)); + formatter->addParam( + "k_0", parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE)); + formatter->addParam("x_0", parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_FALSE_EASTING)); + formatter->addParam("y_0", parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_FALSE_NORTHING)); + } + } else if (methodEPSGCode == + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) { + const double azimuth = + parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, + common::UnitOfMeasure::DEGREE); + const double angleRectifiedToSkewGrid = parameterValueNumeric( + EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, + common::UnitOfMeasure::DEGREE); + // Map to Swiss Oblique Mercator / somerc + if (std::fabs(azimuth - 90) < 1e-4 && + std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) { + bConversionDone = true; + formatter->addStep("somerc"); + formatter->addParam( + "lat_0", parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, + common::UnitOfMeasure::DEGREE)); + formatter->addParam( + "lon_0", parameterValueNumeric( + EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + common::UnitOfMeasure::DEGREE)); + formatter->addParam( + "k_0", parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE)); + formatter->addParam( + "x_0", parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE)); + formatter->addParam( + "y_0", parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE)); + } + } else if (methodEPSGCode == EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED) { + double colatitude = + parameterValueNumeric(EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, + common::UnitOfMeasure::DEGREE); + double latitudePseudoStandardParallel = parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, + common::UnitOfMeasure::DEGREE); + // 30deg 17' 17.30311'' = 30.28813975277777776 + // 30deg 17' 17.303'' = 30.288139722222223 as used in GDAL WKT1 + if (std::fabs(colatitude - 30.2881397) > 1e-7) { + throw io::FormattingException( + std::string("Unsupported value for ") + + EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS); + } + if (std::fabs(latitudePseudoStandardParallel - 78.5) > 1e-8) { + throw io::FormattingException( + std::string("Unsupported value for ") + + EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL); + } + } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) { + double latitudeOrigin = parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + common::UnitOfMeasure::DEGREE); + if (latitudeOrigin != 0) { + throw io::FormattingException( + std::string("Unsupported value for ") + + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); + } + } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B) { + const auto &scaleFactor = parameterValueMeasure(WKT1_SCALE_FACTOR, 0); + if (scaleFactor.unit().type() != common::UnitOfMeasure::Type::UNKNOWN && + std::fabs(scaleFactor.getSIValue() - 1.0) > 1e-10) { + throw io::FormattingException( + "Unexpected presence of scale factor in Mercator (variant B)"); + } + double latitudeOrigin = parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + common::UnitOfMeasure::DEGREE); + if (latitudeOrigin != 0) { + throw io::FormattingException( + std::string("Unsupported value for ") + + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); + } + } else if (methodEPSGCode == + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) { + // We map TMSO to tmerc with axis=wsu. This only works if false easting + // and northings are zero, which is the case in practice for South + // African and Namibian EPSG CRS + const auto falseEasting = parameterValueNumeric( + EPSG_CODE_PARAMETER_FALSE_EASTING, common::UnitOfMeasure::METRE); + if (falseEasting != 0) { + throw io::FormattingException( + std::string("Unsupported value for ") + + EPSG_NAME_PARAMETER_FALSE_EASTING); + } + const auto falseNorthing = parameterValueNumeric( + EPSG_CODE_PARAMETER_FALSE_NORTHING, common::UnitOfMeasure::METRE); + if (falseNorthing != 0) { + throw io::FormattingException( + std::string("Unsupported value for ") + + EPSG_NAME_PARAMETER_FALSE_NORTHING); + } + // PROJ.4 specific hack for webmercator + } else if (formatter->getCRSExport() && + methodEPSGCode == + EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) { + if (!createPROJ4WebMercator(this, formatter)) { + throw io::FormattingException( + std::string("Cannot export ") + + EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR + + " as PROJ.4 string outside of a ProjectedCRS context"); + } + bConversionDone = true; + bEllipsoidParametersDone = true; + applyTargetCRSModifiers = false; + } else if (ci_equal(convName, "Popular Visualisation Mercator")) { + if (formatter->getCRSExport()) { + if (!createPROJ4WebMercator(this, formatter)) { + throw io::FormattingException(concat( + "Cannot export ", convName, + " as PROJ.4 string outside of a ProjectedCRS context")); + } + applyTargetCRSModifiers = false; + } else { + formatter->addStep("webmerc"); + if (l_sourceCRS) { + datum::Ellipsoid::WGS84->_exportToPROJString(formatter); + } + } + bConversionDone = true; + bEllipsoidParametersDone = true; + } else if (starts_with(methodName, "PROJ ")) { + bConversionDone = true; + createPROJExtensionFromCustomProj(this, formatter, false); + } else if (ci_equal(methodName, + PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION)) { + double southPoleLat = parameterValueNumeric( + PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION, + common::UnitOfMeasure::DEGREE); + double southPoleLon = parameterValueNumeric( + PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION, + common::UnitOfMeasure::DEGREE); + double rotation = parameterValueNumeric( + PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION, + common::UnitOfMeasure::DEGREE); + formatter->addStep("ob_tran"); + formatter->addParam("o_proj", "longlat"); + formatter->addParam("o_lon_p", -rotation); + formatter->addParam("o_lat_p", -southPoleLat); + formatter->addParam("lon_0", southPoleLon); + bConversionDone = true; + } else if (ci_equal(methodName, "Adams_Square_II")) { + // Look for ESRI method and parameter names (to be opposed + // to the OGC WKT2 names we use elsewhere, because there's no mapping + // of those parameters to OGC WKT2) + // We also reject non-default values for a number of parameters, + // because they are not implemented on PROJ side. The subset we + // support can handle ESRI:54098 WGS_1984_Adams_Square_II, but not + // ESRI:54099 WGS_1984_Spilhaus_Ocean_Map_in_Square + const double falseEasting = parameterValueNumeric( + "False_Easting", common::UnitOfMeasure::METRE); + const double falseNorthing = parameterValueNumeric( + "False_Northing", common::UnitOfMeasure::METRE); + const double scaleFactor = + parameterValue("Scale_Factor", 0) + ? parameterValueNumeric("Scale_Factor", + common::UnitOfMeasure::SCALE_UNITY) + : 1.0; + const double azimuth = + parameterValueNumeric("Azimuth", common::UnitOfMeasure::DEGREE); + const double longitudeOfCenter = parameterValueNumeric( + "Longitude_Of_Center", common::UnitOfMeasure::DEGREE); + const double latitudeOfCenter = parameterValueNumeric( + "Latitude_Of_Center", common::UnitOfMeasure::DEGREE); + const double XYPlaneRotation = parameterValueNumeric( + "XY_Plane_Rotation", common::UnitOfMeasure::DEGREE); + if (scaleFactor != 1.0 || azimuth != 0.0 || latitudeOfCenter != 0.0 || + XYPlaneRotation != 0.0) { + throw io::FormattingException("Unsupported value for one or " + "several parameters of " + "Adams_Square_II"); + } + formatter->addStep("adams_ws2"); + formatter->addParam("lon_0", longitudeOfCenter); + formatter->addParam("x_0", falseEasting); + formatter->addParam("y_0", falseNorthing); + bConversionDone = true; + } else if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5 && + isZUnitConversion) { + double convFactor = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); + auto uom = common::UnitOfMeasure(std::string(), convFactor, + common::UnitOfMeasure::Type::LINEAR) + .exportToPROJString(); + auto reverse_uom = + convFactor == 0.0 + ? std::string() + : common::UnitOfMeasure(std::string(), 1.0 / convFactor, + common::UnitOfMeasure::Type::LINEAR) + .exportToPROJString(); + if (uom == "m") { + // do nothing + } else if (!uom.empty()) { + formatter->addStep("unitconvert"); + formatter->addParam("z_in", uom); + formatter->addParam("z_out", "m"); + } else if (!reverse_uom.empty()) { + formatter->addStep("unitconvert"); + formatter->addParam("z_in", "m"); + formatter->addParam("z_out", reverse_uom); + } else { + formatter->addStep("affine"); + formatter->addParam("s33", convFactor); + } + bConversionDone = true; + bEllipsoidParametersDone = true; + } else if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) { + if (!srcGeogCRS) { + throw io::FormattingException( + "Export of Geographic/Topocentric conversion to a PROJ string " + "requires an input geographic CRS"); + } + + formatter->addStep("cart"); + srcGeogCRS->ellipsoid()->_exportToPROJString(formatter); + + formatter->addStep("topocentric"); + const auto latOrigin = parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, + common::UnitOfMeasure::DEGREE); + const auto lonOrigin = parameterValueNumeric( + EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, + common::UnitOfMeasure::DEGREE); + const auto heightOrigin = parameterValueNumeric( + EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, + common::UnitOfMeasure::METRE); + formatter->addParam("lat_0", latOrigin); + formatter->addParam("lon_0", lonOrigin); + formatter->addParam("h_0", heightOrigin); + bConversionDone = true; + } + + auto l_targetCRS = targetCRS(); + + bool bAxisSpecFound = false; + if (!bConversionDone) { + const MethodMapping *mapping = getMapping(l_method.get()); + if (mapping && mapping->proj_name_main) { + formatter->addStep(mapping->proj_name_main); + if (useApprox) { + formatter->addParam("approx"); + } + if (mapping->proj_name_aux) { + bool addAux = true; + if (internal::starts_with(mapping->proj_name_aux, "axis=")) { + if (mapping->epsg_code == EPSG_CODE_METHOD_KROVAK) { + auto projCRS = dynamic_cast<const crs::ProjectedCRS *>( + l_targetCRS.get()); + if (projCRS) { + const auto &axisList = + projCRS->coordinateSystem()->axisList(); + if (axisList[0]->direction() == + cs::AxisDirection::WEST && + axisList[1]->direction() == + cs::AxisDirection::SOUTH) { + formatter->addParam("czech"); + addAux = false; + } + } + } + bAxisSpecFound = true; + } + + // No need to add explicit f=0 if the ellipsoid is a sphere + if (strcmp(mapping->proj_name_aux, "f=0") == 0) { + crs::CRS *horiz = l_sourceCRS.get(); + const auto compound = + dynamic_cast<const crs::CompoundCRS *>(horiz); + if (compound) { + const auto &components = + compound->componentReferenceSystems(); + if (!components.empty()) { + horiz = components.front().get(); + } + } + + auto geogCRS = + dynamic_cast<const crs::GeographicCRS *>(horiz); + if (geogCRS && geogCRS->ellipsoid()->isSphere()) { + addAux = false; + } + } + + if (addAux) { + auto kv = split(mapping->proj_name_aux, '='); + if (kv.size() == 2) { + formatter->addParam(kv[0], kv[1]); + } else { + formatter->addParam(mapping->proj_name_aux); + } + } + } + + if (mapping->epsg_code == + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) { + double latitudeStdParallel = parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, + common::UnitOfMeasure::DEGREE); + formatter->addParam("lat_0", + (latitudeStdParallel >= 0) ? 90.0 : -90.0); + } + + for (int i = 0; mapping->params[i] != nullptr; i++) { + const auto *param = mapping->params[i]; + if (!param->proj_name) { + continue; + } + const auto &value = + parameterValueMeasure(param->wkt2_name, param->epsg_code); + double valueConverted = 0; + if (value == nullMeasure) { + // Deal with missing values. In an ideal world, this would + // not happen + if (param->epsg_code == + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) { + valueConverted = 1.0; + } + } else if (param->unit_type == + common::UnitOfMeasure::Type::ANGULAR) { + valueConverted = + value.convertToUnit(common::UnitOfMeasure::DEGREE); + } else { + valueConverted = value.getSIValue(); + } + + if (mapping->epsg_code == + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP && + strcmp(param->proj_name, "lat_1") == 0) { + formatter->addParam(param->proj_name, valueConverted); + formatter->addParam("lat_0", valueConverted); + } else { + formatter->addParam(param->proj_name, valueConverted); + } + } + + } else { + if (!exportToPROJStringGeneric(formatter)) { + throw io::FormattingException( + concat("Unsupported conversion method: ", methodName)); + } + } + } + + if (l_targetCRS && applyTargetCRSModifiers) { + crs::CRS *horiz = l_targetCRS.get(); + const auto compound = dynamic_cast<const crs::CompoundCRS *>(horiz); + if (compound) { + const auto &components = compound->componentReferenceSystems(); + if (!components.empty()) { + horiz = components.front().get(); + } + } + + if (!bEllipsoidParametersDone) { + auto targetGeodCRS = horiz->extractGeodeticCRS(); + auto targetGeogCRS = + std::dynamic_pointer_cast<crs::GeographicCRS>(targetGeodCRS); + if (targetGeogCRS) { + if (formatter->getCRSExport()) { + targetGeogCRS->addDatumInfoToPROJString(formatter); + } else { + targetGeogCRS->ellipsoid()->_exportToPROJString(formatter); + targetGeogCRS->primeMeridian()->_exportToPROJString( + formatter); + } + } else if (targetGeodCRS) { + targetGeodCRS->ellipsoid()->_exportToPROJString(formatter); + } + } + + auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(horiz); + if (projCRS) { + formatter->pushOmitZUnitConversion(); + projCRS->addUnitConvertAndAxisSwap(formatter, bAxisSpecFound); + formatter->popOmitZUnitConversion(); + } + + auto derivedGeographicCRS = + dynamic_cast<const crs::DerivedGeographicCRS *>(horiz); + if (!formatter->getCRSExport() && derivedGeographicCRS) { + formatter->setOmitProjLongLatIfPossible(true); + derivedGeographicCRS->addAngularUnitConvertAndAxisSwap(formatter); + formatter->setOmitProjLongLatIfPossible(false); + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return whether a conversion is a [Universal Transverse Mercator] + * (https://proj.org/operations/projections/utm.html) conversion. + * + * @param[out] zone UTM zone number between 1 and 60. + * @param[out] north true for UTM northern hemisphere, false for UTM southern + * hemisphere. + * @return true if it is a UTM conversion. + */ +bool Conversion::isUTM(int &zone, bool &north) const { + zone = 0; + north = true; + + if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { + // Check for UTM + + bool bLatitudeNatOriginUTM = false; + bool bScaleFactorUTM = false; + bool bFalseEastingUTM = false; + bool bFalseNorthingUTM = false; + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto epsg_code = opParamvalue->parameter()->getEPSGCode(); + const auto &l_parameterValue = opParamvalue->parameterValue(); + if (l_parameterValue->type() == ParameterValue::Type::MEASURE) { + const auto &measure = l_parameterValue->value(); + if (epsg_code == + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN && + std::fabs(measure.value() - + UTM_LATITUDE_OF_NATURAL_ORIGIN) < 1e-10) { + bLatitudeNatOriginUTM = true; + } else if ( + (epsg_code == + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN || + epsg_code == + EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN) && + measure.unit()._isEquivalentTo( + common::UnitOfMeasure::DEGREE, + util::IComparable::Criterion::EQUIVALENT)) { + double dfZone = (measure.value() + 183.0) / 6.0; + if (dfZone > 0.9 && dfZone < 60.1 && + std::abs(dfZone - std::round(dfZone)) < 1e-10) { + zone = static_cast<int>(std::lround(dfZone)); + } + } else if ( + epsg_code == + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN && + measure.unit()._isEquivalentTo( + common::UnitOfMeasure::SCALE_UNITY, + util::IComparable::Criterion::EQUIVALENT) && + std::fabs(measure.value() - UTM_SCALE_FACTOR) < 1e-10) { + bScaleFactorUTM = true; + } else if (epsg_code == EPSG_CODE_PARAMETER_FALSE_EASTING && + measure.value() == UTM_FALSE_EASTING && + measure.unit()._isEquivalentTo( + common::UnitOfMeasure::METRE, + util::IComparable::Criterion::EQUIVALENT)) { + bFalseEastingUTM = true; + } else if (epsg_code == + EPSG_CODE_PARAMETER_FALSE_NORTHING && + measure.unit()._isEquivalentTo( + common::UnitOfMeasure::METRE, + util::IComparable::Criterion::EQUIVALENT)) { + if (std::fabs(measure.value() - + UTM_NORTH_FALSE_NORTHING) < 1e-10) { + bFalseNorthingUTM = true; + north = true; + } else if (std::fabs(measure.value() - + UTM_SOUTH_FALSE_NORTHING) < + 1e-10) { + bFalseNorthingUTM = true; + north = false; + } + } + } + } + } + if (bLatitudeNatOriginUTM && zone > 0 && bScaleFactorUTM && + bFalseEastingUTM && bFalseNorthingUTM) { + return true; + } + } + return false; +} + +// --------------------------------------------------------------------------- + +/** \brief Return a Conversion object where some parameters are better + * identified. + * + * @return a new Conversion. + */ +ConversionNNPtr Conversion::identify() const { + auto newConversion = Conversion::nn_make_shared<Conversion>(*this); + newConversion->assignSelf(newConversion); + + if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { + // Check for UTM + int zone = 0; + bool north = true; + if (isUTM(zone, north)) { + newConversion->setProperties( + getUTMConversionProperty(util::PropertyMap(), zone, north)); + } + } + + return newConversion; +} + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END diff --git a/src/iso19111/operation/coordinateoperation_internal.hpp b/src/iso19111/operation/coordinateoperation_internal.hpp new file mode 100644 index 00000000..d890f710 --- /dev/null +++ b/src/iso19111/operation/coordinateoperation_internal.hpp @@ -0,0 +1,274 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#error This file should only be included from a PROJ cpp file +#endif + +#ifndef COORDINATEOPERATION_INTERNAL_HH_INCLUDED +#define COORDINATEOPERATION_INTERNAL_HH_INCLUDED + +#include "proj/coordinateoperation.hpp" + +#include <vector> + +//! @cond Doxygen_Suppress + +NS_PROJ_START + +namespace operation { + +// --------------------------------------------------------------------------- + +bool isAxisOrderReversal(int methodEPSGCode); + +// --------------------------------------------------------------------------- + +class InverseCoordinateOperation; +/** Shared pointer of InverseCoordinateOperation */ +using InverseCoordinateOperationPtr = + std::shared_ptr<InverseCoordinateOperation>; +/** Non-null shared pointer of InverseCoordinateOperation */ +using InverseCoordinateOperationNNPtr = util::nn<InverseCoordinateOperationPtr>; + +/** \brief Inverse operation of a CoordinateOperation. + * + * This is used when there is no straightforward way of building another + * subclass of CoordinateOperation that models the inverse operation. + */ +class InverseCoordinateOperation : virtual public CoordinateOperation { + public: + InverseCoordinateOperation( + const CoordinateOperationNNPtr &forwardOperationIn, + bool wktSupportsInversion); + + ~InverseCoordinateOperation() override; + + void _exportToPROJString(io::PROJStringFormatter *formatter) + const override; // throw(FormattingException) + + bool _isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion = + util::IComparable::Criterion::STRICT, + const io::DatabaseContextPtr &dbContext = nullptr) const override; + + CoordinateOperationNNPtr inverse() const override; + + const CoordinateOperationNNPtr &forwardOperation() const { + return forwardOperation_; + } + + protected: + CoordinateOperationNNPtr forwardOperation_; + bool wktSupportsInversion_; + + void setPropertiesFromForward(); +}; + +// --------------------------------------------------------------------------- + +/** \brief Inverse of a conversion. */ +class InverseConversion : public Conversion, public InverseCoordinateOperation { + public: + explicit InverseConversion(const ConversionNNPtr &forward); + + ~InverseConversion() override; + + void _exportToWKT(io::WKTFormatter *formatter) const override { + Conversion::_exportToWKT(formatter); + } + + void _exportToJSON(io::JSONFormatter *formatter) const override { + Conversion::_exportToJSON(formatter); + } + + void + _exportToPROJString(io::PROJStringFormatter *formatter) const override { + InverseCoordinateOperation::_exportToPROJString(formatter); + } + + bool _isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion = + util::IComparable::Criterion::STRICT, + const io::DatabaseContextPtr &dbContext = nullptr) const override { + return InverseCoordinateOperation::_isEquivalentTo(other, criterion, + dbContext); + } + + CoordinateOperationNNPtr inverse() const override { + return InverseCoordinateOperation::inverse(); + } + + ConversionNNPtr inverseAsConversion() const; + +#ifdef _MSC_VER + // To avoid a warning C4250: 'osgeo::proj::operation::InverseConversion': + // inherits + // 'osgeo::proj::operation::SingleOperation::osgeo::proj::operation::SingleOperation::gridsNeeded' + // via dominance + std::set<GridDescription> + gridsNeeded(const io::DatabaseContextPtr &databaseContext, + bool considerKnownGridsAsAvailable) const override { + return SingleOperation::gridsNeeded(databaseContext, + considerKnownGridsAsAvailable); + } +#endif + + static CoordinateOperationNNPtr create(const ConversionNNPtr &forward); + + CoordinateOperationNNPtr _shallowClone() const override; +}; + +// --------------------------------------------------------------------------- + +/** \brief Inverse of a transformation. */ +class InverseTransformation : public Transformation, + public InverseCoordinateOperation { + public: + explicit InverseTransformation(const TransformationNNPtr &forward); + + ~InverseTransformation() override; + + void _exportToWKT(io::WKTFormatter *formatter) const override; + + void + _exportToPROJString(io::PROJStringFormatter *formatter) const override { + return InverseCoordinateOperation::_exportToPROJString(formatter); + } + + void _exportToJSON(io::JSONFormatter *formatter) const override { + Transformation::_exportToJSON(formatter); + } + + bool _isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion = + util::IComparable::Criterion::STRICT, + const io::DatabaseContextPtr &dbContext = nullptr) const override { + return InverseCoordinateOperation::_isEquivalentTo(other, criterion, + dbContext); + } + + CoordinateOperationNNPtr inverse() const override { + return InverseCoordinateOperation::inverse(); + } + + TransformationNNPtr inverseAsTransformation() const; + +#ifdef _MSC_VER + // To avoid a warning C4250: + // 'osgeo::proj::operation::InverseTransformation': inherits + // 'osgeo::proj::operation::SingleOperation::osgeo::proj::operation::SingleOperation::gridsNeeded' + // via dominance + std::set<GridDescription> + gridsNeeded(const io::DatabaseContextPtr &databaseContext, + bool considerKnownGridsAsAvailable) const override { + return SingleOperation::gridsNeeded(databaseContext, + considerKnownGridsAsAvailable); + } +#endif + + static TransformationNNPtr create(const TransformationNNPtr &forward); + + CoordinateOperationNNPtr _shallowClone() const override; +}; + +// --------------------------------------------------------------------------- + +class PROJBasedOperation; +/** Shared pointer of PROJBasedOperation */ +using PROJBasedOperationPtr = std::shared_ptr<PROJBasedOperation>; +/** Non-null shared pointer of PROJBasedOperation */ +using PROJBasedOperationNNPtr = util::nn<PROJBasedOperationPtr>; + +/** \brief A PROJ-string based coordinate operation. + */ +class PROJBasedOperation : public SingleOperation { + public: + ~PROJBasedOperation() override; + + void _exportToWKT(io::WKTFormatter *formatter) + const override; // throw(io::FormattingException) + + CoordinateOperationNNPtr inverse() const override; + + static PROJBasedOperationNNPtr + create(const util::PropertyMap &properties, const std::string &PROJString, + const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies); + + static PROJBasedOperationNNPtr + create(const util::PropertyMap &properties, + const io::IPROJStringExportableNNPtr &projExportable, bool inverse, + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const crs::CRSPtr &interpolationCRS, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies, + bool hasRoughTransformation); + + std::set<GridDescription> + gridsNeeded(const io::DatabaseContextPtr &databaseContext, + bool considerKnownGridsAsAvailable) const override; + + protected: + PROJBasedOperation(const PROJBasedOperation &) = default; + explicit PROJBasedOperation(const OperationMethodNNPtr &methodIn); + + void _exportToPROJString(io::PROJStringFormatter *formatter) + const override; // throw(FormattingException) + + void _exportToJSON(io::JSONFormatter *formatter) + const override; // throw(FormattingException) + + CoordinateOperationNNPtr _shallowClone() const override; + + INLINED_MAKE_SHARED + + private: + std::string projString_{}; + io::IPROJStringExportablePtr projStringExportable_{}; + bool inverse_ = false; +}; + +// --------------------------------------------------------------------------- + +class InvalidOperationEmptyIntersection : public InvalidOperation { + public: + explicit InvalidOperationEmptyIntersection(const std::string &message); + InvalidOperationEmptyIntersection( + const InvalidOperationEmptyIntersection &other); + ~InvalidOperationEmptyIntersection() override; +}; +} // namespace operation + +NS_PROJ_END + +//! @endcond + +#endif // COORDINATEOPERATION_INTERNAL_HH_INCLUDED diff --git a/src/iso19111/operation/coordinateoperation_private.hpp b/src/iso19111/operation/coordinateoperation_private.hpp new file mode 100644 index 00000000..42bedd4b --- /dev/null +++ b/src/iso19111/operation/coordinateoperation_private.hpp @@ -0,0 +1,88 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef COORDINATEROPERATION_PRIVATE_HPP +#define COORDINATEROPERATION_PRIVATE_HPP + +#include "proj/coordinateoperation.hpp" +#include "proj/util.hpp" + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CoordinateOperation::Private { + util::optional<std::string> operationVersion_{}; + std::vector<metadata::PositionalAccuracyNNPtr> + coordinateOperationAccuracies_{}; + std::weak_ptr<crs::CRS> sourceCRSWeak_{}; + std::weak_ptr<crs::CRS> targetCRSWeak_{}; + crs::CRSPtr interpolationCRS_{}; + util::optional<common::DataEpoch> sourceCoordinateEpoch_{}; + util::optional<common::DataEpoch> targetCoordinateEpoch_{}; + bool hasBallparkTransformation_ = false; + bool use3DHelmert_ = false; + + // do not set this for a ProjectedCRS.definingConversion + struct CRSStrongRef { + crs::CRSNNPtr sourceCRS_; + crs::CRSNNPtr targetCRS_; + CRSStrongRef(const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn) + : sourceCRS_(sourceCRSIn), targetCRS_(targetCRSIn) {} + }; + std::unique_ptr<CRSStrongRef> strongRef_{}; + + Private() = default; + Private(const Private &other) + : operationVersion_(other.operationVersion_), + coordinateOperationAccuracies_(other.coordinateOperationAccuracies_), + sourceCRSWeak_(other.sourceCRSWeak_), + targetCRSWeak_(other.targetCRSWeak_), + interpolationCRS_(other.interpolationCRS_), + sourceCoordinateEpoch_(other.sourceCoordinateEpoch_), + targetCoordinateEpoch_(other.targetCoordinateEpoch_), + hasBallparkTransformation_(other.hasBallparkTransformation_), + strongRef_(other.strongRef_ ? internal::make_unique<CRSStrongRef>( + *(other.strongRef_)) + : nullptr) {} + + Private &operator=(const Private &) = delete; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END + +#endif // COORDINATEROPERATION_PRIVATE_HPP diff --git a/src/iso19111/operation/coordinateoperationfactory.cpp b/src/iso19111/operation/coordinateoperationfactory.cpp new file mode 100644 index 00000000..b8a9bcdf --- /dev/null +++ b/src/iso19111/operation/coordinateoperationfactory.cpp @@ -0,0 +1,5236 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/coordinateoperation.hpp" +#include "proj/common.hpp" +#include "proj/crs.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" +#include "proj/internal/tracing.hpp" + +#include "coordinateoperation_internal.hpp" +#include "coordinateoperation_private.hpp" +#include "oputils.hpp" + +// PROJ include order is sensitive +// clang-format off +#include "proj.h" +#include "proj_internal.h" // M_PI +// clang-format on + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <cstring> +#include <memory> +#include <set> +#include <string> +#include <vector> + +// #define TRACE_CREATE_OPERATIONS +// #define DEBUG_SORT +// #define DEBUG_CONCATENATED_OPERATION +#if defined(DEBUG_SORT) || defined(DEBUG_CONCATENATED_OPERATION) +#include <iostream> + +void dumpWKT(const NS_PROJ::crs::CRS *crs); +void dumpWKT(const NS_PROJ::crs::CRS *crs) { + auto f(NS_PROJ::io::WKTFormatter::create( + NS_PROJ::io::WKTFormatter::Convention::WKT2_2019)); + std::cerr << crs->exportToWKT(f.get()) << std::endl; +} + +void dumpWKT(const NS_PROJ::crs::CRSPtr &crs); +void dumpWKT(const NS_PROJ::crs::CRSPtr &crs) { dumpWKT(crs.get()); } + +void dumpWKT(const NS_PROJ::crs::CRSNNPtr &crs); +void dumpWKT(const NS_PROJ::crs::CRSNNPtr &crs) { + dumpWKT(crs.as_nullable().get()); +} + +void dumpWKT(const NS_PROJ::crs::GeographicCRSPtr &crs); +void dumpWKT(const NS_PROJ::crs::GeographicCRSPtr &crs) { dumpWKT(crs.get()); } + +void dumpWKT(const NS_PROJ::crs::GeographicCRSNNPtr &crs); +void dumpWKT(const NS_PROJ::crs::GeographicCRSNNPtr &crs) { + dumpWKT(crs.as_nullable().get()); +} + +#endif + +using namespace NS_PROJ::internal; + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +// --------------------------------------------------------------------------- + +#ifdef TRACE_CREATE_OPERATIONS + +//! @cond Doxygen_Suppress + +static std::string objectAsStr(const common::IdentifiedObject *obj) { + std::string ret(obj->nameStr()); + const auto &ids = obj->identifiers(); + if (!ids.empty()) { + ret += " ("; + ret += (*ids[0]->codeSpace()) + ":" + ids[0]->code(); + ret += ")"; + } + return ret; +} +//! @endcond + +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static double getPseudoArea(const metadata::ExtentPtr &extent) { + if (!extent) + return 0.0; + const auto &geographicElements = extent->geographicElements(); + if (geographicElements.empty()) + return 0.0; + auto bbox = dynamic_cast<const metadata::GeographicBoundingBox *>( + geographicElements[0].get()); + if (!bbox) + return 0; + double w = bbox->westBoundLongitude(); + double s = bbox->southBoundLatitude(); + double e = bbox->eastBoundLongitude(); + double n = bbox->northBoundLatitude(); + if (w > e) { + e += 360.0; + } + // Integrate cos(lat) between south_lat and north_lat + return (e - w) * (std::sin(common::Angle(n).getSIValue()) - + std::sin(common::Angle(s).getSIValue())); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CoordinateOperationContext::Private { + io::AuthorityFactoryPtr authorityFactory_{}; + metadata::ExtentPtr extent_{}; + double accuracy_ = 0.0; + SourceTargetCRSExtentUse sourceAndTargetCRSExtentUse_ = + CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST; + SpatialCriterion spatialCriterion_ = + CoordinateOperationContext::SpatialCriterion::STRICT_CONTAINMENT; + bool usePROJNames_ = true; + GridAvailabilityUse gridAvailabilityUse_ = + GridAvailabilityUse::USE_FOR_SORTING; + IntermediateCRSUse allowUseIntermediateCRS_ = CoordinateOperationContext:: + IntermediateCRSUse::IF_NO_DIRECT_TRANSFORMATION; + std::vector<std::pair<std::string, std::string>> + intermediateCRSAuthCodes_{}; + bool discardSuperseded_ = true; + bool allowBallpark_ = true; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateOperationContext::~CoordinateOperationContext() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateOperationContext::CoordinateOperationContext() + : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +/** \brief Return the authority factory, or null */ +const io::AuthorityFactoryPtr & +CoordinateOperationContext::getAuthorityFactory() const { + return d->authorityFactory_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the desired area of interest, or null */ +const metadata::ExtentPtr & +CoordinateOperationContext::getAreaOfInterest() const { + return d->extent_; +} + +// --------------------------------------------------------------------------- + +/** \brief Set the desired area of interest, or null */ +void CoordinateOperationContext::setAreaOfInterest( + const metadata::ExtentPtr &extent) { + d->extent_ = extent; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the desired accuracy (in metre), or 0 */ +double CoordinateOperationContext::getDesiredAccuracy() const { + return d->accuracy_; +} + +// --------------------------------------------------------------------------- + +/** \brief Set the desired accuracy (in metre), or 0 */ +void CoordinateOperationContext::setDesiredAccuracy(double accuracy) { + d->accuracy_ = 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). + * + * The default is + * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST. + */ +void CoordinateOperationContext::setSourceAndTargetCRSExtentUse( + SourceTargetCRSExtentUse use) { + d->sourceAndTargetCRSExtentUse_ = use; +} + +// --------------------------------------------------------------------------- + +/** \brief Return 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). + * + * The default is + * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST. + */ +CoordinateOperationContext::SourceTargetCRSExtentUse +CoordinateOperationContext::getSourceAndTargetCRSExtentUse() const { + return d->sourceAndTargetCRSExtentUse_; +} + +// --------------------------------------------------------------------------- + +/** \brief Set the spatial criterion to use when comparing the area of + * validity + * of coordinate operations with the area of interest / area of validity of + * source and target CRS. + * + * The default is STRICT_CONTAINMENT. + */ +void CoordinateOperationContext::setSpatialCriterion( + SpatialCriterion criterion) { + d->spatialCriterion_ = criterion; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the spatial criterion to use when comparing the area of + * validity + * of coordinate operations with the area of interest / area of validity of + * source and target CRS. + * + * The default is STRICT_CONTAINMENT. + */ +CoordinateOperationContext::SpatialCriterion +CoordinateOperationContext::getSpatialCriterion() const { + return d->spatialCriterion_; +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether PROJ alternative grid names should be substituted to + * the official authority names. + * + * This only has effect is an authority factory with a non-null database context + * has been attached to this context. + * + * If set to false, it is still possible to + * obtain later the substitution by using io::PROJStringFormatter::create() + * with a non-null database context. + * + * The default is true. + */ +void CoordinateOperationContext::setUsePROJAlternativeGridNames( + bool usePROJNames) { + d->usePROJNames_ = usePROJNames; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether PROJ alternative grid names should be substituted to + * the official authority names. + * + * The default is true. + */ +bool CoordinateOperationContext::getUsePROJAlternativeGridNames() const { + return d->usePROJNames_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether transformations that are superseded (but not + * deprecated) + * should be discarded. + * + * The default is true. + */ +bool CoordinateOperationContext::getDiscardSuperseded() const { + return d->discardSuperseded_; +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether transformations that are superseded (but not deprecated) + * should be discarded. + * + * The default is true. + */ +void CoordinateOperationContext::setDiscardSuperseded(bool discard) { + d->discardSuperseded_ = discard; +} + +// --------------------------------------------------------------------------- + +/** \brief Set how grid availability is used. + * + * The default is USE_FOR_SORTING. + */ +void CoordinateOperationContext::setGridAvailabilityUse( + GridAvailabilityUse use) { + d->gridAvailabilityUse_ = use; +} + +// --------------------------------------------------------------------------- + +/** \brief Return how grid availability is used. + * + * The default is USE_FOR_SORTING. + */ +CoordinateOperationContext::GridAvailabilityUse +CoordinateOperationContext::getGridAvailabilityUse() const { + return d->gridAvailabilityUse_; +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether an intermediate pivot CRS can be used for researching + * coordinate operations between a source and target CRS. + * + * Concretely if in the database there is an operation from A to C + * (or C to A), and another one from C to B (or B to C), but no direct + * operation between A and B, setting this parameter to + * ALWAYS/IF_NO_DIRECT_TRANSFORMATION, allow chaining both operations. + * + * The current implementation is limited to researching one intermediate + * step. + * + * By default, with the IF_NO_DIRECT_TRANSFORMATION stratgey, all potential + * C candidates will be used if there is no direct transformation. + */ +void CoordinateOperationContext::setAllowUseIntermediateCRS( + IntermediateCRSUse use) { + d->allowUseIntermediateCRS_ = use; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether an intermediate pivot CRS can be used for researching + * coordinate operations between a source and target CRS. + * + * Concretely if in the database there is an operation from A to C + * (or C to A), and another one from C to B (or B to C), but no direct + * operation between A and B, setting this parameter to + * ALWAYS/IF_NO_DIRECT_TRANSFORMATION, allow chaining both operations. + * + * The default is IF_NO_DIRECT_TRANSFORMATION. + */ +CoordinateOperationContext::IntermediateCRSUse +CoordinateOperationContext::getAllowUseIntermediateCRS() const { + return d->allowUseIntermediateCRS_; +} + +// --------------------------------------------------------------------------- + +/** \brief Restrict the potential pivot CRSs that can be used when trying to + * build a coordinate operation between two CRS that have no direct operation. + * + * @param intermediateCRSAuthCodes a vector of (auth_name, code) that can be + * used as potential pivot RS + */ +void CoordinateOperationContext::setIntermediateCRS( + const std::vector<std::pair<std::string, std::string>> + &intermediateCRSAuthCodes) { + d->intermediateCRSAuthCodes_ = intermediateCRSAuthCodes; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the potential pivot CRSs that can be used when trying to + * build a coordinate operation between two CRS that have no direct operation. + * + */ +const std::vector<std::pair<std::string, std::string>> & +CoordinateOperationContext::getIntermediateCRS() const { + return d->intermediateCRSAuthCodes_; +} + +// --------------------------------------------------------------------------- + +/** \brief Creates a context for a coordinate operation. + * + * If a non null authorityFactory is provided, the resulting context should + * not be used simultaneously by more than one thread. + * + * If authorityFactory->getAuthority() is the empty string, then coordinate + * operations from any authority will be searched, with the restrictions set + * in the authority_to_authority_preference database table. + * If authorityFactory->getAuthority() is set to "any", then coordinate + * operations from any authority will be searched + * If authorityFactory->getAuthority() is a non-empty string different of "any", + * then coordinate operatiosn will be searched only in that authority namespace. + * + * @param authorityFactory Authority factory, or null if no database lookup + * is allowed. + * Use io::authorityFactory::create(context, std::string()) to allow all + * authorities to be used. + * @param extent Area of interest, or null if none is known. + * @param accuracy Maximum allowed accuracy in metre, as specified in or + * 0 to get best accuracy. + * @return a new context. + */ +CoordinateOperationContextNNPtr CoordinateOperationContext::create( + const io::AuthorityFactoryPtr &authorityFactory, + const metadata::ExtentPtr &extent, double accuracy) { + auto ctxt = NN_NO_CHECK( + CoordinateOperationContext::make_unique<CoordinateOperationContext>()); + ctxt->d->authorityFactory_ = authorityFactory; + ctxt->d->extent_ = extent; + ctxt->d->accuracy_ = accuracy; + return ctxt; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CoordinateOperationFactory::Private { + + struct Context { + // This is the extent of the source CRS and target CRS of the initial + // CoordinateOperationFactory::createOperations() public call, not + // necessarily the ones of intermediate + // CoordinateOperationFactory::Private::createOperations() calls. + // This is used to compare transformations area of use against the + // area of use of the source & target CRS. + const metadata::ExtentPtr &extent1; + const metadata::ExtentPtr &extent2; + const CoordinateOperationContextNNPtr &context; + bool inCreateOperationsWithDatumPivotAntiRecursion = false; + bool inCreateOperationsGeogToVertWithAlternativeGeog = false; + bool inCreateOperationsGeogToVertWithIntermediateVert = false; + bool skipHorizontalTransformation = false; + std::map<std::pair<io::AuthorityFactory::ObjectType, std::string>, + std::list<std::pair<std::string, std::string>>> + cacheNameToCRS{}; + + Context(const metadata::ExtentPtr &extent1In, + const metadata::ExtentPtr &extent2In, + const CoordinateOperationContextNNPtr &contextIn) + : extent1(extent1In), extent2(extent2In), context(contextIn) {} + }; + + static std::vector<CoordinateOperationNNPtr> + createOperations(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS, Context &context); + + private: + static constexpr bool disallowEmptyIntersection = true; + + static void + buildCRSIds(const crs::CRSNNPtr &crs, Private::Context &context, + std::list<std::pair<std::string, std::string>> &ids); + + static std::vector<CoordinateOperationNNPtr> findOpsInRegistryDirect( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, bool &resNonEmptyBeforeFiltering); + + static std::vector<CoordinateOperationNNPtr> + findOpsInRegistryDirectTo(const crs::CRSNNPtr &targetCRS, + Private::Context &context); + + static std::vector<CoordinateOperationNNPtr> + findsOpsInRegistryWithIntermediate( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, + bool useCreateBetweenGeodeticCRSWithDatumBasedIntermediates); + + static void createOperationsFromProj4Ext( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const crs::BoundCRS *boundSrc, const crs::BoundCRS *boundDst, + std::vector<CoordinateOperationNNPtr> &res); + + static bool createOperationsFromDatabase( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::GeodeticCRS *geodSrc, + const crs::GeodeticCRS *geodDst, const crs::GeographicCRS *geogSrc, + const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc, + const crs::VerticalCRS *vertDst, + std::vector<CoordinateOperationNNPtr> &res); + + static std::vector<CoordinateOperationNNPtr> + createOperationsGeogToVertFromGeoid(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS, + const crs::VerticalCRS *vertDst, + Context &context); + + static std::vector<CoordinateOperationNNPtr> + createOperationsGeogToVertWithIntermediateVert( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const crs::VerticalCRS *vertDst, Context &context); + + static std::vector<CoordinateOperationNNPtr> + createOperationsGeogToVertWithAlternativeGeog( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Context &context); + + static void createOperationsFromDatabaseWithVertCRS( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::GeographicCRS *geogSrc, + const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc, + const crs::VerticalCRS *vertDst, + std::vector<CoordinateOperationNNPtr> &res); + + static void createOperationsGeodToGeod( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::GeodeticCRS *geodSrc, + const crs::GeodeticCRS *geodDst, + std::vector<CoordinateOperationNNPtr> &res); + + static void createOperationsDerivedTo( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::DerivedCRS *derivedSrc, + std::vector<CoordinateOperationNNPtr> &res); + + static void createOperationsBoundToGeog( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::BoundCRS *boundSrc, + const crs::GeographicCRS *geogDst, + std::vector<CoordinateOperationNNPtr> &res); + + static void createOperationsBoundToVert( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::BoundCRS *boundSrc, + const crs::VerticalCRS *vertDst, + std::vector<CoordinateOperationNNPtr> &res); + + static void createOperationsVertToVert( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::VerticalCRS *vertSrc, + const crs::VerticalCRS *vertDst, + std::vector<CoordinateOperationNNPtr> &res); + + static void createOperationsVertToGeog( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::VerticalCRS *vertSrc, + const crs::GeographicCRS *geogDst, + std::vector<CoordinateOperationNNPtr> &res); + + static void createOperationsVertToGeogBallpark( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::VerticalCRS *vertSrc, + const crs::GeographicCRS *geogDst, + std::vector<CoordinateOperationNNPtr> &res); + + static void createOperationsBoundToBound( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::BoundCRS *boundSrc, + const crs::BoundCRS *boundDst, + std::vector<CoordinateOperationNNPtr> &res); + + static void createOperationsCompoundToGeog( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::CompoundCRS *compoundSrc, + const crs::GeographicCRS *geogDst, + std::vector<CoordinateOperationNNPtr> &res); + + static void createOperationsToGeod( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::GeodeticCRS *geodDst, + std::vector<CoordinateOperationNNPtr> &res); + + static void createOperationsCompoundToCompound( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::CompoundCRS *compoundSrc, + const crs::CompoundCRS *compoundDst, + std::vector<CoordinateOperationNNPtr> &res); + + static void createOperationsBoundToCompound( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::BoundCRS *boundSrc, + const crs::CompoundCRS *compoundDst, + std::vector<CoordinateOperationNNPtr> &res); + + static std::vector<CoordinateOperationNNPtr> createOperationsGeogToGeog( + std::vector<CoordinateOperationNNPtr> &res, + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::GeographicCRS *geogSrc, + const crs::GeographicCRS *geogDst); + + static void createOperationsWithDatumPivot( + std::vector<CoordinateOperationNNPtr> &res, + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const crs::GeodeticCRS *geodSrc, const crs::GeodeticCRS *geodDst, + Context &context); + + static bool + hasPerfectAccuracyResult(const std::vector<CoordinateOperationNNPtr> &res, + const Context &context); + + static void setCRSs(CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS); +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateOperationFactory::~CoordinateOperationFactory() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateOperationFactory::CoordinateOperationFactory() : d(nullptr) {} + +// --------------------------------------------------------------------------- + +/** \brief Find a CoordinateOperation from sourceCRS to targetCRS. + * + * This is a helper of createOperations(), using a coordinate operation + * context + * with no authority factory (so no catalog searching is done), no desired + * accuracy and no area of interest. + * This returns the first operation of the result set of createOperations(), + * or null if none found. + * + * @param sourceCRS source CRS. + * @param targetCRS source CRS. + * @return a CoordinateOperation or nullptr. + */ +CoordinateOperationPtr CoordinateOperationFactory::createOperation( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) const { + auto res = createOperations( + sourceCRS, targetCRS, + CoordinateOperationContext::create(nullptr, nullptr, 0.0)); + if (!res.empty()) { + return res[0]; + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +struct PrecomputedOpCharacteristics { + double area_{}; + double accuracy_{}; + bool isPROJExportable_ = false; + bool hasGrids_ = false; + bool gridsAvailable_ = false; + bool gridsKnown_ = false; + size_t stepCount_ = 0; + bool isApprox_ = false; + bool hasBallparkVertical_ = false; + bool isNullTransformation_ = false; + + PrecomputedOpCharacteristics() = default; + PrecomputedOpCharacteristics(double area, double accuracy, + bool isPROJExportable, bool hasGrids, + bool gridsAvailable, bool gridsKnown, + size_t stepCount, bool isApprox, + bool hasBallparkVertical, + bool isNullTransformation) + : area_(area), accuracy_(accuracy), isPROJExportable_(isPROJExportable), + hasGrids_(hasGrids), gridsAvailable_(gridsAvailable), + gridsKnown_(gridsKnown), stepCount_(stepCount), isApprox_(isApprox), + hasBallparkVertical_(hasBallparkVertical), + isNullTransformation_(isNullTransformation) {} +}; + +// --------------------------------------------------------------------------- + +// We could have used a lambda instead of this old-school way, but +// filterAndSort() is already huge. +struct SortFunction { + + const std::map<CoordinateOperation *, PrecomputedOpCharacteristics> ↦ + + explicit SortFunction(const std::map<CoordinateOperation *, + PrecomputedOpCharacteristics> &mapIn) + : map(mapIn) {} + + // Sorting function + // Return true if a < b + bool compare(const CoordinateOperationNNPtr &a, + const CoordinateOperationNNPtr &b) const { + auto iterA = map.find(a.get()); + assert(iterA != map.end()); + auto iterB = map.find(b.get()); + assert(iterB != map.end()); + + // CAUTION: the order of the comparisons is extremely important + // to get the intended result. + + if (iterA->second.isPROJExportable_ && + !iterB->second.isPROJExportable_) { + return true; + } + if (!iterA->second.isPROJExportable_ && + iterB->second.isPROJExportable_) { + return false; + } + + if (!iterA->second.isApprox_ && iterB->second.isApprox_) { + return true; + } + if (iterA->second.isApprox_ && !iterB->second.isApprox_) { + return false; + } + + if (!iterA->second.hasBallparkVertical_ && + iterB->second.hasBallparkVertical_) { + return true; + } + if (iterA->second.hasBallparkVertical_ && + !iterB->second.hasBallparkVertical_) { + return false; + } + + if (!iterA->second.isNullTransformation_ && + iterB->second.isNullTransformation_) { + return true; + } + if (iterA->second.isNullTransformation_ && + !iterB->second.isNullTransformation_) { + return false; + } + + // Operations where grids are all available go before other + if (iterA->second.gridsAvailable_ && !iterB->second.gridsAvailable_) { + return true; + } + if (iterB->second.gridsAvailable_ && !iterA->second.gridsAvailable_) { + return false; + } + + // Operations where grids are all known in our DB go before other + if (iterA->second.gridsKnown_ && !iterB->second.gridsKnown_) { + return true; + } + if (iterB->second.gridsKnown_ && !iterA->second.gridsKnown_) { + return false; + } + + // Operations with known accuracy go before those with unknown accuracy + const double accuracyA = iterA->second.accuracy_; + const double accuracyB = iterB->second.accuracy_; + if (accuracyA >= 0 && accuracyB < 0) { + return true; + } + if (accuracyB >= 0 && accuracyA < 0) { + return false; + } + + if (accuracyA < 0 && accuracyB < 0) { + // unknown accuracy ? then prefer operations with grids, which + // are likely to have best practical accuracy + if (iterA->second.hasGrids_ && !iterB->second.hasGrids_) { + return true; + } + if (!iterA->second.hasGrids_ && iterB->second.hasGrids_) { + return false; + } + } + + // Operations with larger non-zero area of use go before those with + // lower one + const double areaA = iterA->second.area_; + const double areaB = iterB->second.area_; + if (areaA > 0) { + if (areaA > areaB) { + return true; + } + if (areaA < areaB) { + return false; + } + } else if (areaB > 0) { + return false; + } + + // Operations with better accuracy go before those with worse one + if (accuracyA >= 0 && accuracyA < accuracyB) { + return true; + } + if (accuracyB >= 0 && accuracyB < accuracyA) { + return false; + } + + if (accuracyA >= 0 && accuracyA == accuracyB) { + // same accuracy ? then prefer operations without grids + if (!iterA->second.hasGrids_ && iterB->second.hasGrids_) { + return true; + } + if (iterA->second.hasGrids_ && !iterB->second.hasGrids_) { + return false; + } + } + + // The less intermediate steps, the better + if (iterA->second.stepCount_ < iterB->second.stepCount_) { + return true; + } + if (iterB->second.stepCount_ < iterA->second.stepCount_) { + return false; + } + + const auto &a_name = a->nameStr(); + const auto &b_name = b->nameStr(); + // The shorter name, the better ? + if (a_name.size() < b_name.size()) { + return true; + } + if (b_name.size() < a_name.size()) { + return false; + } + + // Arbitrary final criterion. We actually return the greater element + // first, so that "Amersfoort to WGS 84 (4)" is presented before + // "Amersfoort to WGS 84 (3)", which is probably a better guess. + + // Except for French NTF (Paris) to NTF, where the (1) conversion + // should be preferred because in the remarks of (2), it is mentioned + // OGP prefers value from IGN Paris (code 1467)... + if (a_name.find("NTF (Paris) to NTF (1)") != std::string::npos && + b_name.find("NTF (Paris) to NTF (2)") != std::string::npos) { + return true; + } + if (a_name.find("NTF (Paris) to NTF (2)") != std::string::npos && + b_name.find("NTF (Paris) to NTF (1)") != std::string::npos) { + return false; + } + if (a_name.find("NTF (Paris) to RGF93 (1)") != std::string::npos && + b_name.find("NTF (Paris) to RGF93 (2)") != std::string::npos) { + return true; + } + if (a_name.find("NTF (Paris) to RGF93 (2)") != std::string::npos && + b_name.find("NTF (Paris) to RGF93 (1)") != std::string::npos) { + return false; + } + + return a_name > b_name; + } + + bool operator()(const CoordinateOperationNNPtr &a, + const CoordinateOperationNNPtr &b) const { + const bool ret = compare(a, b); +#if 0 + std::cerr << a->nameStr() << " < " << b->nameStr() << " : " << ret << std::endl; +#endif + return ret; + } +}; + +// --------------------------------------------------------------------------- + +static size_t getStepCount(const CoordinateOperationNNPtr &op) { + auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get()); + size_t stepCount = 1; + if (concat) { + stepCount = concat->operations().size(); + } + return stepCount; +} + +// --------------------------------------------------------------------------- + +// Return number of steps that are transformations (and not conversions) +static size_t getTransformationStepCount(const CoordinateOperationNNPtr &op) { + auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get()); + size_t stepCount = 1; + if (concat) { + stepCount = 0; + for (const auto &subOp : concat->operations()) { + if (dynamic_cast<const Conversion *>(subOp.get()) == nullptr) { + stepCount++; + } + } + } + return stepCount; +} + +// --------------------------------------------------------------------------- + +static bool isNullTransformation(const std::string &name) { + if (name.find(" + ") != std::string::npos) + return false; + return starts_with(name, BALLPARK_GEOCENTRIC_TRANSLATION) || + starts_with(name, BALLPARK_GEOGRAPHIC_OFFSET) || + starts_with(name, NULL_GEOGRAPHIC_OFFSET) || + starts_with(name, NULL_GEOCENTRIC_TRANSLATION); +} + +// --------------------------------------------------------------------------- + +struct FilterResults { + + FilterResults(const std::vector<CoordinateOperationNNPtr> &sourceListIn, + const CoordinateOperationContextNNPtr &contextIn, + const metadata::ExtentPtr &extent1In, + const metadata::ExtentPtr &extent2In, + bool forceStrictContainmentTest) + : sourceList(sourceListIn), context(contextIn), extent1(extent1In), + extent2(extent2In), areaOfInterest(context->getAreaOfInterest()), + desiredAccuracy(context->getDesiredAccuracy()), + sourceAndTargetCRSExtentUse( + context->getSourceAndTargetCRSExtentUse()) { + + computeAreaOfInterest(); + filterOut(forceStrictContainmentTest); + } + + FilterResults &andSort() { + sort(); + + // And now that we have a sorted list, we can remove uninteresting + // results + // ... + removeSyntheticNullTransforms(); + removeUninterestingOps(); + removeDuplicateOps(); + removeSyntheticNullTransforms(); + return *this; + } + + // ---------------------------------------------------------------------- + + // cppcheck-suppress functionStatic + const std::vector<CoordinateOperationNNPtr> &getRes() { return res; } + + // ---------------------------------------------------------------------- + private: + const std::vector<CoordinateOperationNNPtr> &sourceList; + const CoordinateOperationContextNNPtr &context; + const metadata::ExtentPtr &extent1; + const metadata::ExtentPtr &extent2; + metadata::ExtentPtr areaOfInterest; + const double desiredAccuracy = context->getDesiredAccuracy(); + const CoordinateOperationContext::SourceTargetCRSExtentUse + sourceAndTargetCRSExtentUse; + + bool hasOpThatContainsAreaOfInterestAndNoGrid = false; + std::vector<CoordinateOperationNNPtr> res{}; + + // ---------------------------------------------------------------------- + void computeAreaOfInterest() { + + // Compute an area of interest from the CRS extent if the user did + // not specify one + if (!areaOfInterest) { + if (sourceAndTargetCRSExtentUse == + CoordinateOperationContext::SourceTargetCRSExtentUse:: + INTERSECTION) { + if (extent1 && extent2) { + areaOfInterest = + extent1->intersection(NN_NO_CHECK(extent2)); + } + } else if (sourceAndTargetCRSExtentUse == + CoordinateOperationContext::SourceTargetCRSExtentUse:: + SMALLEST) { + if (extent1 && extent2) { + if (getPseudoArea(extent1) < getPseudoArea(extent2)) { + areaOfInterest = extent1; + } else { + areaOfInterest = extent2; + } + } else if (extent1) { + areaOfInterest = extent1; + } else { + areaOfInterest = extent2; + } + } + } + } + + // --------------------------------------------------------------------------- + + void filterOut(bool forceStrictContainmentTest) { + + // Filter out operations that do not match the expected accuracy + // and area of use. + const auto spatialCriterion = + forceStrictContainmentTest + ? CoordinateOperationContext::SpatialCriterion:: + STRICT_CONTAINMENT + : context->getSpatialCriterion(); + bool hasOnlyBallpark = true; + bool hasNonBallparkWithoutExtent = false; + bool hasNonBallparkOpWithExtent = false; + const bool allowBallpark = context->getAllowBallparkTransformations(); + for (const auto &op : sourceList) { + if (desiredAccuracy != 0) { + const double accuracy = getAccuracy(op); + if (accuracy < 0 || accuracy > desiredAccuracy) { + continue; + } + } + if (!allowBallpark && op->hasBallparkTransformation()) { + continue; + } + if (areaOfInterest) { + bool emptyIntersection = false; + auto extent = getExtent(op, true, emptyIntersection); + if (!extent) { + if (!op->hasBallparkTransformation()) { + hasNonBallparkWithoutExtent = true; + } + continue; + } + if (!op->hasBallparkTransformation()) { + hasNonBallparkOpWithExtent = true; + } + bool extentContains = + extent->contains(NN_NO_CHECK(areaOfInterest)); + if (!hasOpThatContainsAreaOfInterestAndNoGrid && + extentContains) { + if (!op->hasBallparkTransformation() && + op->gridsNeeded(nullptr, true).empty()) { + hasOpThatContainsAreaOfInterestAndNoGrid = true; + } + } + if (spatialCriterion == + CoordinateOperationContext::SpatialCriterion:: + STRICT_CONTAINMENT && + !extentContains) { + continue; + } + if (spatialCriterion == + CoordinateOperationContext::SpatialCriterion:: + PARTIAL_INTERSECTION && + !extent->intersects(NN_NO_CHECK(areaOfInterest))) { + continue; + } + } else if (sourceAndTargetCRSExtentUse == + CoordinateOperationContext::SourceTargetCRSExtentUse:: + BOTH) { + bool emptyIntersection = false; + auto extent = getExtent(op, true, emptyIntersection); + if (!extent) { + if (!op->hasBallparkTransformation()) { + hasNonBallparkWithoutExtent = true; + } + continue; + } + if (!op->hasBallparkTransformation()) { + hasNonBallparkOpWithExtent = true; + } + bool extentContainsExtent1 = + !extent1 || extent->contains(NN_NO_CHECK(extent1)); + bool extentContainsExtent2 = + !extent2 || extent->contains(NN_NO_CHECK(extent2)); + if (!hasOpThatContainsAreaOfInterestAndNoGrid && + extentContainsExtent1 && extentContainsExtent2) { + if (!op->hasBallparkTransformation() && + op->gridsNeeded(nullptr, true).empty()) { + hasOpThatContainsAreaOfInterestAndNoGrid = true; + } + } + if (spatialCriterion == + CoordinateOperationContext::SpatialCriterion:: + STRICT_CONTAINMENT) { + if (!extentContainsExtent1 || !extentContainsExtent2) { + continue; + } + } else if (spatialCriterion == + CoordinateOperationContext::SpatialCriterion:: + PARTIAL_INTERSECTION) { + bool extentIntersectsExtent1 = + !extent1 || extent->intersects(NN_NO_CHECK(extent1)); + bool extentIntersectsExtent2 = + extent2 && extent->intersects(NN_NO_CHECK(extent2)); + if (!extentIntersectsExtent1 || !extentIntersectsExtent2) { + continue; + } + } + } + if (!op->hasBallparkTransformation()) { + hasOnlyBallpark = false; + } + res.emplace_back(op); + } + + // In case no operation has an extent and no result is found, + // retain all initial operations that match accuracy criterion. + if ((res.empty() && !hasNonBallparkOpWithExtent) || + (hasOnlyBallpark && hasNonBallparkWithoutExtent)) { + for (const auto &op : sourceList) { + if (desiredAccuracy != 0) { + const double accuracy = getAccuracy(op); + if (accuracy < 0 || accuracy > desiredAccuracy) { + continue; + } + } + if (!allowBallpark && op->hasBallparkTransformation()) { + continue; + } + res.emplace_back(op); + } + } + } + + // ---------------------------------------------------------------------- + + void sort() { + + // Precompute a number of parameters for each operation that will be + // useful for the sorting. + std::map<CoordinateOperation *, PrecomputedOpCharacteristics> map; + const auto gridAvailabilityUse = context->getGridAvailabilityUse(); + for (const auto &op : res) { + bool dummy = false; + auto extentOp = getExtent(op, true, dummy); + double area = 0.0; + if (extentOp) { + if (areaOfInterest) { + area = getPseudoArea( + extentOp->intersection(NN_NO_CHECK(areaOfInterest))); + } else if (extent1 && extent2) { + auto x = extentOp->intersection(NN_NO_CHECK(extent1)); + auto y = extentOp->intersection(NN_NO_CHECK(extent2)); + area = getPseudoArea(x) + getPseudoArea(y) - + ((x && y) + ? getPseudoArea(x->intersection(NN_NO_CHECK(y))) + : 0.0); + } else if (extent1) { + area = getPseudoArea( + extentOp->intersection(NN_NO_CHECK(extent1))); + } else if (extent2) { + area = getPseudoArea( + extentOp->intersection(NN_NO_CHECK(extent2))); + } else { + area = getPseudoArea(extentOp); + } + } + + bool hasGrids = false; + bool gridsAvailable = true; + bool gridsKnown = true; + if (context->getAuthorityFactory()) { + const auto gridsNeeded = op->gridsNeeded( + context->getAuthorityFactory()->databaseContext(), + gridAvailabilityUse == + CoordinateOperationContext::GridAvailabilityUse:: + KNOWN_AVAILABLE); + for (const auto &gridDesc : gridsNeeded) { + hasGrids = true; + if (gridAvailabilityUse == + CoordinateOperationContext::GridAvailabilityUse:: + USE_FOR_SORTING && + !gridDesc.available) { + gridsAvailable = false; + } + if (gridDesc.packageName.empty() && + !(!gridDesc.url.empty() && gridDesc.openLicense) && + !gridDesc.available) { + gridsKnown = false; + } + } + } + + const auto stepCount = getStepCount(op); + + bool isPROJExportable = false; + auto formatter = io::PROJStringFormatter::create(); + try { + op->exportToPROJString(formatter.get()); + // Grids might be missing, but at least this is something + // PROJ could potentially process + isPROJExportable = true; + } catch (const std::exception &) { + } + +#if 0 + std::cerr << op->nameStr() << " "; + std::cerr << area << " "; + std::cerr << getAccuracy(op) << " "; + std::cerr << isPROJExportable << " "; + std::cerr << hasGrids << " "; + std::cerr << gridsAvailable << " "; + std::cerr << gridsKnown << " "; + std::cerr << stepCount << " "; + std::cerr << op->hasBallparkTransformation() << " "; + std::cerr << isNullTransformation(op->nameStr()) << " "; + std::cerr << std::endl; +#endif + map[op.get()] = PrecomputedOpCharacteristics( + area, getAccuracy(op), isPROJExportable, hasGrids, + gridsAvailable, gridsKnown, stepCount, + op->hasBallparkTransformation(), + op->nameStr().find("ballpark vertical transformation") != + std::string::npos, + isNullTransformation(op->nameStr())); + } + + // Sort ! + SortFunction sortFunc(map); + std::sort(res.begin(), res.end(), sortFunc); + +// Debug code to check consistency of the sort function +#ifdef DEBUG_SORT + constexpr bool debugSort = true; +#elif !defined(NDEBUG) + const bool debugSort = getenv("PROJ_DEBUG_SORT_FUNCT") != nullptr; +#endif +#if defined(DEBUG_SORT) || !defined(NDEBUG) + if (debugSort) { + const bool assertIfIssue = + !(getenv("PROJ_DEBUG_SORT_FUNCT_ASSERT") != nullptr); + for (size_t i = 0; i < res.size(); ++i) { + for (size_t j = i + 1; j < res.size(); ++j) { + if (sortFunc(res[j], res[i])) { +#ifdef DEBUG_SORT + std::cerr << "Sorting issue with entry " << i << "(" + << res[i]->nameStr() << ") and " << j << "(" + << res[j]->nameStr() << ")" << std::endl; +#endif + if (assertIfIssue) { + assert(false); + } + } + } + } + } +#endif + } + + // ---------------------------------------------------------------------- + + void removeSyntheticNullTransforms() { + + // 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 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(); + if (opLast->hasBallparkTransformation() || + isNullTransformation(opLast->nameStr())) { + std::vector<CoordinateOperationNNPtr> resTemp; + for (size_t i = 0; i < res.size() - 1; i++) { + resTemp.emplace_back(res[i]); + } + res = std::move(resTemp); + } + } + } + + // ---------------------------------------------------------------------- + + void removeUninterestingOps() { + + // Eliminate operations that bring nothing, ie for a given area of use, + // do not keep operations that have similar or worse accuracy, but + // involve more (non conversion) steps + std::vector<CoordinateOperationNNPtr> resTemp; + metadata::ExtentPtr lastExtent; + double lastAccuracy = -1; + size_t lastStepCount = 0; + CoordinateOperationPtr lastOp; + + bool first = true; + for (const auto &op : res) { + const auto curAccuracy = getAccuracy(op); + bool dummy = false; + const auto curExtent = getExtent(op, true, dummy); + const auto curStepCount = getTransformationStepCount(op); + + if (first) { + resTemp.emplace_back(op); + first = false; + } else { + if (lastOp->_isEquivalentTo(op.get())) { + continue; + } + const bool sameExtent = + ((!curExtent && !lastExtent) || + (curExtent && lastExtent && + curExtent->contains(NN_NO_CHECK(lastExtent)) && + lastExtent->contains(NN_NO_CHECK(curExtent)))); + if (((curAccuracy >= lastAccuracy && lastAccuracy >= 0) || + (curAccuracy < 0 && lastAccuracy >= 0)) && + sameExtent && curStepCount > lastStepCount) { + continue; + } + + resTemp.emplace_back(op); + } + + lastOp = op.as_nullable(); + lastStepCount = curStepCount; + lastExtent = curExtent; + lastAccuracy = curAccuracy; + } + res = std::move(resTemp); + } + + // ---------------------------------------------------------------------- + + // cppcheck-suppress functionStatic + void removeDuplicateOps() { + + if (res.size() <= 1) { + return; + } + + // When going from EPSG:4807 (NTF Paris) to EPSG:4171 (RGC93), we get + // EPSG:7811, NTF (Paris) to RGF93 (2), 1 m + // and unknown id, NTF (Paris) to NTF (1) + Inverse of RGF93 to NTF (2), + // 1 m + // both have same PROJ string and extent + // Do not keep the later (that has more steps) as it adds no value. + + std::set<std::string> setPROJPlusExtent; + std::vector<CoordinateOperationNNPtr> resTemp; + for (const auto &op : res) { + auto formatter = io::PROJStringFormatter::create(); + try { + std::string key(op->exportToPROJString(formatter.get())); + bool dummy = false; + auto extentOp = getExtent(op, true, dummy); + if (extentOp) { + const auto &geogElts = extentOp->geographicElements(); + if (geogElts.size() == 1) { + auto bbox = dynamic_cast< + const metadata::GeographicBoundingBox *>( + geogElts[0].get()); + if (bbox) { + double w = bbox->westBoundLongitude(); + double s = bbox->southBoundLatitude(); + double e = bbox->eastBoundLongitude(); + double n = bbox->northBoundLatitude(); + key += "-"; + key += toString(w); + key += "-"; + key += toString(s); + key += "-"; + key += toString(e); + key += "-"; + key += toString(n); + } + } + } + + if (setPROJPlusExtent.find(key) == setPROJPlusExtent.end()) { + resTemp.emplace_back(op); + setPROJPlusExtent.insert(key); + } + } catch (const std::exception &) { + resTemp.emplace_back(op); + } + } + res = std::move(resTemp); + } +}; + +// --------------------------------------------------------------------------- + +/** \brief Filter operations and sort them given context. + * + * If a desired accuracy is specified, only keep operations whose accuracy + * is at least the desired one. + * If an area of interest is specified, only keep operations whose area of + * use include the area of interest. + * Then sort remaining operations by descending area of use, and increasing + * accuracy. + */ +static std::vector<CoordinateOperationNNPtr> +filterAndSort(const std::vector<CoordinateOperationNNPtr> &sourceList, + const CoordinateOperationContextNNPtr &context, + const metadata::ExtentPtr &extent1, + const metadata::ExtentPtr &extent2) { +#ifdef TRACE_CREATE_OPERATIONS + ENTER_FUNCTION(); + logTrace("number of results before filter and sort: " + + toString(static_cast<int>(sourceList.size()))); +#endif + auto resFiltered = + FilterResults(sourceList, context, extent1, extent2, false) + .andSort() + .getRes(); +#ifdef TRACE_CREATE_OPERATIONS + logTrace("number of results after filter and sort: " + + toString(static_cast<int>(resFiltered.size()))); +#endif + return resFiltered; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +// Apply the inverse() method on all elements of the input list +static std::vector<CoordinateOperationNNPtr> +applyInverse(const std::vector<CoordinateOperationNNPtr> &list) { + auto res = list; + for (auto &op : res) { +#ifdef DEBUG + auto opNew = op->inverse(); + assert(opNew->targetCRS()->isEquivalentTo(op->sourceCRS().get())); + assert(opNew->sourceCRS()->isEquivalentTo(op->targetCRS().get())); + op = opNew; +#else + op = op->inverse(); +#endif + } + return res; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +void CoordinateOperationFactory::Private::buildCRSIds( + const crs::CRSNNPtr &crs, Private::Context &context, + std::list<std::pair<std::string, std::string>> &ids) { + const auto &authFactory = context.context->getAuthorityFactory(); + assert(authFactory); + for (const auto &id : crs->identifiers()) { + const auto &authName = *(id->codeSpace()); + const auto &code = id->code(); + if (!authName.empty()) { + const auto tmpAuthFactory = io::AuthorityFactory::create( + authFactory->databaseContext(), authName); + try { + // Consistency check for the ID attached to the object. + // See https://github.com/OSGeo/PROJ/issues/1982 where EPSG:4656 + // is attached to a GeographicCRS whereas it is a ProjectedCRS + if (tmpAuthFactory->createCoordinateReferenceSystem(code) + ->_isEquivalentTo( + crs.get(), + util::IComparable::Criterion:: + EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)) { + ids.emplace_back(authName, code); + } else { + // TODO? log this inconsistency + } + } catch (const std::exception &) { + // TODO? log this inconsistency + } + } + } + if (ids.empty()) { + std::vector<io::AuthorityFactory::ObjectType> allowedObjects; + auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(crs.get()); + if (geogCRS) { + allowedObjects.push_back( + geogCRS->coordinateSystem()->axisList().size() == 2 + ? io::AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS + : io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS); + } else if (dynamic_cast<crs::ProjectedCRS *>(crs.get())) { + allowedObjects.push_back( + io::AuthorityFactory::ObjectType::PROJECTED_CRS); + } else if (dynamic_cast<crs::VerticalCRS *>(crs.get())) { + allowedObjects.push_back( + io::AuthorityFactory::ObjectType::VERTICAL_CRS); + } + if (!allowedObjects.empty()) { + + const std::pair<io::AuthorityFactory::ObjectType, std::string> key( + allowedObjects[0], crs->nameStr()); + auto iter = context.cacheNameToCRS.find(key); + if (iter != context.cacheNameToCRS.end()) { + ids = iter->second; + return; + } + + const auto &authFactoryName = authFactory->getAuthority(); + try { + const auto tmpAuthFactory = io::AuthorityFactory::create( + authFactory->databaseContext(), + (authFactoryName.empty() || authFactoryName == "any") + ? std::string() + : authFactoryName); + + auto matches = tmpAuthFactory->createObjectsFromName( + crs->nameStr(), allowedObjects, false, 2); + if (matches.size() == 1 && + crs->_isEquivalentTo( + matches.front().get(), + util::IComparable::Criterion::EQUIVALENT) && + !matches.front()->identifiers().empty()) { + const auto &tmpIds = matches.front()->identifiers(); + ids.emplace_back(*(tmpIds[0]->codeSpace()), + tmpIds[0]->code()); + } + } catch (const std::exception &) { + } + context.cacheNameToCRS[key] = ids; + } + } +} + +// --------------------------------------------------------------------------- + +static std::vector<std::string> +getCandidateAuthorities(const io::AuthorityFactoryPtr &authFactory, + const std::string &srcAuthName, + const std::string &targetAuthName) { + const auto &authFactoryName = authFactory->getAuthority(); + std::vector<std::string> authorities; + if (authFactoryName == "any") { + authorities.emplace_back(); + } + if (authFactoryName.empty()) { + authorities = authFactory->databaseContext()->getAllowedAuthorities( + srcAuthName, targetAuthName); + if (authorities.empty()) { + authorities.emplace_back(); + } + } else { + authorities.emplace_back(authFactoryName); + } + return authorities; +} + +// --------------------------------------------------------------------------- + +// Look in the authority registry for operations from sourceCRS to targetCRS +std::vector<CoordinateOperationNNPtr> +CoordinateOperationFactory::Private::findOpsInRegistryDirect( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, bool &resNonEmptyBeforeFiltering) { + const auto &authFactory = context.context->getAuthorityFactory(); + assert(authFactory); + +#ifdef TRACE_CREATE_OPERATIONS + ENTER_BLOCK("findOpsInRegistryDirect(" + objectAsStr(sourceCRS.get()) + + " --> " + objectAsStr(targetCRS.get()) + ")"); +#endif + + resNonEmptyBeforeFiltering = false; + std::list<std::pair<std::string, std::string>> sourceIds; + std::list<std::pair<std::string, std::string>> targetIds; + buildCRSIds(sourceCRS, context, sourceIds); + buildCRSIds(targetCRS, context, targetIds); + + const auto gridAvailabilityUse = context.context->getGridAvailabilityUse(); + for (const auto &idSrc : sourceIds) { + const auto &srcAuthName = idSrc.first; + const auto &srcCode = idSrc.second; + for (const auto &idTarget : targetIds) { + const auto &targetAuthName = idTarget.first; + const auto &targetCode = idTarget.second; + + const auto authorities(getCandidateAuthorities( + authFactory, srcAuthName, targetAuthName)); + std::vector<CoordinateOperationNNPtr> res; + for (const auto &authority : authorities) { + const auto authName = + authority == "any" ? std::string() : authority; + const auto tmpAuthFactory = io::AuthorityFactory::create( + authFactory->databaseContext(), authName); + auto resTmp = + tmpAuthFactory->createFromCoordinateReferenceSystemCodes( + srcAuthName, srcCode, targetAuthName, targetCode, + context.context->getUsePROJAlternativeGridNames(), + gridAvailabilityUse == + CoordinateOperationContext:: + GridAvailabilityUse:: + DISCARD_OPERATION_IF_MISSING_GRID || + gridAvailabilityUse == + CoordinateOperationContext:: + GridAvailabilityUse::KNOWN_AVAILABLE, + gridAvailabilityUse == + CoordinateOperationContext::GridAvailabilityUse:: + KNOWN_AVAILABLE, + context.context->getDiscardSuperseded(), true, false, + context.extent1, context.extent2); + res.insert(res.end(), resTmp.begin(), resTmp.end()); + if (authName == "PROJ") { + continue; + } + if (!res.empty()) { + resNonEmptyBeforeFiltering = true; + auto resFiltered = + FilterResults(res, context.context, context.extent1, + context.extent2, false) + .getRes(); +#ifdef TRACE_CREATE_OPERATIONS + logTrace("filtering reduced from " + + toString(static_cast<int>(res.size())) + " to " + + toString(static_cast<int>(resFiltered.size()))); +#endif + return resFiltered; + } + } + } + } + return std::vector<CoordinateOperationNNPtr>(); +} + +// --------------------------------------------------------------------------- + +// Look in the authority registry for operations to targetCRS +std::vector<CoordinateOperationNNPtr> +CoordinateOperationFactory::Private::findOpsInRegistryDirectTo( + const crs::CRSNNPtr &targetCRS, Private::Context &context) { +#ifdef TRACE_CREATE_OPERATIONS + ENTER_BLOCK("findOpsInRegistryDirectTo({any} -->" + + objectAsStr(targetCRS.get()) + ")"); +#endif + + const auto &authFactory = context.context->getAuthorityFactory(); + assert(authFactory); + + std::list<std::pair<std::string, std::string>> ids; + buildCRSIds(targetCRS, context, ids); + + const auto gridAvailabilityUse = context.context->getGridAvailabilityUse(); + for (const auto &id : ids) { + const auto &targetAuthName = id.first; + const auto &targetCode = id.second; + + const auto authorities(getCandidateAuthorities( + authFactory, targetAuthName, targetAuthName)); + for (const auto &authority : authorities) { + const auto tmpAuthFactory = io::AuthorityFactory::create( + authFactory->databaseContext(), + authority == "any" ? std::string() : authority); + auto res = tmpAuthFactory->createFromCoordinateReferenceSystemCodes( + std::string(), std::string(), targetAuthName, targetCode, + context.context->getUsePROJAlternativeGridNames(), + + gridAvailabilityUse == + CoordinateOperationContext::GridAvailabilityUse:: + DISCARD_OPERATION_IF_MISSING_GRID || + gridAvailabilityUse == + CoordinateOperationContext::GridAvailabilityUse:: + KNOWN_AVAILABLE, + gridAvailabilityUse == CoordinateOperationContext:: + GridAvailabilityUse::KNOWN_AVAILABLE, + context.context->getDiscardSuperseded(), true, true, + context.extent1, context.extent2); + if (!res.empty()) { + auto resFiltered = + FilterResults(res, context.context, context.extent1, + context.extent2, false) + .getRes(); +#ifdef TRACE_CREATE_OPERATIONS + logTrace("filtering reduced from " + + toString(static_cast<int>(res.size())) + " to " + + toString(static_cast<int>(resFiltered.size()))); +#endif + return resFiltered; + } + } + } + return std::vector<CoordinateOperationNNPtr>(); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// Look in the authority registry for operations from sourceCRS to targetCRS +// using an intermediate pivot +std::vector<CoordinateOperationNNPtr> +CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, + bool useCreateBetweenGeodeticCRSWithDatumBasedIntermediates) { + +#ifdef TRACE_CREATE_OPERATIONS + ENTER_BLOCK("findsOpsInRegistryWithIntermediate(" + + objectAsStr(sourceCRS.get()) + " --> " + + objectAsStr(targetCRS.get()) + ")"); +#endif + + const auto &authFactory = context.context->getAuthorityFactory(); + assert(authFactory); + + std::list<std::pair<std::string, std::string>> sourceIds; + std::list<std::pair<std::string, std::string>> targetIds; + buildCRSIds(sourceCRS, context, sourceIds); + buildCRSIds(targetCRS, context, targetIds); + + const auto gridAvailabilityUse = context.context->getGridAvailabilityUse(); + for (const auto &idSrc : sourceIds) { + const auto &srcAuthName = idSrc.first; + const auto &srcCode = idSrc.second; + for (const auto &idTarget : targetIds) { + const auto &targetAuthName = idTarget.first; + const auto &targetCode = idTarget.second; + + const auto authorities(getCandidateAuthorities( + authFactory, srcAuthName, targetAuthName)); + assert(!authorities.empty()); + + const auto tmpAuthFactory = io::AuthorityFactory::create( + authFactory->databaseContext(), + (authFactory->getAuthority() == "any" || authorities.size() > 1) + ? std::string() + : authorities.front()); + + std::vector<CoordinateOperationNNPtr> res; + if (useCreateBetweenGeodeticCRSWithDatumBasedIntermediates) { + res = + tmpAuthFactory + ->createBetweenGeodeticCRSWithDatumBasedIntermediates( + sourceCRS, srcAuthName, srcCode, targetCRS, + targetAuthName, targetCode, + context.context->getUsePROJAlternativeGridNames(), + gridAvailabilityUse == + CoordinateOperationContext:: + GridAvailabilityUse:: + DISCARD_OPERATION_IF_MISSING_GRID || + gridAvailabilityUse == + CoordinateOperationContext:: + GridAvailabilityUse::KNOWN_AVAILABLE, + gridAvailabilityUse == + CoordinateOperationContext:: + GridAvailabilityUse::KNOWN_AVAILABLE, + context.context->getDiscardSuperseded(), + authFactory->getAuthority() != "any" && + authorities.size() > 1 + ? authorities + : std::vector<std::string>(), + context.extent1, context.extent2); + } else { + io::AuthorityFactory::ObjectType intermediateObjectType = + io::AuthorityFactory::ObjectType::CRS; + + // If doing GeogCRS --> GeogCRS, only use GeogCRS as + // intermediate CRS + // Avoid weird behavior when doing NAD83 -> NAD83(2011) + // that would go through NAVD88 otherwise. + if (context.context->getIntermediateCRS().empty() && + dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()) && + dynamic_cast<const crs::GeographicCRS *>(targetCRS.get())) { + intermediateObjectType = + io::AuthorityFactory::ObjectType::GEOGRAPHIC_CRS; + } + res = tmpAuthFactory->createFromCRSCodesWithIntermediates( + srcAuthName, srcCode, targetAuthName, targetCode, + context.context->getUsePROJAlternativeGridNames(), + gridAvailabilityUse == + CoordinateOperationContext::GridAvailabilityUse:: + DISCARD_OPERATION_IF_MISSING_GRID || + gridAvailabilityUse == + CoordinateOperationContext::GridAvailabilityUse:: + KNOWN_AVAILABLE, + gridAvailabilityUse == + CoordinateOperationContext::GridAvailabilityUse:: + KNOWN_AVAILABLE, + context.context->getDiscardSuperseded(), + context.context->getIntermediateCRS(), + intermediateObjectType, + authFactory->getAuthority() != "any" && + authorities.size() > 1 + ? authorities + : std::vector<std::string>(), + context.extent1, context.extent2); + } + if (!res.empty()) { + + auto resFiltered = + FilterResults(res, context.context, context.extent1, + context.extent2, false) + .getRes(); +#ifdef TRACE_CREATE_OPERATIONS + logTrace("filtering reduced from " + + toString(static_cast<int>(res.size())) + " to " + + toString(static_cast<int>(resFiltered.size()))); +#endif + return resFiltered; + } + } + } + return std::vector<CoordinateOperationNNPtr>(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static TransformationNNPtr +createBallparkGeographicOffset(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS, + const io::DatabaseContextPtr &dbContext) { + + const crs::GeographicCRS *geogSrc = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); + const crs::GeographicCRS *geogDst = + dynamic_cast<const crs::GeographicCRS *>(targetCRS.get()); + const bool isSameDatum = geogSrc && geogDst && + geogSrc->datumNonNull(dbContext)->_isEquivalentTo( + geogDst->datumNonNull(dbContext).get(), + util::IComparable::Criterion::EQUIVALENT); + + auto name = buildOpName(isSameDatum ? NULL_GEOGRAPHIC_OFFSET + : BALLPARK_GEOGRAPHIC_OFFSET, + sourceCRS, targetCRS); + + const auto &sourceCRSExtent = getExtent(sourceCRS); + const auto &targetCRSExtent = getExtent(targetCRS); + const bool sameExtent = + sourceCRSExtent && targetCRSExtent && + sourceCRSExtent->_isEquivalentTo( + targetCRSExtent.get(), util::IComparable::Criterion::EQUIVALENT); + + util::PropertyMap map; + map.set(common::IdentifiedObject::NAME_KEY, name) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + sameExtent ? NN_NO_CHECK(sourceCRSExtent) + : metadata::Extent::WORLD); + const common::Angle angle0(0); + + std::vector<metadata::PositionalAccuracyNNPtr> accuracies; + if (isSameDatum) { + accuracies.emplace_back(metadata::PositionalAccuracy::create("0")); + } + + if (dynamic_cast<const crs::SingleCRS *>(sourceCRS.get()) + ->coordinateSystem() + ->axisList() + .size() == 3 || + dynamic_cast<const crs::SingleCRS *>(targetCRS.get()) + ->coordinateSystem() + ->axisList() + .size() == 3) { + return Transformation::createGeographic3DOffsets( + map, sourceCRS, targetCRS, angle0, angle0, common::Length(0), + accuracies); + } else { + return Transformation::createGeographic2DOffsets( + map, sourceCRS, targetCRS, angle0, angle0, accuracies); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +struct MyPROJStringExportableGeodToGeod final + : public io::IPROJStringExportable { + crs::GeodeticCRSPtr geodSrc{}; + crs::GeodeticCRSPtr geodDst{}; + + MyPROJStringExportableGeodToGeod(const crs::GeodeticCRSPtr &geodSrcIn, + const crs::GeodeticCRSPtr &geodDstIn) + : geodSrc(geodSrcIn), geodDst(geodDstIn) {} + + ~MyPROJStringExportableGeodToGeod() override; + + void + // cppcheck-suppress functionStatic + _exportToPROJString(io::PROJStringFormatter *formatter) const override { + + formatter->startInversion(); + geodSrc->_exportToPROJString(formatter); + formatter->stopInversion(); + geodDst->_exportToPROJString(formatter); + } +}; + +MyPROJStringExportableGeodToGeod::~MyPROJStringExportableGeodToGeod() = default; + +// --------------------------------------------------------------------------- + +struct MyPROJStringExportableHorizVertical final + : public io::IPROJStringExportable { + CoordinateOperationPtr horizTransform{}; + CoordinateOperationPtr verticalTransform{}; + crs::GeographicCRSPtr geogDst{}; + + MyPROJStringExportableHorizVertical( + const CoordinateOperationPtr &horizTransformIn, + const CoordinateOperationPtr &verticalTransformIn, + const crs::GeographicCRSPtr &geogDstIn) + : horizTransform(horizTransformIn), + verticalTransform(verticalTransformIn), geogDst(geogDstIn) {} + + ~MyPROJStringExportableHorizVertical() override; + + void + // cppcheck-suppress functionStatic + _exportToPROJString(io::PROJStringFormatter *formatter) const override { + + formatter->pushOmitZUnitConversion(); + + horizTransform->_exportToPROJString(formatter); + + formatter->startInversion(); + geogDst->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + formatter->popOmitZUnitConversion(); + + formatter->pushOmitHorizontalConversionInVertTransformation(); + verticalTransform->_exportToPROJString(formatter); + formatter->popOmitHorizontalConversionInVertTransformation(); + + formatter->pushOmitZUnitConversion(); + geogDst->addAngularUnitConvertAndAxisSwap(formatter); + formatter->popOmitZUnitConversion(); + } +}; + +MyPROJStringExportableHorizVertical::~MyPROJStringExportableHorizVertical() = + default; + +// --------------------------------------------------------------------------- + +struct MyPROJStringExportableHorizVerticalHorizPROJBased final + : public io::IPROJStringExportable { + CoordinateOperationPtr opSrcCRSToGeogCRS{}; + CoordinateOperationPtr verticalTransform{}; + CoordinateOperationPtr opGeogCRStoDstCRS{}; + crs::GeographicCRSPtr interpolationGeogCRS{}; + + MyPROJStringExportableHorizVerticalHorizPROJBased( + const CoordinateOperationPtr &opSrcCRSToGeogCRSIn, + const CoordinateOperationPtr &verticalTransformIn, + const CoordinateOperationPtr &opGeogCRStoDstCRSIn, + const crs::GeographicCRSPtr &interpolationGeogCRSIn) + : opSrcCRSToGeogCRS(opSrcCRSToGeogCRSIn), + verticalTransform(verticalTransformIn), + opGeogCRStoDstCRS(opGeogCRStoDstCRSIn), + interpolationGeogCRS(interpolationGeogCRSIn) {} + + ~MyPROJStringExportableHorizVerticalHorizPROJBased() override; + + void + // cppcheck-suppress functionStatic + _exportToPROJString(io::PROJStringFormatter *formatter) const override { + + formatter->pushOmitZUnitConversion(); + + opSrcCRSToGeogCRS->_exportToPROJString(formatter); + + formatter->startInversion(); + interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + formatter->popOmitZUnitConversion(); + + formatter->pushOmitHorizontalConversionInVertTransformation(); + verticalTransform->_exportToPROJString(formatter); + formatter->popOmitHorizontalConversionInVertTransformation(); + + formatter->pushOmitZUnitConversion(); + + interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter); + + opGeogCRStoDstCRS->_exportToPROJString(formatter); + + formatter->popOmitZUnitConversion(); + } +}; + +MyPROJStringExportableHorizVerticalHorizPROJBased:: + ~MyPROJStringExportableHorizVerticalHorizPROJBased() = default; + +//! @endcond + +} // namespace operation +NS_PROJ_END + +#if 0 +namespace dropbox{ namespace oxygen { +template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableGeodToGeod>>::~nn() = default; +template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableHorizVertical>>::~nn() = default; +template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableHorizVerticalHorizPROJBased>>::~nn() = default; +}} +#endif + +NS_PROJ_START +namespace operation { + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +static std::string buildTransfName(const std::string &srcName, + const std::string &targetName) { + std::string name("Transformation from "); + name += srcName; + name += " to "; + name += targetName; + return name; +} + +// --------------------------------------------------------------------------- + +static SingleOperationNNPtr createPROJBased( + const util::PropertyMap &properties, + const io::IPROJStringExportableNNPtr &projExportable, + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const crs::CRSPtr &interpolationCRS, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies, + bool hasBallparkTransformation) { + return util::nn_static_pointer_cast<SingleOperation>( + PROJBasedOperation::create(properties, projExportable, false, sourceCRS, + targetCRS, interpolationCRS, accuracies, + hasBallparkTransformation)); +} + +// --------------------------------------------------------------------------- + +static CoordinateOperationNNPtr +createGeodToGeodPROJBased(const crs::CRSNNPtr &geodSrc, + const crs::CRSNNPtr &geodDst) { + + auto exportable = util::nn_make_shared<MyPROJStringExportableGeodToGeod>( + util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(geodSrc), + util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(geodDst)); + + auto properties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + buildTransfName(geodSrc->nameStr(), geodDst->nameStr())); + return createPROJBased(properties, exportable, geodSrc, geodDst, nullptr, + {}, false); +} + +// --------------------------------------------------------------------------- + +static std::string +getRemarks(const std::vector<operation::CoordinateOperationNNPtr> &ops) { + std::string remarks; + for (const auto &op : ops) { + const auto &opRemarks = op->remarks(); + if (!opRemarks.empty()) { + if (!remarks.empty()) { + remarks += '\n'; + } + + std::string opName(op->nameStr()); + if (starts_with(opName, INVERSE_OF)) { + opName = opName.substr(INVERSE_OF.size()); + } + + remarks += "For "; + remarks += opName; + + const auto &ids = op->identifiers(); + if (!ids.empty()) { + std::string authority(*ids.front()->codeSpace()); + if (starts_with(authority, "INVERSE(") && + authority.back() == ')') { + authority = authority.substr(strlen("INVERSE("), + authority.size() - 1 - + strlen("INVERSE(")); + } + if (starts_with(authority, "DERIVED_FROM(") && + authority.back() == ')') { + authority = authority.substr(strlen("DERIVED_FROM("), + authority.size() - 1 - + strlen("DERIVED_FROM(")); + } + + remarks += " ("; + remarks += authority; + remarks += ':'; + remarks += ids.front()->code(); + remarks += ')'; + } + remarks += ": "; + remarks += opRemarks; + } + } + return remarks; +} + +// --------------------------------------------------------------------------- + +static CoordinateOperationNNPtr createHorizVerticalPROJBased( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const operation::CoordinateOperationNNPtr &horizTransform, + const operation::CoordinateOperationNNPtr &verticalTransform, + bool checkExtent) { + + auto geogDst = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(targetCRS); + assert(geogDst); + + auto exportable = util::nn_make_shared<MyPROJStringExportableHorizVertical>( + horizTransform, verticalTransform, geogDst); + + const bool horizTransformIsNoOp = + starts_with(horizTransform->nameStr(), NULL_GEOGRAPHIC_OFFSET) && + horizTransform->nameStr().find(" + ") == std::string::npos; + if (horizTransformIsNoOp) { + auto properties = util::PropertyMap(); + properties.set(common::IdentifiedObject::NAME_KEY, + verticalTransform->nameStr()); + bool dummy = false; + auto extent = getExtent(verticalTransform, true, dummy); + if (extent) { + properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + NN_NO_CHECK(extent)); + } + const auto &remarks = verticalTransform->remarks(); + if (!remarks.empty()) { + properties.set(common::IdentifiedObject::REMARKS_KEY, remarks); + } + return createPROJBased( + properties, exportable, sourceCRS, targetCRS, nullptr, + verticalTransform->coordinateOperationAccuracies(), + verticalTransform->hasBallparkTransformation()); + } else { + bool emptyIntersection = false; + auto ops = std::vector<CoordinateOperationNNPtr>{horizTransform, + verticalTransform}; + auto extent = getExtent(ops, true, emptyIntersection); + if (checkExtent && emptyIntersection) { + std::string msg( + "empty intersection of area of validity of concatenated " + "operations"); + throw InvalidOperationEmptyIntersection(msg); + } + auto properties = util::PropertyMap(); + properties.set(common::IdentifiedObject::NAME_KEY, + computeConcatenatedName(ops)); + + if (extent) { + properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + NN_NO_CHECK(extent)); + } + + const auto remarks = getRemarks(ops); + if (!remarks.empty()) { + properties.set(common::IdentifiedObject::REMARKS_KEY, remarks); + } + + std::vector<metadata::PositionalAccuracyNNPtr> accuracies; + const double accuracy = getAccuracy(ops); + if (accuracy >= 0.0) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create(toString(accuracy))); + } + + return createPROJBased( + properties, exportable, sourceCRS, targetCRS, nullptr, accuracies, + horizTransform->hasBallparkTransformation() || + verticalTransform->hasBallparkTransformation()); + } +} + +// --------------------------------------------------------------------------- + +static CoordinateOperationNNPtr createHorizVerticalHorizPROJBased( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const operation::CoordinateOperationNNPtr &opSrcCRSToGeogCRS, + const operation::CoordinateOperationNNPtr &verticalTransform, + const operation::CoordinateOperationNNPtr &opGeogCRStoDstCRS, + const crs::GeographicCRSPtr &interpolationGeogCRS, bool checkExtent) { + + auto exportable = + util::nn_make_shared<MyPROJStringExportableHorizVerticalHorizPROJBased>( + opSrcCRSToGeogCRS, verticalTransform, opGeogCRStoDstCRS, + interpolationGeogCRS); + + std::vector<CoordinateOperationNNPtr> ops; + if (!(starts_with(opSrcCRSToGeogCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) && + opSrcCRSToGeogCRS->nameStr().find(" + ") == std::string::npos)) { + ops.emplace_back(opSrcCRSToGeogCRS); + } + ops.emplace_back(verticalTransform); + if (!(starts_with(opGeogCRStoDstCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) && + opGeogCRStoDstCRS->nameStr().find(" + ") == std::string::npos)) { + ops.emplace_back(opGeogCRStoDstCRS); + } + + bool hasBallparkTransformation = false; + for (const auto &op : ops) { + hasBallparkTransformation |= op->hasBallparkTransformation(); + } + bool emptyIntersection = false; + auto extent = getExtent(ops, false, emptyIntersection); + if (checkExtent && emptyIntersection) { + std::string msg( + "empty intersection of area of validity of concatenated " + "operations"); + throw InvalidOperationEmptyIntersection(msg); + } + auto properties = util::PropertyMap(); + properties.set(common::IdentifiedObject::NAME_KEY, + computeConcatenatedName(ops)); + + if (extent) { + properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + NN_NO_CHECK(extent)); + } + + const auto remarks = getRemarks(ops); + if (!remarks.empty()) { + properties.set(common::IdentifiedObject::REMARKS_KEY, remarks); + } + + std::vector<metadata::PositionalAccuracyNNPtr> accuracies; + const double accuracy = getAccuracy(ops); + if (accuracy >= 0.0) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create(toString(accuracy))); + } + + return createPROJBased(properties, exportable, sourceCRS, targetCRS, + nullptr, accuracies, hasBallparkTransformation); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::vector<CoordinateOperationNNPtr> +CoordinateOperationFactory::Private::createOperationsGeogToGeog( + std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS, Private::Context &context, + const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst) { + + assert(sourceCRS.get() == geogSrc); + assert(targetCRS.get() == geogDst); + + const auto &src_pm = geogSrc->primeMeridian()->longitude(); + const auto &dst_pm = geogDst->primeMeridian()->longitude(); + auto offset_pm = + (src_pm.unit() == dst_pm.unit()) + ? common::Angle(src_pm.value() - dst_pm.value(), src_pm.unit()) + : common::Angle( + src_pm.convertToUnit(common::UnitOfMeasure::DEGREE) - + dst_pm.convertToUnit(common::UnitOfMeasure::DEGREE), + common::UnitOfMeasure::DEGREE); + + double vconvSrc = 1.0; + const auto &srcCS = geogSrc->coordinateSystem(); + const auto &srcAxisList = srcCS->axisList(); + if (srcAxisList.size() == 3) { + vconvSrc = srcAxisList[2]->unit().conversionToSI(); + } + double vconvDst = 1.0; + const auto &dstCS = geogDst->coordinateSystem(); + const auto &dstAxisList = dstCS->axisList(); + if (dstAxisList.size() == 3) { + vconvDst = dstAxisList[2]->unit().conversionToSI(); + } + + std::string name(buildTransfName(geogSrc->nameStr(), geogDst->nameStr())); + + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() : nullptr; + + const bool sameDatum = geogSrc->datumNonNull(dbContext)->_isEquivalentTo( + geogDst->datumNonNull(dbContext).get(), + util::IComparable::Criterion::EQUIVALENT); + + // Do the CRS differ by their axis order ? + bool axisReversal2D = false; + bool axisReversal3D = false; + if (!srcCS->_isEquivalentTo(dstCS.get(), + util::IComparable::Criterion::EQUIVALENT)) { + auto srcOrder = srcCS->axisOrder(); + auto dstOrder = dstCS->axisOrder(); + if (((srcOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST || + srcOrder == cs::EllipsoidalCS::AxisOrder:: + LAT_NORTH_LONG_EAST_HEIGHT_UP) && + (dstOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH || + dstOrder == cs::EllipsoidalCS::AxisOrder:: + LONG_EAST_LAT_NORTH_HEIGHT_UP)) || + ((srcOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH || + srcOrder == cs::EllipsoidalCS::AxisOrder:: + LONG_EAST_LAT_NORTH_HEIGHT_UP) && + (dstOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST || + dstOrder == cs::EllipsoidalCS::AxisOrder:: + LAT_NORTH_LONG_EAST_HEIGHT_UP))) { + if (srcAxisList.size() == 3 || dstAxisList.size() == 3) + axisReversal3D = true; + else + axisReversal2D = true; + } + } + + // Do they differ by vertical units ? + if (vconvSrc != vconvDst && + geogSrc->ellipsoid()->_isEquivalentTo( + geogDst->ellipsoid().get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (offset_pm.value() == 0 && !axisReversal2D && !axisReversal3D) { + // If only by vertical units, use a Change of Vertical + // Unit + // transformation + const double factor = vconvSrc / vconvDst; + auto conv = Conversion::createChangeVerticalUnit( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + name), + common::Scale(factor)); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + conv->setHasBallparkTransformation(!sameDatum); + res.push_back(conv); + return res; + } else { + auto op = createGeodToGeodPROJBased(sourceCRS, targetCRS); + op->setHasBallparkTransformation(!sameDatum); + res.emplace_back(op); + return res; + } + } + + // Do the CRS differ only by their axis order ? + if (sameDatum && (axisReversal2D || axisReversal3D)) { + auto conv = Conversion::createAxisOrderReversal(axisReversal3D); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + res.emplace_back(conv); + return res; + } + + std::vector<CoordinateOperationNNPtr> steps; + // If both are geographic and only differ by their prime + // meridian, + // apply a longitude rotation transformation. + if (geogSrc->ellipsoid()->_isEquivalentTo( + geogDst->ellipsoid().get(), + util::IComparable::Criterion::EQUIVALENT) && + src_pm.getSIValue() != dst_pm.getSIValue()) { + + steps.emplace_back(Transformation::createLongitudeRotation( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, name) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + metadata::Extent::WORLD), + sourceCRS, targetCRS, offset_pm)); + // If only the target has a non-zero prime meridian, chain a + // null geographic offset and then the longitude rotation + } else if (src_pm.getSIValue() == 0 && dst_pm.getSIValue() != 0) { + auto datum = datum::GeodeticReferenceFrame::create( + util::PropertyMap(), geogDst->ellipsoid(), + util::optional<std::string>(), geogSrc->primeMeridian()); + std::string interm_crs_name(geogDst->nameStr()); + interm_crs_name += " altered to use prime meridian of "; + interm_crs_name += geogSrc->nameStr(); + auto interm_crs = + util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, interm_crs_name) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + metadata::Extent::WORLD), + datum, dstCS)); + + steps.emplace_back( + createBallparkGeographicOffset(sourceCRS, interm_crs, dbContext)); + + steps.emplace_back(Transformation::createLongitudeRotation( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, + buildTransfName(geogSrc->nameStr(), interm_crs->nameStr())) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + metadata::Extent::WORLD), + interm_crs, targetCRS, offset_pm)); + + } else { + // If the prime meridians are different, chain a longitude + // rotation and the null geographic offset. + if (src_pm.getSIValue() != dst_pm.getSIValue()) { + auto datum = datum::GeodeticReferenceFrame::create( + util::PropertyMap(), geogSrc->ellipsoid(), + util::optional<std::string>(), geogDst->primeMeridian()); + std::string interm_crs_name(geogSrc->nameStr()); + interm_crs_name += " altered to use prime meridian of "; + interm_crs_name += geogDst->nameStr(); + auto interm_crs = util::nn_static_pointer_cast<crs::CRS>( + crs::GeographicCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + interm_crs_name), + datum, srcCS)); + + steps.emplace_back(Transformation::createLongitudeRotation( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, + buildTransfName(geogSrc->nameStr(), + interm_crs->nameStr())) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + metadata::Extent::WORLD), + sourceCRS, interm_crs, offset_pm)); + steps.emplace_back(createBallparkGeographicOffset( + interm_crs, targetCRS, dbContext)); + } else { + steps.emplace_back(createBallparkGeographicOffset( + sourceCRS, targetCRS, dbContext)); + } + } + + auto op = ConcatenatedOperation::createComputeMetadata( + steps, disallowEmptyIntersection); + op->setHasBallparkTransformation(!sameDatum); + res.emplace_back(op); + return res; +} + +// --------------------------------------------------------------------------- + +static bool hasIdentifiers(const CoordinateOperationNNPtr &op) { + if (!op->identifiers().empty()) { + return true; + } + auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get()); + if (concatenated) { + for (const auto &subOp : concatenated->operations()) { + if (hasIdentifiers(subOp)) { + return true; + } + } + } + return false; +} + +// --------------------------------------------------------------------------- + +static std::vector<crs::CRSNNPtr> +findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory, + const crs::GeodeticCRS *crs, + const datum::GeodeticReferenceFrame *datum) { + std::vector<crs::CRSNNPtr> 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<const datum::GeodeticReferenceFrame *>( + match.get())); + } + } + } + return candidates; +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::setCRSs( + CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS) { + co->setCRSs(sourceCRS, targetCRS, nullptr); + + auto invCO = dynamic_cast<InverseCoordinateOperation *>(co); + if (invCO) { + invCO->forwardOperation()->setCRSs(targetCRS, sourceCRS, nullptr); + } + + auto transf = dynamic_cast<Transformation *>(co); + if (transf) { + transf->inverseAsTransformation()->setCRSs(targetCRS, sourceCRS, + nullptr); + } + + auto concat = dynamic_cast<ConcatenatedOperation *>(co); + if (concat) { + auto first = concat->operations().front().get(); + auto &firstTarget(first->targetCRS()); + if (firstTarget) { + setCRSs(first, sourceCRS, NN_NO_CHECK(firstTarget)); + } + auto last = concat->operations().back().get(); + auto &lastSource(last->sourceCRS()); + if (lastSource) { + setCRSs(last, NN_NO_CHECK(lastSource), targetCRS); + } + } +} + +// --------------------------------------------------------------------------- + +static bool hasResultSetOnlyResultsWithPROJStep( + const std::vector<CoordinateOperationNNPtr> &res) { + for (const auto &op : res) { + auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get()); + if (concat) { + bool hasPROJStep = false; + const auto &steps = concat->operations(); + for (const auto &step : steps) { + const auto &ids = step->identifiers(); + if (!ids.empty()) { + const auto &opAuthority = *(ids.front()->codeSpace()); + if (opAuthority == "PROJ" || + opAuthority == "INVERSE(PROJ)" || + opAuthority == "DERIVED_FROM(PROJ)") { + hasPROJStep = true; + break; + } + } + } + if (!hasPROJStep) { + return false; + } + } else { + return false; + } + } + return true; +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsWithDatumPivot( + std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS, const crs::GeodeticCRS *geodSrc, + const crs::GeodeticCRS *geodDst, Private::Context &context) { + +#ifdef TRACE_CREATE_OPERATIONS + ENTER_BLOCK("createOperationsWithDatumPivot(" + + objectAsStr(sourceCRS.get()) + "," + + objectAsStr(targetCRS.get()) + ")"); +#endif + + struct CreateOperationsWithDatumPivotAntiRecursion { + Context &context; + + explicit CreateOperationsWithDatumPivotAntiRecursion(Context &contextIn) + : context(contextIn) { + assert(!context.inCreateOperationsWithDatumPivotAntiRecursion); + context.inCreateOperationsWithDatumPivotAntiRecursion = true; + } + + ~CreateOperationsWithDatumPivotAntiRecursion() { + context.inCreateOperationsWithDatumPivotAntiRecursion = false; + } + }; + CreateOperationsWithDatumPivotAntiRecursion guard(context); + + const auto &authFactory = context.context->getAuthorityFactory(); + const auto &dbContext = authFactory->databaseContext(); + + const auto candidatesSrcGeod(findCandidateGeodCRSForDatum( + authFactory, geodSrc, + geodSrc->datumNonNull(dbContext.as_nullable()).get())); + const auto candidatesDstGeod(findCandidateGeodCRSForDatum( + authFactory, geodDst, + geodDst->datumNonNull(dbContext.as_nullable()).get())); + + const bool sourceAndTargetAre3D = + geodSrc->coordinateSystem()->axisList().size() == 3 && + geodDst->coordinateSystem()->axisList().size() == 3; + + auto createTransformations = [&](const crs::CRSNNPtr &candidateSrcGeod, + const crs::CRSNNPtr &candidateDstGeod, + const CoordinateOperationNNPtr &opFirst, + bool isNullFirst) { + const auto opsSecond = + createOperations(candidateSrcGeod, candidateDstGeod, context); + const auto opsThird = + createOperations(candidateDstGeod, targetCRS, context); + assert(!opsThird.empty()); + + for (auto &opSecond : opsSecond) { + // Check that it is not a transformation synthetized by + // ourselves + if (!hasIdentifiers(opSecond)) { + continue; + } + // And even if it is a referenced transformation, check that + // it is not a trivial one + auto so = dynamic_cast<const SingleOperation *>(opSecond.get()); + if (so && isAxisOrderReversal(so->method()->getEPSGCode())) { + continue; + } + + std::vector<CoordinateOperationNNPtr> subOps; + const bool isNullThird = + isNullTransformation(opsThird[0]->nameStr()); + CoordinateOperationNNPtr opSecondCloned( + (isNullFirst || isNullThird || sourceAndTargetAre3D) + ? opSecond->shallowClone() + : opSecond); + if (isNullFirst || isNullThird) { + if (opSecondCloned->identifiers().size() == 1 && + (*opSecondCloned->identifiers()[0]->codeSpace()) + .find("DERIVED_FROM") == std::string::npos) { + { + util::PropertyMap map; + addModifiedIdentifier(map, opSecondCloned.get(), false, + true); + opSecondCloned->setProperties(map); + } + auto invCO = dynamic_cast<InverseCoordinateOperation *>( + opSecondCloned.get()); + if (invCO) { + auto invCOForward = invCO->forwardOperation().get(); + if (invCOForward->identifiers().size() == 1 && + (*invCOForward->identifiers()[0]->codeSpace()) + .find("DERIVED_FROM") == + std::string::npos) { + util::PropertyMap map; + addModifiedIdentifier(map, invCOForward, false, + true); + invCOForward->setProperties(map); + } + } + } + } + if (sourceAndTargetAre3D) { + opSecondCloned->getPrivate()->use3DHelmert_ = true; + auto invCO = dynamic_cast<InverseCoordinateOperation *>( + opSecondCloned.get()); + if (invCO) { + auto invCOForward = invCO->forwardOperation().get(); + invCOForward->getPrivate()->use3DHelmert_ = true; + } + } + if (isNullFirst) { + auto oldTarget(NN_CHECK_ASSERT(opSecondCloned->targetCRS())); + setCRSs(opSecondCloned.get(), sourceCRS, oldTarget); + } else { + subOps.emplace_back(opFirst); + } + if (isNullThird) { + auto oldSource(NN_CHECK_ASSERT(opSecondCloned->sourceCRS())); + setCRSs(opSecondCloned.get(), oldSource, targetCRS); + subOps.emplace_back(opSecondCloned); + } else { + subOps.emplace_back(opSecondCloned); + subOps.emplace_back(opsThird[0]); + } +#ifdef TRACE_CREATE_OPERATIONS + std::string debugStr; + for (const auto &op : subOps) { + if (!debugStr.empty()) { + debugStr += " + "; + } + debugStr += objectAsStr(op.get()); + debugStr += " ("; + debugStr += objectAsStr(op->sourceCRS().get()); + debugStr += "->"; + debugStr += objectAsStr(op->targetCRS().get()); + debugStr += ")"; + } + logTrace("transformation " + debugStr); +#endif + try { + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + subOps, disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + }; + + // Start in priority with candidates that have exactly the same name as + // the sourcCRS and targetCRS. Typically for the case of init=IGNF:XXXX + + // Transformation from IGNF:NTFP to IGNF:RGF93G, + // using + // NTF geographiques Paris (gr) vers NTF GEOGRAPHIQUES GREENWICH (DMS) + + // NOUVELLE TRIANGULATION DE LA FRANCE (NTF) vers RGF93 (ETRS89) + // that is using ntf_r93.gsb, is horribly dependent + // of IGNF:RGF93G being returned before IGNF:RGF93GEO in candidatesDstGeod. + // If RGF93GEO is returned before then we go through WGS84 and use + // instead a Helmert transformation. + // The below logic is thus quite fragile, and attempts at changing it + // result in degraded results for other use cases... + + for (const auto &candidateSrcGeod : candidatesSrcGeod) { + if (candidateSrcGeod->nameStr() == sourceCRS->nameStr()) { + for (const auto &candidateDstGeod : candidatesDstGeod) { + if (candidateDstGeod->nameStr() == targetCRS->nameStr()) { +#ifdef TRACE_CREATE_OPERATIONS + ENTER_BLOCK("try " + objectAsStr(sourceCRS.get()) + "->" + + objectAsStr(candidateSrcGeod.get()) + "->" + + objectAsStr(candidateDstGeod.get()) + "->" + + objectAsStr(targetCRS.get()) + ")"); +#endif + const auto opsFirst = + createOperations(sourceCRS, candidateSrcGeod, context); + assert(!opsFirst.empty()); + const bool isNullFirst = + isNullTransformation(opsFirst[0]->nameStr()); + createTransformations(candidateSrcGeod, candidateDstGeod, + opsFirst[0], isNullFirst); + if (!res.empty()) { + if (hasResultSetOnlyResultsWithPROJStep(res)) { + continue; + } + return; + } + } + } + } + } + + for (const auto &candidateSrcGeod : candidatesSrcGeod) { + const bool bSameSrcName = + candidateSrcGeod->nameStr() == sourceCRS->nameStr(); +#ifdef TRACE_CREATE_OPERATIONS + ENTER_BLOCK(""); +#endif + const auto opsFirst = + createOperations(sourceCRS, candidateSrcGeod, context); + assert(!opsFirst.empty()); + const bool isNullFirst = isNullTransformation(opsFirst[0]->nameStr()); + + for (const auto &candidateDstGeod : candidatesDstGeod) { + if (bSameSrcName && + candidateDstGeod->nameStr() == targetCRS->nameStr()) { + continue; + } + +#ifdef TRACE_CREATE_OPERATIONS + ENTER_BLOCK("try " + objectAsStr(sourceCRS.get()) + "->" + + objectAsStr(candidateSrcGeod.get()) + "->" + + objectAsStr(candidateDstGeod.get()) + "->" + + objectAsStr(targetCRS.get()) + ")"); +#endif + createTransformations(candidateSrcGeod, candidateDstGeod, + opsFirst[0], isNullFirst); + if (!res.empty() && !hasResultSetOnlyResultsWithPROJStep(res)) { + return; + } + } + } +} + +// --------------------------------------------------------------------------- + +static CoordinateOperationNNPtr +createBallparkGeocentricTranslation(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS) { + std::string name(BALLPARK_GEOCENTRIC_TRANSLATION); + name += " from "; + name += sourceCRS->nameStr(); + name += " to "; + name += targetCRS->nameStr(); + + return util::nn_static_pointer_cast<CoordinateOperation>( + Transformation::createGeocentricTranslations( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, name) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + metadata::Extent::WORLD), + sourceCRS, targetCRS, 0.0, 0.0, 0.0, {})); +} + +// --------------------------------------------------------------------------- + +bool CoordinateOperationFactory::Private::hasPerfectAccuracyResult( + const std::vector<CoordinateOperationNNPtr> &res, const Context &context) { + auto resTmp = FilterResults(res, context.context, context.extent1, + context.extent2, true) + .getRes(); + for (const auto &op : resTmp) { + const double acc = getAccuracy(op); + if (acc == 0.0) { + return true; + } + } + return false; +} + +// --------------------------------------------------------------------------- + +std::vector<CoordinateOperationNNPtr> +CoordinateOperationFactory::Private::createOperations( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context) { + +#ifdef TRACE_CREATE_OPERATIONS + ENTER_BLOCK("createOperations(" + objectAsStr(sourceCRS.get()) + " --> " + + objectAsStr(targetCRS.get()) + ")"); +#endif + + std::vector<CoordinateOperationNNPtr> res; + + auto boundSrc = dynamic_cast<const crs::BoundCRS *>(sourceCRS.get()); + auto boundDst = dynamic_cast<const crs::BoundCRS *>(targetCRS.get()); + + const auto &sourceProj4Ext = boundSrc + ? boundSrc->baseCRS()->getExtensionProj4() + : sourceCRS->getExtensionProj4(); + const auto &targetProj4Ext = boundDst + ? boundDst->baseCRS()->getExtensionProj4() + : targetCRS->getExtensionProj4(); + if (!sourceProj4Ext.empty() || !targetProj4Ext.empty()) { + createOperationsFromProj4Ext(sourceCRS, targetCRS, boundSrc, boundDst, + res); + return res; + } + + auto geodSrc = dynamic_cast<const crs::GeodeticCRS *>(sourceCRS.get()); + auto geodDst = dynamic_cast<const crs::GeodeticCRS *>(targetCRS.get()); + auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); + auto geogDst = dynamic_cast<const crs::GeographicCRS *>(targetCRS.get()); + auto vertSrc = dynamic_cast<const crs::VerticalCRS *>(sourceCRS.get()); + auto vertDst = dynamic_cast<const crs::VerticalCRS *>(targetCRS.get()); + + // First look-up if the registry provide us with operations. + auto derivedSrc = dynamic_cast<const crs::DerivedCRS *>(sourceCRS.get()); + auto derivedDst = dynamic_cast<const crs::DerivedCRS *>(targetCRS.get()); + const auto &authFactory = context.context->getAuthorityFactory(); + if (authFactory && + (derivedSrc == nullptr || + !derivedSrc->baseCRS()->_isEquivalentTo( + targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) && + (derivedDst == nullptr || + !derivedDst->baseCRS()->_isEquivalentTo( + sourceCRS.get(), util::IComparable::Criterion::EQUIVALENT))) { + + if (createOperationsFromDatabase(sourceCRS, targetCRS, context, geodSrc, + geodDst, geogSrc, geogDst, vertSrc, + vertDst, res)) { + return res; + } + } + + // Special case if both CRS are geodetic + if (geodSrc && geodDst && !derivedSrc && !derivedDst) { + createOperationsGeodToGeod(sourceCRS, targetCRS, context, geodSrc, + geodDst, res); + return res; + } + + // If the source is a derived CRS, then chain the inverse of its + // deriving conversion, with transforms from its baseCRS to the + // targetCRS + if (derivedSrc) { + createOperationsDerivedTo(sourceCRS, targetCRS, context, derivedSrc, + res); + return res; + } + + // reverse of previous case + if (derivedDst) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + // Order of comparison between the geogDst vs geodDst is impotant + if (boundSrc && geogDst) { + createOperationsBoundToGeog(sourceCRS, targetCRS, context, boundSrc, + geogDst, res); + return res; + } else if (boundSrc && geodDst) { + createOperationsToGeod(sourceCRS, targetCRS, context, geodDst, res); + return res; + } + + // reverse of previous case + if (geodSrc && boundDst) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + // vertCRS (as boundCRS with transformation to target vertCRS) to + // vertCRS + if (boundSrc && vertDst) { + createOperationsBoundToVert(sourceCRS, targetCRS, context, boundSrc, + vertDst, res); + return res; + } + + // reverse of previous case + if (boundDst && vertSrc) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + if (vertSrc && vertDst) { + createOperationsVertToVert(sourceCRS, targetCRS, context, vertSrc, + vertDst, res); + return res; + } + + // A bit odd case as we are comparing apples to oranges, but in case + // the vertical unit differ, do something useful. + if (vertSrc && geogDst) { + createOperationsVertToGeog(sourceCRS, targetCRS, context, vertSrc, + geogDst, res); + return res; + } + + // reverse of previous case + if (vertDst && geogSrc) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + // boundCRS to boundCRS + if (boundSrc && boundDst) { + createOperationsBoundToBound(sourceCRS, targetCRS, context, boundSrc, + boundDst, res); + return res; + } + + auto compoundSrc = dynamic_cast<crs::CompoundCRS *>(sourceCRS.get()); + // Order of comparison between the geogDst vs geodDst is impotant + if (compoundSrc && geogDst) { + createOperationsCompoundToGeog(sourceCRS, targetCRS, context, + compoundSrc, geogDst, res); + return res; + } else if (compoundSrc && geodDst) { + createOperationsToGeod(sourceCRS, targetCRS, context, geodDst, res); + return res; + } + + // reverse of previous cases + auto compoundDst = dynamic_cast<const crs::CompoundCRS *>(targetCRS.get()); + if (geodSrc && compoundDst) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + if (compoundSrc && compoundDst) { + createOperationsCompoundToCompound(sourceCRS, targetCRS, context, + compoundSrc, compoundDst, res); + return res; + } + + // '+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +type=crs' to + // '+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@bar.gtx + // +type=crs' + if (boundSrc && compoundDst) { + createOperationsBoundToCompound(sourceCRS, targetCRS, context, boundSrc, + compoundDst, res); + return res; + } + + // reverse of previous case + if (boundDst && compoundSrc) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + return res; +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsFromProj4Ext( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const crs::BoundCRS *boundSrc, const crs::BoundCRS *boundDst, + std::vector<CoordinateOperationNNPtr> &res) { + + ENTER_FUNCTION(); + + auto sourceProjExportable = dynamic_cast<const io::IPROJStringExportable *>( + boundSrc ? boundSrc : sourceCRS.get()); + auto targetProjExportable = dynamic_cast<const io::IPROJStringExportable *>( + boundDst ? boundDst : targetCRS.get()); + if (!sourceProjExportable) { + throw InvalidOperation("Source CRS is not PROJ exportable"); + } + if (!targetProjExportable) { + throw InvalidOperation("Target CRS is not PROJ exportable"); + } + auto projFormatter = io::PROJStringFormatter::create(); + projFormatter->setCRSExport(true); + projFormatter->setLegacyCRSToCRSContext(true); + projFormatter->startInversion(); + sourceProjExportable->_exportToPROJString(projFormatter.get()); + auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); + if (geogSrc) { + auto tmpFormatter = io::PROJStringFormatter::create(); + geogSrc->addAngularUnitConvertAndAxisSwap(tmpFormatter.get()); + projFormatter->ingestPROJString(tmpFormatter->toString()); + } + + projFormatter->stopInversion(); + + targetProjExportable->_exportToPROJString(projFormatter.get()); + auto geogDst = dynamic_cast<const crs::GeographicCRS *>(targetCRS.get()); + if (geogDst) { + auto tmpFormatter = io::PROJStringFormatter::create(); + geogDst->addAngularUnitConvertAndAxisSwap(tmpFormatter.get()); + projFormatter->ingestPROJString(tmpFormatter->toString()); + } + + const auto PROJString = projFormatter->toString(); + auto properties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr())); + res.emplace_back(SingleOperation::createPROJBased( + properties, PROJString, sourceCRS, targetCRS, {})); +} + +// --------------------------------------------------------------------------- + +bool CoordinateOperationFactory::Private::createOperationsFromDatabase( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::GeodeticCRS *geodSrc, + const crs::GeodeticCRS *geodDst, const crs::GeographicCRS *geogSrc, + const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc, + const crs::VerticalCRS *vertDst, + std::vector<CoordinateOperationNNPtr> &res) { + + ENTER_FUNCTION(); + + if (geogSrc && vertDst) { + createOperationsFromDatabase(targetCRS, sourceCRS, context, geodDst, + geodSrc, geogDst, geogSrc, vertDst, + vertSrc, res); + res = applyInverse(res); + } else if (geogDst && vertSrc) { + res = applyInverse(createOperationsGeogToVertFromGeoid( + targetCRS, sourceCRS, vertSrc, context)); + if (!res.empty()) { + createOperationsVertToGeogBallpark(sourceCRS, targetCRS, context, + vertSrc, geogDst, res); + } + } + + if (!res.empty()) { + return true; + } + + bool resFindDirectNonEmptyBeforeFiltering = false; + res = findOpsInRegistryDirect(sourceCRS, targetCRS, context, + resFindDirectNonEmptyBeforeFiltering); + + // If we get at least a result with perfect accuracy, do not + // bother generating synthetic transforms. + if (hasPerfectAccuracyResult(res, context)) { + return true; + } + + bool doFilterAndCheckPerfectOp = false; + + bool sameGeodeticDatum = false; + + if (vertSrc || vertDst) { + if (res.empty()) { + if (geogSrc && + geogSrc->coordinateSystem()->axisList().size() == 2 && + vertDst) { + auto dbContext = + context.context->getAuthorityFactory()->databaseContext(); + auto resTmp = findOpsInRegistryDirect( + sourceCRS->promoteTo3D(std::string(), dbContext), targetCRS, + context, resFindDirectNonEmptyBeforeFiltering); + for (auto &op : resTmp) { + auto newOp = op->shallowClone(); + setCRSs(newOp.get(), sourceCRS, targetCRS); + res.emplace_back(newOp); + } + } else if (geogDst && + geogDst->coordinateSystem()->axisList().size() == 2 && + vertSrc) { + auto dbContext = + context.context->getAuthorityFactory()->databaseContext(); + auto resTmp = findOpsInRegistryDirect( + sourceCRS, targetCRS->promoteTo3D(std::string(), dbContext), + context, resFindDirectNonEmptyBeforeFiltering); + for (auto &op : resTmp) { + auto newOp = op->shallowClone(); + setCRSs(newOp.get(), sourceCRS, targetCRS); + res.emplace_back(newOp); + } + } + } + if (res.empty()) { + createOperationsFromDatabaseWithVertCRS(sourceCRS, targetCRS, + context, geogSrc, geogDst, + vertSrc, vertDst, res); + } + } else if (geodSrc && geodDst) { + + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = authFactory->databaseContext().as_nullable(); + + const auto srcDatum = geodSrc->datumNonNull(dbContext); + const auto dstDatum = geodDst->datumNonNull(dbContext); + sameGeodeticDatum = srcDatum->_isEquivalentTo( + dstDatum.get(), util::IComparable::Criterion::EQUIVALENT); + + if (res.empty() && !sameGeodeticDatum && + !context.inCreateOperationsWithDatumPivotAntiRecursion) { + // If we still didn't find a transformation, and that the source + // and target are GeodeticCRS, then go through their underlying + // datum to find potential transformations between other + // GeodeticCRSs + // that are made of those datum + // The typical example is if transforming between two + // GeographicCRS, + // but transformations are only available between their + // corresponding geocentric CRS. + createOperationsWithDatumPivot(res, sourceCRS, targetCRS, geodSrc, + geodDst, context); + doFilterAndCheckPerfectOp = !res.empty(); + } + } + + bool foundInstantiableOp = false; + // FIXME: the limitation to .size() == 1 is just for the + // -s EPSG:4959+5759 -t "EPSG:4959+7839" case + // finding EPSG:7860 'NZVD2016 height to Auckland 1946 + // height (1)', which uses the EPSG:1071 'Vertical Offset by Grid + // Interpolation (NZLVD)' method which is not currently implemented by PROJ + // (cannot deal with .csv files) + // Initially the test was written to iterate over for all operations of a + // non-empty res, but this causes failures in the test suite when no grids + // are installed at all. Ideally we should tweak the test suite to be + // robust to that, or skip some tests. + if (res.size() == 1) { + try { + res.front()->exportToPROJString( + io::PROJStringFormatter::create().get()); + foundInstantiableOp = true; + } catch (const std::exception &) { + } + if (!foundInstantiableOp) { + resFindDirectNonEmptyBeforeFiltering = false; + } + } else if (res.size() > 1) { + foundInstantiableOp = true; + } + + // NAD27 to NAD83 has tens of results already. No need to look + // for a pivot + if (!sameGeodeticDatum && + (((res.empty() || !foundInstantiableOp) && + !resFindDirectNonEmptyBeforeFiltering && + context.context->getAllowUseIntermediateCRS() == + CoordinateOperationContext::IntermediateCRSUse:: + IF_NO_DIRECT_TRANSFORMATION) || + context.context->getAllowUseIntermediateCRS() == + CoordinateOperationContext::IntermediateCRSUse::ALWAYS || + getenv("PROJ_FORCE_SEARCH_PIVOT"))) { + auto resWithIntermediate = findsOpsInRegistryWithIntermediate( + sourceCRS, targetCRS, context, false); + res.insert(res.end(), resWithIntermediate.begin(), + resWithIntermediate.end()); + doFilterAndCheckPerfectOp = !res.empty(); + + } else if (!context.inCreateOperationsWithDatumPivotAntiRecursion && + !resFindDirectNonEmptyBeforeFiltering && geodSrc && geodDst && + !sameGeodeticDatum && + context.context->getIntermediateCRS().empty() && + context.context->getAllowUseIntermediateCRS() != + CoordinateOperationContext::IntermediateCRSUse::NEVER) { + + bool tryWithGeodeticDatumIntermediate = res.empty(); + if (!tryWithGeodeticDatumIntermediate) { + // This is in particular for the GDA94 to WGS 84 (G1762) case + // As we have a WGS 84 -> WGS 84 (G1762) null-transformation in the + // PROJ authority, previous steps might have use that WGS 84 + // intermediate directly. They might also have generated a path + // through ITRF2008, as there is a path + // GDA94 (geoc.) -> ITRF2008 (geoc.) -> WGS84 (G1762) (geoc.) + // But there's a better path using + // GDA94 (geog.) --> GDA2020 (geog.) and + // GDA2020 (geoc.) -> WGS84 (G1762) (geoc.) that requires to + // explore intermediates through their datum, and not directly + // trough the CRS code. + // Do that only if the number of results we got through other + // algorithms is small, or if all results we have go through an + // operation in the PROJ authority. + constexpr size_t ARBITRARY_SMALL_NUMBER = 5U; + tryWithGeodeticDatumIntermediate = + res.size() < ARBITRARY_SMALL_NUMBER || + hasResultSetOnlyResultsWithPROJStep(res); + } + if (tryWithGeodeticDatumIntermediate) { + auto resWithIntermediate = findsOpsInRegistryWithIntermediate( + sourceCRS, targetCRS, context, true); + res.insert(res.end(), resWithIntermediate.begin(), + resWithIntermediate.end()); + doFilterAndCheckPerfectOp = !res.empty(); + } + } + + if (doFilterAndCheckPerfectOp) { + // If we get at least a result with perfect accuracy, do not bother + // generating synthetic transforms. + if (hasPerfectAccuracyResult(res, context)) { + return true; + } + } + return false; +} + +// --------------------------------------------------------------------------- + +static std::vector<crs::CRSNNPtr> +findCandidateVertCRSForDatum(const io::AuthorityFactoryPtr &authFactory, + const datum::VerticalReferenceFrame *datum) { + std::vector<crs::CRSNNPtr> 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()) { + auto l_candidates = + authFactory->createVerticalCRSFromDatum(authName, code); + for (const auto &candidate : l_candidates) { + candidates.emplace_back(candidate); + } + } + } + } else if (datumName != "unknown" && datumName != "unnamed") { + auto matches = authFactory->createObjectsFromName( + datumName, + {io::AuthorityFactory::ObjectType::VERTICAL_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 findCandidateVertCRSForDatum( + authFactory, + dynamic_cast<const datum::VerticalReferenceFrame *>( + match.get())); + } + } + } + return candidates; +} + +// --------------------------------------------------------------------------- + +std::vector<CoordinateOperationNNPtr> +CoordinateOperationFactory::Private::createOperationsGeogToVertFromGeoid( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const crs::VerticalCRS *vertDst, Private::Context &context) { + + ENTER_FUNCTION(); + + const auto useTransf = [&targetCRS, &context, + vertDst](const CoordinateOperationNNPtr &op) { + const auto targetOp = + dynamic_cast<const crs::VerticalCRS *>(op->targetCRS().get()); + assert(targetOp); + if (targetOp->_isEquivalentTo( + vertDst, util::IComparable::Criterion::EQUIVALENT)) { + return op; + } + std::vector<CoordinateOperationNNPtr> tmp; + createOperationsVertToVert(NN_NO_CHECK(op->targetCRS()), targetCRS, + context, targetOp, vertDst, tmp); + assert(!tmp.empty()); + auto ret = ConcatenatedOperation::createComputeMetadata( + {op, tmp.front()}, disallowEmptyIntersection); + return ret; + }; + + const auto getProjGeoidTransformation = [&sourceCRS, &targetCRS, &vertDst, + &context]( + const CoordinateOperationNNPtr &model, + const std::string &projFilename) { + + const auto getNameVertCRSMetre = [](const std::string &name) { + if (name.empty()) + return std::string("unnamed"); + auto ret(name); + bool haveOriginalUnit = false; + if (name.back() == ')') { + const auto pos = ret.rfind(" ("); + if (pos != std::string::npos) { + haveOriginalUnit = true; + ret = ret.substr(0, pos); + } + } + const auto pos = ret.rfind(" depth"); + if (pos != std::string::npos) { + ret = ret.substr(0, pos) + " height"; + } + if (!haveOriginalUnit) { + ret += " (metre)"; + } + return ret; + }; + + const auto &axis = vertDst->coordinateSystem()->axisList()[0]; + const auto geogSrcCRS = + dynamic_cast<crs::GeographicCRS *>(model->interpolationCRS().get()) + ? NN_NO_CHECK(model->interpolationCRS()) + : sourceCRS; + const auto vertCRSMetre = + axis->unit() == common::UnitOfMeasure::METRE && + axis->direction() == cs::AxisDirection::UP + ? targetCRS + : util::nn_static_pointer_cast<crs::CRS>( + crs::VerticalCRS::create( + util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + getNameVertCRSMetre(targetCRS->nameStr())), + vertDst->datum(), vertDst->datumEnsemble(), + cs::VerticalCS::createGravityRelatedHeight( + common::UnitOfMeasure::METRE))); + const auto properties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + buildOpName("Transformation", vertCRSMetre, geogSrcCRS)); + + // Try to find a representative value for the accuracy of this grid + // from the registered transformations. + std::vector<metadata::PositionalAccuracyNNPtr> accuracies; + const auto &modelAccuracies = model->coordinateOperationAccuracies(); + if (modelAccuracies.empty()) { + const auto &authFactory = context.context->getAuthorityFactory(); + if (authFactory) { + const auto transformationsForGrid = + io::DatabaseContext::getTransformationsForGridName( + authFactory->databaseContext(), projFilename); + double accuracy = -1; + for (const auto &transf : transformationsForGrid) { + accuracy = std::max(accuracy, getAccuracy(transf)); + } + if (accuracy >= 0) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create( + toString(accuracy))); + } + } + } + + return Transformation::createGravityRelatedHeightToGeographic3D( + properties, vertCRSMetre, geogSrcCRS, nullptr, projFilename, + !modelAccuracies.empty() ? modelAccuracies : accuracies); + }; + + std::vector<CoordinateOperationNNPtr> res; + const auto &authFactory = context.context->getAuthorityFactory(); + if (authFactory) { + const auto &models = vertDst->geoidModel(); + for (const auto &model : models) { + const auto &modelName = model->nameStr(); + const auto transformations = + starts_with(modelName, "PROJ ") + ? std::vector< + CoordinateOperationNNPtr>{getProjGeoidTransformation( + model, modelName.substr(strlen("PROJ ")))} + : authFactory->getTransformationsForGeoid( + modelName, + context.context->getUsePROJAlternativeGridNames()); + for (const auto &transf : transformations) { + if (dynamic_cast<crs::GeographicCRS *>( + transf->sourceCRS().get()) && + dynamic_cast<crs::VerticalCRS *>( + transf->targetCRS().get())) { + res.push_back(useTransf(transf)); + } else if (dynamic_cast<crs::GeographicCRS *>( + transf->targetCRS().get()) && + dynamic_cast<crs::VerticalCRS *>( + transf->sourceCRS().get())) { + res.push_back(useTransf(transf->inverse())); + } + } + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private:: + createOperationsGeogToVertWithIntermediateVert( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const crs::VerticalCRS *vertDst, Private::Context &context) { + + ENTER_FUNCTION(); + + std::vector<CoordinateOperationNNPtr> res; + + struct AntiRecursionGuard { + Context &context; + + explicit AntiRecursionGuard(Context &contextIn) : context(contextIn) { + assert(!context.inCreateOperationsGeogToVertWithIntermediateVert); + context.inCreateOperationsGeogToVertWithIntermediateVert = true; + } + + ~AntiRecursionGuard() { + context.inCreateOperationsGeogToVertWithIntermediateVert = false; + } + }; + AntiRecursionGuard guard(context); + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = authFactory->databaseContext().as_nullable(); + + auto candidatesVert = findCandidateVertCRSForDatum( + authFactory, vertDst->datumNonNull(dbContext).get()); + for (const auto &candidateVert : candidatesVert) { + auto resTmp = createOperations(sourceCRS, candidateVert, context); + if (!resTmp.empty()) { + const auto opsSecond = + createOperations(candidateVert, targetCRS, context); + if (!opsSecond.empty()) { + // The transformation from candidateVert to targetCRS should + // be just a unit change typically, so take only the first one, + // which is likely/hopefully the only one. + for (const auto &opFirst : resTmp) { + if (hasIdentifiers(opFirst)) { + if (candidateVert->_isEquivalentTo( + targetCRS.get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(opFirst); + } else { + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + {opFirst, opsSecond.front()}, + disallowEmptyIntersection)); + } + } + } + if (!res.empty()) + break; + } + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private:: + createOperationsGeogToVertWithAlternativeGeog( + const crs::CRSNNPtr & /*sourceCRS*/, // geographic CRS + const crs::CRSNNPtr &targetCRS, // vertical CRS + Private::Context &context) { + + ENTER_FUNCTION(); + + std::vector<CoordinateOperationNNPtr> res; + + struct AntiRecursionGuard { + Context &context; + + explicit AntiRecursionGuard(Context &contextIn) : context(contextIn) { + assert(!context.inCreateOperationsGeogToVertWithAlternativeGeog); + context.inCreateOperationsGeogToVertWithAlternativeGeog = true; + } + + ~AntiRecursionGuard() { + context.inCreateOperationsGeogToVertWithAlternativeGeog = false; + } + }; + AntiRecursionGuard guard(context); + + // Generally EPSG has operations from GeogCrs to VertCRS + auto ops = findOpsInRegistryDirectTo(targetCRS, context); + + for (const auto &op : ops) { + const auto tmpCRS = op->sourceCRS(); + if (tmpCRS && dynamic_cast<const crs::GeographicCRS *>(tmpCRS.get())) { + res.emplace_back(op); + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private:: + createOperationsFromDatabaseWithVertCRS( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::GeographicCRS *geogSrc, + const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc, + const crs::VerticalCRS *vertDst, + std::vector<CoordinateOperationNNPtr> &res) { + + // Typically to transform from "NAVD88 height (ftUS)" to a geog CRS + // by using transformations of "NAVD88 height" (metre) to that geog CRS + if (res.empty() && + !context.inCreateOperationsGeogToVertWithIntermediateVert && geogSrc && + vertDst) { + res = createOperationsGeogToVertWithIntermediateVert( + sourceCRS, targetCRS, vertDst, context); + } else if (res.empty() && + !context.inCreateOperationsGeogToVertWithIntermediateVert && + geogDst && vertSrc) { + res = applyInverse(createOperationsGeogToVertWithIntermediateVert( + targetCRS, sourceCRS, vertSrc, context)); + } + + // NAD83 only exists in 2D version in EPSG, so if it has been + // promoted to 3D, when researching a vertical to geog + // transformation, try to down cast to 2D. + const auto geog3DToVertTryThroughGeog2D = [&res, &context]( + const crs::GeographicCRS *geogSrcIn, const crs::VerticalCRS *vertDstIn, + const crs::CRSNNPtr &targetCRSIn) { + if (res.empty() && geogSrcIn && vertDstIn && + geogSrcIn->coordinateSystem()->axisList().size() == 3) { + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() + : nullptr; + const auto candidatesSrcGeod(findCandidateGeodCRSForDatum( + authFactory, geogSrcIn, + geogSrcIn->datumNonNull(dbContext).get())); + for (const auto &candidate : candidatesSrcGeod) { + auto geogCandidate = + util::nn_dynamic_pointer_cast<crs::GeographicCRS>( + candidate); + if (geogCandidate && + geogCandidate->coordinateSystem()->axisList().size() == 2) { + bool ignored; + res = + findOpsInRegistryDirect(NN_NO_CHECK(geogCandidate), + targetCRSIn, context, ignored); + break; + } + } + return true; + } + return false; + }; + + if (geog3DToVertTryThroughGeog2D(geogSrc, vertDst, targetCRS)) { + // do nothing + } else if (geog3DToVertTryThroughGeog2D(geogDst, vertSrc, sourceCRS)) { + res = applyInverse(res); + } + + // There's no direct transformation from NAVD88 height to WGS84, + // so try to research all transformations from NAVD88 to another + // intermediate GeographicCRS. + if (res.empty() && + !context.inCreateOperationsGeogToVertWithAlternativeGeog && geogSrc && + vertDst) { + res = createOperationsGeogToVertWithAlternativeGeog(sourceCRS, + targetCRS, context); + } else if (res.empty() && + !context.inCreateOperationsGeogToVertWithAlternativeGeog && + geogDst && vertSrc) { + res = applyInverse(createOperationsGeogToVertWithAlternativeGeog( + targetCRS, sourceCRS, context)); + } +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsGeodToGeod( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::GeodeticCRS *geodSrc, + const crs::GeodeticCRS *geodDst, + std::vector<CoordinateOperationNNPtr> &res) { + + ENTER_FUNCTION(); + + if (geodSrc->ellipsoid()->celestialBody() != + geodDst->ellipsoid()->celestialBody()) { + throw util::UnsupportedOperationException( + "Source and target ellipsoid do not belong to the same " + "celestial body"); + } + + auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(geodSrc); + auto geogDst = dynamic_cast<const crs::GeographicCRS *>(geodDst); + + if (geogSrc && geogDst) { + createOperationsGeogToGeog(res, sourceCRS, targetCRS, context, geogSrc, + geogDst); + return; + } + + const bool isSrcGeocentric = geodSrc->isGeocentric(); + const bool isSrcGeographic = geogSrc != nullptr; + const bool isTargetGeocentric = geodDst->isGeocentric(); + const bool isTargetGeographic = geogDst != nullptr; + + const auto IsSameDatum = [&context, &geodSrc, &geodDst]() { + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() + : nullptr; + + return geodSrc->datumNonNull(dbContext)->_isEquivalentTo( + geodDst->datumNonNull(dbContext).get(), + util::IComparable::Criterion::EQUIVALENT); + }; + + if (((isSrcGeocentric && isTargetGeographic) || + (isSrcGeographic && isTargetGeocentric))) { + + // Same datum ? + if (IsSameDatum()) { + res.emplace_back( + Conversion::createGeographicGeocentric(sourceCRS, targetCRS)); + } else if (isSrcGeocentric && geogDst) { + std::string interm_crs_name(geogDst->nameStr()); + interm_crs_name += " (geocentric)"; + auto interm_crs = + util::nn_static_pointer_cast<crs::CRS>(crs::GeodeticCRS::create( + addDomains(util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + interm_crs_name), + geogDst), + geogDst->datum(), geogDst->datumEnsemble(), + NN_CHECK_ASSERT( + util::nn_dynamic_pointer_cast<cs::CartesianCS>( + geodSrc->coordinateSystem())))); + auto opFirst = + createBallparkGeocentricTranslation(sourceCRS, interm_crs); + auto opSecond = + Conversion::createGeographicGeocentric(interm_crs, targetCRS); + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + {opFirst, opSecond}, disallowEmptyIntersection)); + } else { + // Apply previous case in reverse way + std::vector<CoordinateOperationNNPtr> resTmp; + createOperationsGeodToGeod(targetCRS, sourceCRS, context, geodDst, + geodSrc, resTmp); + assert(resTmp.size() == 1); + res.emplace_back(resTmp.front()->inverse()); + } + + return; + } + + if (isSrcGeocentric && isTargetGeocentric) { + if (sourceCRS->_isEquivalentTo( + targetCRS.get(), util::IComparable::Criterion::EQUIVALENT) || + IsSameDatum()) { + std::string name(NULL_GEOCENTRIC_TRANSLATION); + name += " from "; + name += sourceCRS->nameStr(); + name += " to "; + name += targetCRS->nameStr(); + res.emplace_back(Transformation::createGeocentricTranslations( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, name) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + metadata::Extent::WORLD), + sourceCRS, targetCRS, 0.0, 0.0, 0.0, + {metadata::PositionalAccuracy::create("0")})); + } else { + res.emplace_back( + createBallparkGeocentricTranslation(sourceCRS, targetCRS)); + } + return; + } + + // Transformation between two geodetic systems of unknown type + // This should normally not be triggered with "standard" CRS + res.emplace_back(createGeodToGeodPROJBased(sourceCRS, targetCRS)); +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsDerivedTo( + const crs::CRSNNPtr & /*sourceCRS*/, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::DerivedCRS *derivedSrc, + std::vector<CoordinateOperationNNPtr> &res) { + + ENTER_FUNCTION(); + + auto opFirst = derivedSrc->derivingConversion()->inverse(); + // Small optimization if the targetCRS is the baseCRS of the source + // derivedCRS. + if (derivedSrc->baseCRS()->_isEquivalentTo( + targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(opFirst); + return; + } + auto opsSecond = + createOperations(derivedSrc->baseCRS(), targetCRS, context); + for (const auto &opSecond : opsSecond) { + try { + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + {opFirst, opSecond}, disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsBoundToGeog( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::BoundCRS *boundSrc, + const crs::GeographicCRS *geogDst, + std::vector<CoordinateOperationNNPtr> &res) { + + ENTER_FUNCTION(); + + const auto &hubSrc = boundSrc->hubCRS(); + auto hubSrcGeog = dynamic_cast<const crs::GeographicCRS *>(hubSrc.get()); + auto geogCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractGeographicCRS(); + { + // If geogCRSOfBaseOfBoundSrc is a DerivedGeographicCRS, use its base + // instead (if it is a GeographicCRS) + auto derivedGeogCRS = + std::dynamic_pointer_cast<crs::DerivedGeographicCRS>( + geogCRSOfBaseOfBoundSrc); + if (derivedGeogCRS) { + auto baseCRS = std::dynamic_pointer_cast<crs::GeographicCRS>( + derivedGeogCRS->baseCRS().as_nullable()); + if (baseCRS) { + geogCRSOfBaseOfBoundSrc = baseCRS; + } + } + } + + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() : nullptr; + + const auto geogDstDatum = geogDst->datumNonNull(dbContext); + + // If the underlying datum of the source is the same as the target, do + // not consider the boundCRS at all, but just its base + if (geogCRSOfBaseOfBoundSrc) { + auto geogCRSOfBaseOfBoundSrcDatum = + geogCRSOfBaseOfBoundSrc->datumNonNull(dbContext); + if (geogCRSOfBaseOfBoundSrcDatum->_isEquivalentTo( + geogDstDatum.get(), util::IComparable::Criterion::EQUIVALENT)) { + res = createOperations(boundSrc->baseCRS(), targetCRS, context); + return; + } + } + + bool triedBoundCrsToGeogCRSSameAsHubCRS = false; + // Is it: boundCRS to a geogCRS that is the same as the hubCRS ? + if (hubSrcGeog && geogCRSOfBaseOfBoundSrc && + (hubSrcGeog->_isEquivalentTo( + geogDst, util::IComparable::Criterion::EQUIVALENT) || + hubSrcGeog->is2DPartOf3D(NN_NO_CHECK(geogDst), dbContext))) { + triedBoundCrsToGeogCRSSameAsHubCRS = true; + + CoordinateOperationPtr opIntermediate; + if (!geogCRSOfBaseOfBoundSrc->_isEquivalentTo( + boundSrc->transformation()->sourceCRS().get(), + util::IComparable::Criterion::EQUIVALENT)) { + auto opsIntermediate = createOperations( + NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), + boundSrc->transformation()->sourceCRS(), context); + assert(!opsIntermediate.empty()); + opIntermediate = opsIntermediate.front(); + } + + if (boundSrc->baseCRS() == geogCRSOfBaseOfBoundSrc) { + if (opIntermediate) { + try { + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + {NN_NO_CHECK(opIntermediate), + boundSrc->transformation()}, + disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } else { + // Optimization to avoid creating a useless concatenated + // operation + res.emplace_back(boundSrc->transformation()); + } + return; + } + auto opsFirst = createOperations( + boundSrc->baseCRS(), NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context); + if (!opsFirst.empty()) { + for (const auto &opFirst : opsFirst) { + try { + std::vector<CoordinateOperationNNPtr> subops; + subops.emplace_back(opFirst); + if (opIntermediate) { + subops.emplace_back(NN_NO_CHECK(opIntermediate)); + } + subops.emplace_back(boundSrc->transformation()); + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + subops, disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + if (!res.empty()) { + return; + } + } + // If the datum are equivalent, this is also fine + } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog && + hubSrcGeog->datumNonNull(dbContext)->_isEquivalentTo( + geogDstDatum.get(), + util::IComparable::Criterion::EQUIVALENT)) { + auto opsFirst = createOperations( + boundSrc->baseCRS(), NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context); + auto opsLast = createOperations(hubSrc, targetCRS, context); + if (!opsFirst.empty() && !opsLast.empty()) { + CoordinateOperationPtr opIntermediate; + if (!geogCRSOfBaseOfBoundSrc->_isEquivalentTo( + boundSrc->transformation()->sourceCRS().get(), + util::IComparable::Criterion::EQUIVALENT)) { + auto opsIntermediate = createOperations( + NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), + boundSrc->transformation()->sourceCRS(), context); + assert(!opsIntermediate.empty()); + opIntermediate = opsIntermediate.front(); + } + for (const auto &opFirst : opsFirst) { + for (const auto &opLast : opsLast) { + try { + std::vector<CoordinateOperationNNPtr> subops; + subops.emplace_back(opFirst); + if (opIntermediate) { + subops.emplace_back(NN_NO_CHECK(opIntermediate)); + } + subops.emplace_back(boundSrc->transformation()); + subops.emplace_back(opLast); + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + subops, disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + } + if (!res.empty()) { + return; + } + } + // Consider WGS 84 and NAD83 as equivalent in that context if the + // geogCRSOfBaseOfBoundSrc ellipsoid is Clarke66 (for NAD27) + // Case of "+proj=latlong +ellps=clrk66 + // +nadgrids=ntv1_can.dat,conus" + // to "+proj=latlong +datum=NAD83" + } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog && + geogCRSOfBaseOfBoundSrc->ellipsoid()->_isEquivalentTo( + datum::Ellipsoid::CLARKE_1866.get(), + util::IComparable::Criterion::EQUIVALENT) && + hubSrcGeog->datumNonNull(dbContext)->_isEquivalentTo( + datum::GeodeticReferenceFrame::EPSG_6326.get(), + util::IComparable::Criterion::EQUIVALENT) && + geogDstDatum->_isEquivalentTo( + datum::GeodeticReferenceFrame::EPSG_6269.get(), + util::IComparable::Criterion::EQUIVALENT)) { + auto nnGeogCRSOfBaseOfBoundSrc = NN_NO_CHECK(geogCRSOfBaseOfBoundSrc); + if (boundSrc->baseCRS()->_isEquivalentTo( + nnGeogCRSOfBaseOfBoundSrc.get(), + util::IComparable::Criterion::EQUIVALENT)) { + auto transf = boundSrc->transformation()->shallowClone(); + transf->setProperties(util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + buildTransfName(boundSrc->baseCRS()->nameStr(), + targetCRS->nameStr()))); + transf->setCRSs(boundSrc->baseCRS(), targetCRS, nullptr); + res.emplace_back(transf); + return; + } else { + auto opsFirst = createOperations( + boundSrc->baseCRS(), nnGeogCRSOfBaseOfBoundSrc, context); + auto transf = boundSrc->transformation()->shallowClone(); + transf->setProperties(util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + buildTransfName(nnGeogCRSOfBaseOfBoundSrc->nameStr(), + targetCRS->nameStr()))); + transf->setCRSs(nnGeogCRSOfBaseOfBoundSrc, targetCRS, nullptr); + if (!opsFirst.empty()) { + for (const auto &opFirst : opsFirst) { + try { + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + {opFirst, transf}, disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + if (!res.empty()) { + return; + } + } + } + } + + if (hubSrcGeog && + hubSrcGeog->_isEquivalentTo(geogDst, + util::IComparable::Criterion::EQUIVALENT) && + dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get())) { + auto transfSrc = boundSrc->transformation()->sourceCRS(); + if (dynamic_cast<const crs::VerticalCRS *>(transfSrc.get()) && + !boundSrc->baseCRS()->_isEquivalentTo( + transfSrc.get(), util::IComparable::Criterion::EQUIVALENT)) { + auto opsFirst = + createOperations(boundSrc->baseCRS(), transfSrc, context); + for (const auto &opFirst : opsFirst) { + try { + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + {opFirst, boundSrc->transformation()}, + disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + return; + } + + res.emplace_back(boundSrc->transformation()); + return; + } + + if (!triedBoundCrsToGeogCRSSameAsHubCRS && hubSrcGeog && + geogCRSOfBaseOfBoundSrc) { + // This one should go to the above 'Is it: boundCRS to a geogCRS + // that is the same as the hubCRS ?' case + auto opsFirst = createOperations(sourceCRS, hubSrc, context); + auto opsLast = createOperations(hubSrc, targetCRS, context); + if (!opsFirst.empty() && !opsLast.empty()) { + for (const auto &opFirst : opsFirst) { + for (const auto &opLast : opsLast) { + // Exclude artificial transformations from the hub + // to the target CRS, if it is the only one. + if (opsLast.size() > 1 || + !opLast->hasBallparkTransformation()) { + try { + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + {opFirst, opLast}, + disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } else { + // std::cerr << "excluded " << opLast->nameStr() << + // std::endl; + } + } + } + if (!res.empty()) { + return; + } + } + } + + auto vertCRSOfBaseOfBoundSrc = + dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get()); + if (vertCRSOfBaseOfBoundSrc && hubSrcGeog) { + auto opsFirst = createOperations(sourceCRS, hubSrc, context); + if (context.skipHorizontalTransformation) { + if (!opsFirst.empty()) { + const auto &hubAxisList = + hubSrcGeog->coordinateSystem()->axisList(); + const auto &targetAxisList = + geogDst->coordinateSystem()->axisList(); + if (hubAxisList.size() == 3 && targetAxisList.size() == 3 && + !hubAxisList[2]->_isEquivalentTo( + targetAxisList[2].get(), + util::IComparable::Criterion::EQUIVALENT)) { + + const auto &srcAxis = hubAxisList[2]; + const double convSrc = srcAxis->unit().conversionToSI(); + const auto &dstAxis = targetAxisList[2]; + const double convDst = dstAxis->unit().conversionToSI(); + const bool srcIsUp = + srcAxis->direction() == cs::AxisDirection::UP; + const bool srcIsDown = + srcAxis->direction() == cs::AxisDirection::DOWN; + const bool dstIsUp = + dstAxis->direction() == cs::AxisDirection::UP; + const bool dstIsDown = + dstAxis->direction() == cs::AxisDirection::DOWN; + const bool heightDepthReversal = + ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp)); + + const double factor = convSrc / convDst; + auto conv = Conversion::createChangeVerticalUnit( + util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + "Change of vertical unit"), + common::Scale(heightDepthReversal ? -factor : factor)); + + conv->setCRSs( + hubSrc, + hubSrc->demoteTo2D(std::string(), dbContext) + ->promoteTo3D(std::string(), dbContext, dstAxis), + nullptr); + + for (const auto &op : opsFirst) { + try { + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + {op, conv}, disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + } else { + res = opsFirst; + } + } + return; + } else { + auto opsSecond = createOperations(hubSrc, targetCRS, context); + if (!opsFirst.empty() && !opsSecond.empty()) { + for (const auto &opFirst : opsFirst) { + for (const auto &opLast : opsSecond) { + // Exclude artificial transformations from the hub + // to the target CRS + if (!opLast->hasBallparkTransformation()) { + try { + res.emplace_back( + ConcatenatedOperation:: + createComputeMetadata( + {opFirst, opLast}, + disallowEmptyIntersection)); + } catch ( + const InvalidOperationEmptyIntersection &) { + } + } else { + // std::cerr << "excluded " << opLast->nameStr() << + // std::endl; + } + } + } + if (!res.empty()) { + return; + } + } + } + } + + res = createOperations(boundSrc->baseCRS(), targetCRS, context); +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsBoundToVert( + const crs::CRSNNPtr & /*sourceCRS*/, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::BoundCRS *boundSrc, + const crs::VerticalCRS *vertDst, + std::vector<CoordinateOperationNNPtr> &res) { + + ENTER_FUNCTION(); + + auto baseSrcVert = + dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get()); + const auto &hubSrc = boundSrc->hubCRS(); + auto hubSrcVert = dynamic_cast<const crs::VerticalCRS *>(hubSrc.get()); + if (baseSrcVert && hubSrcVert && + vertDst->_isEquivalentTo(hubSrcVert, + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(boundSrc->transformation()); + return; + } + + res = createOperations(boundSrc->baseCRS(), targetCRS, context); +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsVertToVert( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::VerticalCRS *vertSrc, + const crs::VerticalCRS *vertDst, + std::vector<CoordinateOperationNNPtr> &res) { + + ENTER_FUNCTION(); + + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() : nullptr; + + const auto srcDatum = vertSrc->datumNonNull(dbContext); + const auto dstDatum = vertDst->datumNonNull(dbContext); + const bool equivalentVDatum = srcDatum->_isEquivalentTo( + dstDatum.get(), util::IComparable::Criterion::EQUIVALENT); + + const auto &srcAxis = vertSrc->coordinateSystem()->axisList()[0]; + const double convSrc = srcAxis->unit().conversionToSI(); + const auto &dstAxis = vertDst->coordinateSystem()->axisList()[0]; + const double convDst = dstAxis->unit().conversionToSI(); + const bool srcIsUp = srcAxis->direction() == cs::AxisDirection::UP; + const bool srcIsDown = srcAxis->direction() == cs::AxisDirection::DOWN; + const bool dstIsUp = dstAxis->direction() == cs::AxisDirection::UP; + const bool dstIsDown = dstAxis->direction() == cs::AxisDirection::DOWN; + const bool heightDepthReversal = + ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp)); + + const double factor = convSrc / convDst; + auto name = buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr()); + if (!equivalentVDatum) { + name += BALLPARK_VERTICAL_TRANSFORMATION; + auto conv = Transformation::createChangeVerticalUnit( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name), + sourceCRS, targetCRS, + // In case of a height depth reversal, we should probably have + // 2 steps instead of putting a negative factor... + common::Scale(heightDepthReversal ? -factor : factor), {}); + conv->setHasBallparkTransformation(true); + res.push_back(conv); + } else if (convSrc != convDst || !heightDepthReversal) { + auto conv = Conversion::createChangeVerticalUnit( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name), + // In case of a height depth reversal, we should probably have + // 2 steps instead of putting a negative factor... + common::Scale(heightDepthReversal ? -factor : factor)); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + res.push_back(conv); + } else { + auto conv = Conversion::createHeightDepthReversal( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name)); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + res.push_back(conv); + } +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsVertToGeog( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::VerticalCRS *vertSrc, + const crs::GeographicCRS *geogDst, + std::vector<CoordinateOperationNNPtr> &res) { + + ENTER_FUNCTION(); + + if (vertSrc->identifiers().empty()) { + const auto &vertSrcName = vertSrc->nameStr(); + const auto &authFactory = context.context->getAuthorityFactory(); + if (authFactory != nullptr && vertSrcName != "unnamed" && + vertSrcName != "unknown") { + auto matches = authFactory->createObjectsFromName( + vertSrcName, {io::AuthorityFactory::ObjectType::VERTICAL_CRS}, + false, 2); + if (matches.size() == 1) { + const auto &match = matches.front(); + if (vertSrc->_isEquivalentTo( + match.get(), + util::IComparable::Criterion::EQUIVALENT) && + !match->identifiers().empty()) { + auto resTmp = createOperations( + NN_NO_CHECK( + util::nn_dynamic_pointer_cast<crs::VerticalCRS>( + match)), + targetCRS, context); + res.insert(res.end(), resTmp.begin(), resTmp.end()); + return; + } + } + } + } + + createOperationsVertToGeogBallpark(sourceCRS, targetCRS, context, vertSrc, + geogDst, res); +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsVertToGeogBallpark( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &, const crs::VerticalCRS *vertSrc, + const crs::GeographicCRS *geogDst, + std::vector<CoordinateOperationNNPtr> &res) { + + ENTER_FUNCTION(); + + const auto &srcAxis = vertSrc->coordinateSystem()->axisList()[0]; + const double convSrc = srcAxis->unit().conversionToSI(); + double convDst = 1.0; + const auto &geogAxis = geogDst->coordinateSystem()->axisList(); + bool dstIsUp = true; + bool dstIsDown = false; + if (geogAxis.size() == 3) { + const auto &dstAxis = geogAxis[2]; + convDst = dstAxis->unit().conversionToSI(); + dstIsUp = dstAxis->direction() == cs::AxisDirection::UP; + dstIsDown = dstAxis->direction() == cs::AxisDirection::DOWN; + } + const bool srcIsUp = srcAxis->direction() == cs::AxisDirection::UP; + const bool srcIsDown = srcAxis->direction() == cs::AxisDirection::DOWN; + const bool heightDepthReversal = + ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp)); + + const double factor = convSrc / convDst; + + const auto &sourceCRSExtent = getExtent(sourceCRS); + const auto &targetCRSExtent = getExtent(targetCRS); + const bool sameExtent = + sourceCRSExtent && targetCRSExtent && + sourceCRSExtent->_isEquivalentTo( + targetCRSExtent.get(), util::IComparable::Criterion::EQUIVALENT); + + util::PropertyMap map; + map.set(common::IdentifiedObject::NAME_KEY, + buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr()) + + BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + sameExtent ? NN_NO_CHECK(sourceCRSExtent) + : metadata::Extent::WORLD); + + auto conv = Transformation::createChangeVerticalUnit( + map, sourceCRS, targetCRS, + common::Scale(heightDepthReversal ? -factor : factor), {}); + conv->setHasBallparkTransformation(true); + res.push_back(conv); +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsBoundToBound( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::BoundCRS *boundSrc, + const crs::BoundCRS *boundDst, std::vector<CoordinateOperationNNPtr> &res) { + + ENTER_FUNCTION(); + + // BoundCRS to BoundCRS of horizontal CRS using the same (geographic) hub + const auto &hubSrc = boundSrc->hubCRS(); + auto hubSrcGeog = dynamic_cast<const crs::GeographicCRS *>(hubSrc.get()); + const auto &hubDst = boundDst->hubCRS(); + auto hubDstGeog = dynamic_cast<const crs::GeographicCRS *>(hubDst.get()); + if (hubSrcGeog && hubDstGeog && + hubSrcGeog->_isEquivalentTo(hubDstGeog, + util::IComparable::Criterion::EQUIVALENT)) { + auto opsFirst = createOperations(sourceCRS, hubSrc, context); + auto opsLast = createOperations(hubSrc, targetCRS, context); + for (const auto &opFirst : opsFirst) { + for (const auto &opLast : opsLast) { + try { + std::vector<CoordinateOperationNNPtr> ops; + ops.push_back(opFirst); + ops.push_back(opLast); + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + ops, disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + } + if (!res.empty()) { + return; + } + } + + // BoundCRS to BoundCRS of vertical CRS using the same vertical datum + // ==> ignore the bound transformation + auto baseOfBoundSrcAsVertCRS = + dynamic_cast<crs::VerticalCRS *>(boundSrc->baseCRS().get()); + auto baseOfBoundDstAsVertCRS = + dynamic_cast<crs::VerticalCRS *>(boundDst->baseCRS().get()); + if (baseOfBoundSrcAsVertCRS && baseOfBoundDstAsVertCRS) { + + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() + : nullptr; + + const auto datumSrc = baseOfBoundSrcAsVertCRS->datumNonNull(dbContext); + const auto datumDst = baseOfBoundDstAsVertCRS->datumNonNull(dbContext); + if (datumSrc->nameStr() == datumDst->nameStr() && + (datumSrc->nameStr() != "unknown" || + boundSrc->transformation()->_isEquivalentTo( + boundDst->transformation().get(), + util::IComparable::Criterion::EQUIVALENT))) { + res = createOperations(boundSrc->baseCRS(), boundDst->baseCRS(), + context); + return; + } + } + + // BoundCRS to BoundCRS of vertical CRS + auto vertCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractVerticalCRS(); + auto vertCRSOfBaseOfBoundDst = boundDst->baseCRS()->extractVerticalCRS(); + if (hubSrcGeog && hubDstGeog && + hubSrcGeog->_isEquivalentTo(hubDstGeog, + util::IComparable::Criterion::EQUIVALENT) && + vertCRSOfBaseOfBoundSrc && vertCRSOfBaseOfBoundDst) { + auto opsFirst = createOperations(sourceCRS, hubSrc, context); + auto opsLast = createOperations(hubSrc, targetCRS, context); + if (!opsFirst.empty() && !opsLast.empty()) { + for (const auto &opFirst : opsFirst) { + for (const auto &opLast : opsLast) { + try { + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + {opFirst, opLast}, disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + } + if (!res.empty()) { + return; + } + } + } + + res = createOperations(boundSrc->baseCRS(), boundDst->baseCRS(), context); +} + +// --------------------------------------------------------------------------- + +static std::vector<CoordinateOperationNNPtr> +getOps(const CoordinateOperationNNPtr &op) { + auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get()); + if (concatenated) + return concatenated->operations(); + return {op}; +} + +// --------------------------------------------------------------------------- + +static bool useDifferentTransformationsForSameSourceTarget( + const CoordinateOperationNNPtr &opA, const CoordinateOperationNNPtr &opB) { + auto subOpsA = getOps(opA); + auto subOpsB = getOps(opB); + for (const auto &subOpA : subOpsA) { + if (!dynamic_cast<const Transformation *>(subOpA.get())) + continue; + if (subOpA->sourceCRS()->nameStr() == "unknown" || + subOpA->targetCRS()->nameStr() == "unknown") + continue; + for (const auto &subOpB : subOpsB) { + if (!dynamic_cast<const Transformation *>(subOpB.get())) + continue; + if (subOpB->sourceCRS()->nameStr() == "unknown" || + subOpB->targetCRS()->nameStr() == "unknown") + continue; + + if (subOpA->sourceCRS()->nameStr() == + subOpB->sourceCRS()->nameStr() && + subOpA->targetCRS()->nameStr() == + subOpB->targetCRS()->nameStr()) { + if (starts_with(subOpA->nameStr(), NULL_GEOGRAPHIC_OFFSET) && + starts_with(subOpB->nameStr(), NULL_GEOGRAPHIC_OFFSET)) { + continue; + } + + if (!subOpA->isEquivalentTo(subOpB.get())) { + return true; + } + } else if (subOpA->sourceCRS()->nameStr() == + subOpB->targetCRS()->nameStr() && + subOpA->targetCRS()->nameStr() == + subOpB->sourceCRS()->nameStr()) { + if (starts_with(subOpA->nameStr(), NULL_GEOGRAPHIC_OFFSET) && + starts_with(subOpB->nameStr(), NULL_GEOGRAPHIC_OFFSET)) { + continue; + } + + if (!subOpA->isEquivalentTo(subOpB->inverse().get())) { + return true; + } + } + } + } + return false; +} + +// --------------------------------------------------------------------------- + +static crs::GeographicCRSPtr +getInterpolationGeogCRS(const CoordinateOperationNNPtr &verticalTransform, + const io::DatabaseContextPtr &dbContext) { + crs::GeographicCRSPtr interpolationGeogCRS; + auto transformationVerticalTransform = + dynamic_cast<const Transformation *>(verticalTransform.get()); + if (transformationVerticalTransform == nullptr) { + const auto concat = dynamic_cast<const ConcatenatedOperation *>( + verticalTransform.get()); + if (concat) { + const auto &steps = concat->operations(); + // Is this change of unit and/or height depth reversal + + // transformation ? + for (const auto &step : steps) { + const auto transf = + dynamic_cast<const Transformation *>(step.get()); + if (transf) { + // Only support a single Transformation in the steps + if (transformationVerticalTransform != nullptr) { + transformationVerticalTransform = nullptr; + break; + } + transformationVerticalTransform = transf; + } + } + } + } + if (transformationVerticalTransform && + !transformationVerticalTransform->hasBallparkTransformation()) { + auto interpTransformCRS = + transformationVerticalTransform->interpolationCRS(); + if (interpTransformCRS) { + interpolationGeogCRS = + std::dynamic_pointer_cast<crs::GeographicCRS>( + interpTransformCRS); + } else { + // If no explicit interpolation CRS, then + // this will be the geographic CRS of the + // vertical to geog transformation + interpolationGeogCRS = + std::dynamic_pointer_cast<crs::GeographicCRS>( + transformationVerticalTransform->targetCRS().as_nullable()); + } + } + + if (interpolationGeogCRS) { + if (interpolationGeogCRS->coordinateSystem()->axisList().size() == 3) { + // We need to force the interpolation CRS, which + // will + // frequently be 3D, to 2D to avoid transformations + // between source CRS and interpolation CRS to have + // 3D terms. + interpolationGeogCRS = + interpolationGeogCRS->demoteTo2D(std::string(), dbContext) + .as_nullable(); + } + } + + return interpolationGeogCRS; +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsCompoundToGeog( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::CompoundCRS *compoundSrc, + const crs::GeographicCRS *geogDst, + std::vector<CoordinateOperationNNPtr> &res) { + + ENTER_FUNCTION(); + + const auto &authFactory = context.context->getAuthorityFactory(); + const auto &componentsSrc = compoundSrc->componentReferenceSystems(); + if (!componentsSrc.empty()) { + + if (componentsSrc.size() == 2) { + auto derivedHSrc = + dynamic_cast<const crs::DerivedCRS *>(componentsSrc[0].get()); + if (derivedHSrc) { + std::vector<crs::CRSNNPtr> intermComponents{ + derivedHSrc->baseCRS(), componentsSrc[1]}; + auto properties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + intermComponents[0]->nameStr() + " + " + + intermComponents[1]->nameStr()); + auto intermCompound = + crs::CompoundCRS::create(properties, intermComponents); + auto opsFirst = + createOperations(sourceCRS, intermCompound, context); + assert(!opsFirst.empty()); + auto opsLast = + createOperations(intermCompound, targetCRS, context); + for (const auto &opLast : opsLast) { + try { + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + {opsFirst.front(), opLast}, + disallowEmptyIntersection)); + } catch (const std::exception &) { + } + } + return; + } + } + + std::vector<CoordinateOperationNNPtr> horizTransforms; + auto srcGeogCRS = componentsSrc[0]->extractGeographicCRS(); + if (srcGeogCRS) { + horizTransforms = + createOperations(componentsSrc[0], targetCRS, context); + } + std::vector<CoordinateOperationNNPtr> verticalTransforms; + + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() + : nullptr; + if (componentsSrc.size() >= 2 && + componentsSrc[1]->extractVerticalCRS()) { + + struct SetSkipHorizontalTransform { + Context &context; + + explicit SetSkipHorizontalTransform(Context &contextIn) + : context(contextIn) { + assert(!context.skipHorizontalTransformation); + context.skipHorizontalTransformation = true; + } + + ~SetSkipHorizontalTransform() { + context.skipHorizontalTransformation = false; + } + }; + SetSkipHorizontalTransform setSkipHorizontalTransform(context); + + verticalTransforms = createOperations( + componentsSrc[1], + targetCRS->promoteTo3D(std::string(), dbContext), context); + bool foundRegisteredTransformWithAllGridsAvailable = false; + const auto gridAvailabilityUse = + context.context->getGridAvailabilityUse(); + const bool ignoreMissingGrids = + gridAvailabilityUse == + CoordinateOperationContext::GridAvailabilityUse:: + IGNORE_GRID_AVAILABILITY; + for (const auto &op : verticalTransforms) { + if (hasIdentifiers(op) && dbContext) { + bool missingGrid = false; + if (!ignoreMissingGrids) { + const auto gridsNeeded = op->gridsNeeded( + dbContext, + gridAvailabilityUse == + CoordinateOperationContext:: + GridAvailabilityUse::KNOWN_AVAILABLE); + for (const auto &gridDesc : gridsNeeded) { + if (!gridDesc.available) { + missingGrid = true; + break; + } + } + } + if (!missingGrid) { + foundRegisteredTransformWithAllGridsAvailable = true; + break; + } + } + } + if (!foundRegisteredTransformWithAllGridsAvailable && srcGeogCRS && + !srcGeogCRS->_isEquivalentTo( + geogDst, util::IComparable::Criterion::EQUIVALENT) && + !srcGeogCRS->is2DPartOf3D(NN_NO_CHECK(geogDst), dbContext)) { + auto verticalTransformsTmp = createOperations( + componentsSrc[1], + NN_NO_CHECK(srcGeogCRS) + ->promoteTo3D(std::string(), dbContext), + context); + bool foundRegisteredTransform = false; + foundRegisteredTransformWithAllGridsAvailable = false; + for (const auto &op : verticalTransformsTmp) { + if (hasIdentifiers(op) && dbContext) { + bool missingGrid = false; + if (!ignoreMissingGrids) { + const auto gridsNeeded = op->gridsNeeded( + dbContext, + gridAvailabilityUse == + CoordinateOperationContext:: + GridAvailabilityUse::KNOWN_AVAILABLE); + for (const auto &gridDesc : gridsNeeded) { + if (!gridDesc.available) { + missingGrid = true; + break; + } + } + } + foundRegisteredTransform = true; + if (!missingGrid) { + foundRegisteredTransformWithAllGridsAvailable = + true; + break; + } + } + } + if (foundRegisteredTransformWithAllGridsAvailable) { + verticalTransforms = verticalTransformsTmp; + } else if (foundRegisteredTransform) { + verticalTransforms.insert(verticalTransforms.end(), + verticalTransformsTmp.begin(), + verticalTransformsTmp.end()); + } + } + } + + if (horizTransforms.empty() || verticalTransforms.empty()) { + res = horizTransforms; + return; + } + + typedef std::pair<std::vector<CoordinateOperationNNPtr>, + std::vector<CoordinateOperationNNPtr>> + PairOfTransforms; + std::map<std::string, PairOfTransforms> + cacheHorizToInterpAndInterpToTarget; + + for (const auto &verticalTransform : verticalTransforms) { +#ifdef TRACE_CREATE_OPERATIONS + ENTER_BLOCK("Considering vertical transform " + + objectAsStr(verticalTransform.get())); +#endif + crs::GeographicCRSPtr interpolationGeogCRS = + getInterpolationGeogCRS(verticalTransform, dbContext); + if (interpolationGeogCRS) { +#ifdef TRACE_CREATE_OPERATIONS + logTrace("Using " + objectAsStr(interpolationGeogCRS.get()) + + " as interpolation CRS"); +#endif + std::vector<CoordinateOperationNNPtr> srcToInterpOps; + std::vector<CoordinateOperationNNPtr> interpToTargetOps; + + std::string key; + const auto &ids = interpolationGeogCRS->identifiers(); + if (!ids.empty()) { + key = + (*ids.front()->codeSpace()) + ':' + ids.front()->code(); + } + + const auto computeOpsToInterp = + [&srcToInterpOps, &interpToTargetOps, &componentsSrc, + &interpolationGeogCRS, &targetCRS, &dbContext, + &context]() { + srcToInterpOps = createOperations( + componentsSrc[0], NN_NO_CHECK(interpolationGeogCRS), + context); + auto target2D = + targetCRS->demoteTo2D(std::string(), dbContext); + if (!componentsSrc[0]->isEquivalentTo( + target2D.get(), + util::IComparable::Criterion::EQUIVALENT)) { + // We do the transformation from the + // interpolationCRS + // to the target one in 3D (see #2225) + // But we don't do that between sourceCRS and + // interpolationCRS, as this would mess with an + // orthometric elevation. + auto interp3D = interpolationGeogCRS->promoteTo3D( + std::string(), dbContext); + interpToTargetOps = + createOperations(interp3D, targetCRS, context); + } + }; + + if (!key.empty()) { + auto iter = cacheHorizToInterpAndInterpToTarget.find(key); + if (iter == cacheHorizToInterpAndInterpToTarget.end()) { +#ifdef TRACE_CREATE_OPERATIONS + ENTER_BLOCK("looking for horizontal transformation " + "from source to interpCRS and interpCRS to " + "target"); +#endif + computeOpsToInterp(); + cacheHorizToInterpAndInterpToTarget[key] = + PairOfTransforms(srcToInterpOps, interpToTargetOps); + } else { + srcToInterpOps = iter->second.first; + interpToTargetOps = iter->second.second; + } + } else { +#ifdef TRACE_CREATE_OPERATIONS + ENTER_BLOCK("looking for horizontal transformation " + "from source to interpCRS and interpCRS to " + "target"); +#endif + computeOpsToInterp(); + } + +#ifdef TRACE_CREATE_OPERATIONS + ENTER_BLOCK("creating HorizVerticalHorizPROJBased operations"); +#endif + for (const auto &srcToInterp : srcToInterpOps) { + if (interpToTargetOps.empty()) { + try { + auto op = createHorizVerticalHorizPROJBased( + sourceCRS, targetCRS, srcToInterp, + verticalTransform, srcToInterp->inverse(), + interpolationGeogCRS, true); + res.emplace_back(op); + } catch (const std::exception &) { + } + } else { + for (const auto &interpToTarget : interpToTargetOps) { + + if (useDifferentTransformationsForSameSourceTarget( + srcToInterp, interpToTarget)) { + continue; + } + + try { + auto op = createHorizVerticalHorizPROJBased( + sourceCRS, targetCRS, srcToInterp, + verticalTransform, interpToTarget, + interpolationGeogCRS, true); + res.emplace_back(op); + } catch (const std::exception &) { + } + } + } + } + } else { + // This case is probably only correct if + // verticalTransform and horizTransform are independent + // and in particular that verticalTransform does not + // involve a grid, because of the rather arbitrary order + // horizontal then vertical applied + for (const auto &horizTransform : horizTransforms) { + try { + auto op = createHorizVerticalPROJBased( + sourceCRS, targetCRS, horizTransform, + verticalTransform, disallowEmptyIntersection); + res.emplace_back(op); + } catch (const std::exception &) { + } + } + } + } + } +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsToGeod( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::GeodeticCRS *geodDst, + std::vector<CoordinateOperationNNPtr> &res) { + + auto cs = cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( + common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE); + auto intermGeog3DCRS = + util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, geodDst->nameStr()) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + metadata::Extent::WORLD), + geodDst->datum(), geodDst->datumEnsemble(), cs)); + auto sourceToGeog3DOps = + createOperations(sourceCRS, intermGeog3DCRS, context); + auto geog3DToTargetOps = + createOperations(intermGeog3DCRS, targetCRS, context); + if (!geog3DToTargetOps.empty()) { + for (const auto &op : sourceToGeog3DOps) { + auto newOp = op->shallowClone(); + setCRSs(newOp.get(), sourceCRS, intermGeog3DCRS); + try { + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + {newOp, geog3DToTargetOps.front()}, + disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + } +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsCompoundToCompound( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::CompoundCRS *compoundSrc, + const crs::CompoundCRS *compoundDst, + std::vector<CoordinateOperationNNPtr> &res) { + + const auto &componentsSrc = compoundSrc->componentReferenceSystems(); + const auto &componentsDst = compoundDst->componentReferenceSystems(); + if (componentsSrc.empty() || componentsSrc.size() != componentsDst.size()) { + return; + } + const auto srcGeog = componentsSrc[0]->extractGeographicCRS(); + const auto dstGeog = componentsDst[0]->extractGeographicCRS(); + if (srcGeog == nullptr || dstGeog == nullptr) { + return; + } + + std::vector<CoordinateOperationNNPtr> verticalTransforms; + if (componentsSrc.size() >= 2 && componentsSrc[1]->extractVerticalCRS() && + componentsDst[1]->extractVerticalCRS()) { + if (!componentsSrc[1]->_isEquivalentTo(componentsDst[1].get())) { + verticalTransforms = + createOperations(componentsSrc[1], componentsDst[1], context); + } + } + + // 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 + // there is a geoid model for EGM96 referenced to WGS 84 + // and a geoid model for Belfast height referenced to ETRS89 + if (verticalTransforms.size() == 1 && + verticalTransforms.front()->hasBallparkTransformation()) { + auto dbContext = + context.context->getAuthorityFactory()->databaseContext(); + const auto intermGeogSrc = + srcGeog->promoteTo3D(std::string(), dbContext); + const bool intermGeogSrcIsSameAsIntermGeogDst = + srcGeog->_isEquivalentTo(dstGeog.get()); + const auto intermGeogDst = + intermGeogSrcIsSameAsIntermGeogDst + ? intermGeogSrc + : dstGeog->promoteTo3D(std::string(), dbContext); + const auto opsSrcToGeog = + createOperations(sourceCRS, intermGeogSrc, context); + const auto opsGeogToTarget = + createOperations(intermGeogDst, targetCRS, context); + const bool hasNonTrivalSrcTransf = + !opsSrcToGeog.empty() && + !opsSrcToGeog.front()->hasBallparkTransformation(); + const bool hasNonTrivialTargetTransf = + !opsGeogToTarget.empty() && + !opsGeogToTarget.front()->hasBallparkTransformation(); + if (hasNonTrivalSrcTransf && hasNonTrivialTargetTransf) { + const auto opsGeogSrcToGeogDst = + createOperations(intermGeogSrc, intermGeogDst, context); + for (const auto &op1 : opsSrcToGeog) { + if (op1->hasBallparkTransformation()) { + // std::cerr << "excluded " << op1->nameStr() << std::endl; + continue; + } + for (const auto &op2 : opsGeogSrcToGeogDst) { + for (const auto &op3 : opsGeogToTarget) { + if (op3->hasBallparkTransformation()) { + // std::cerr << "excluded " << op3->nameStr() << + // std::endl; + continue; + } + try { + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + intermGeogSrcIsSameAsIntermGeogDst + ? std::vector< + CoordinateOperationNNPtr>{op1, + op3} + : std::vector< + CoordinateOperationNNPtr>{op1, + op2, + op3}, + disallowEmptyIntersection)); + } catch (const std::exception &) { + } + } + } + } + } + if (!res.empty()) { + return; + } + } + + for (const auto &verticalTransform : verticalTransforms) { + auto interpolationGeogCRS = NN_NO_CHECK(srcGeog); + auto interpTransformCRS = verticalTransform->interpolationCRS(); + if (interpTransformCRS) { + auto nn_interpTransformCRS = NN_NO_CHECK(interpTransformCRS); + if (dynamic_cast<const crs::GeographicCRS *>( + nn_interpTransformCRS.get())) { + interpolationGeogCRS = NN_NO_CHECK( + util::nn_dynamic_pointer_cast<crs::GeographicCRS>( + nn_interpTransformCRS)); + } + } else { + auto compSrc0BoundCrs = + dynamic_cast<crs::BoundCRS *>(componentsSrc[0].get()); + auto compDst0BoundCrs = + dynamic_cast<crs::BoundCRS *>(componentsDst[0].get()); + if (compSrc0BoundCrs && compDst0BoundCrs && + dynamic_cast<crs::GeographicCRS *>( + compSrc0BoundCrs->hubCRS().get()) && + compSrc0BoundCrs->hubCRS()->_isEquivalentTo( + compDst0BoundCrs->hubCRS().get())) { + interpolationGeogCRS = NN_NO_CHECK( + util::nn_dynamic_pointer_cast<crs::GeographicCRS>( + compSrc0BoundCrs->hubCRS())); + } + } + auto opSrcCRSToGeogCRS = + createOperations(componentsSrc[0], interpolationGeogCRS, context); + auto opGeogCRStoDstCRS = + createOperations(interpolationGeogCRS, componentsDst[0], context); + for (const auto &opSrc : opSrcCRSToGeogCRS) { + for (const auto &opDst : opGeogCRStoDstCRS) { + + try { + auto op = createHorizVerticalHorizPROJBased( + sourceCRS, targetCRS, opSrc, verticalTransform, opDst, + interpolationGeogCRS, true); + res.emplace_back(op); + } catch (const InvalidOperationEmptyIntersection &) { + } catch (const io::FormattingException &) { + } + } + } + } + + if (verticalTransforms.empty()) { + auto resTmp = + createOperations(componentsSrc[0], componentsDst[0], context); + for (const auto &op : resTmp) { + auto opClone = op->shallowClone(); + setCRSs(opClone.get(), sourceCRS, targetCRS); + res.emplace_back(opClone); + } + } +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsBoundToCompound( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::BoundCRS *boundSrc, + const crs::CompoundCRS *compoundDst, + std::vector<CoordinateOperationNNPtr> &res) { + + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() : nullptr; + + const auto &componentsDst = compoundDst->componentReferenceSystems(); + if (!componentsDst.empty()) { + auto compDst0BoundCrs = + dynamic_cast<crs::BoundCRS *>(componentsDst[0].get()); + if (compDst0BoundCrs) { + auto boundSrcHubAsGeogCRS = + dynamic_cast<crs::GeographicCRS *>(boundSrc->hubCRS().get()); + auto compDst0BoundCrsHubAsGeogCRS = + dynamic_cast<crs::GeographicCRS *>( + compDst0BoundCrs->hubCRS().get()); + if (boundSrcHubAsGeogCRS && compDst0BoundCrsHubAsGeogCRS) { + const auto boundSrcHubAsGeogCRSDatum = + boundSrcHubAsGeogCRS->datumNonNull(dbContext); + const auto compDst0BoundCrsHubAsGeogCRSDatum = + compDst0BoundCrsHubAsGeogCRS->datumNonNull(dbContext); + if (boundSrcHubAsGeogCRSDatum->_isEquivalentTo( + compDst0BoundCrsHubAsGeogCRSDatum.get())) { + auto cs = cs::EllipsoidalCS:: + createLatitudeLongitudeEllipsoidalHeight( + common::UnitOfMeasure::DEGREE, + common::UnitOfMeasure::METRE); + auto intermGeog3DCRS = util::nn_static_pointer_cast< + crs::CRS>(crs::GeographicCRS::create( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, + boundSrcHubAsGeogCRS->nameStr()) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + metadata::Extent::WORLD), + boundSrcHubAsGeogCRS->datum(), + boundSrcHubAsGeogCRS->datumEnsemble(), cs)); + auto sourceToGeog3DOps = + createOperations(sourceCRS, intermGeog3DCRS, context); + auto geog3DToTargetOps = + createOperations(intermGeog3DCRS, targetCRS, context); + for (const auto &opSrc : sourceToGeog3DOps) { + for (const auto &opDst : geog3DToTargetOps) { + if (opSrc->targetCRS() && opDst->sourceCRS() && + !opSrc->targetCRS()->_isEquivalentTo( + opDst->sourceCRS().get())) { + // Shouldn't happen normally, but typically + // one of them can be 2D and the other 3D + // due to above createOperations() not + // exactly setting the expected source and + // target CRS. + // So create an adapter operation... + auto intermOps = createOperations( + NN_NO_CHECK(opSrc->targetCRS()), + NN_NO_CHECK(opDst->sourceCRS()), context); + if (!intermOps.empty()) { + res.emplace_back( + ConcatenatedOperation:: + createComputeMetadata( + {opSrc, intermOps.front(), + opDst}, + disallowEmptyIntersection)); + } + } else { + res.emplace_back( + ConcatenatedOperation:: + createComputeMetadata( + {opSrc, opDst}, + disallowEmptyIntersection)); + } + } + } + return; + } + } + } + } + + // There might be better things to do, but for now just ignore the + // transformation of the bound CRS + res = createOperations(boundSrc->baseCRS(), targetCRS, context); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Find a list of CoordinateOperation from sourceCRS to targetCRS. + * + * The operations are sorted with the most relevant ones first: by + * descending + * area (intersection of the transformation area with the area of interest, + * or intersection of the transformation with the area of use of the CRS), + * and + * by increasing accuracy. Operations with unknown accuracy are sorted last, + * whatever their area. + * + * When one of the source or target CRS has a vertical component but not the + * other one, the one that has no vertical component is automatically promoted + * to a 3D version, where its vertical axis is the ellipsoidal height in metres, + * using the ellipsoid of the base geodetic CRS. + * + * @param sourceCRS source CRS. + * @param targetCRS target CRS. + * @param context Search context. + * @return a list + */ +std::vector<CoordinateOperationNNPtr> +CoordinateOperationFactory::createOperations( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const CoordinateOperationContextNNPtr &context) const { + +#ifdef TRACE_CREATE_OPERATIONS + ENTER_FUNCTION(); +#endif + // Look if we are called on CRS that have a link to a 'canonical' + // BoundCRS + // If so, use that one as input + const auto &srcBoundCRS = sourceCRS->canonicalBoundCRS(); + const auto &targetBoundCRS = targetCRS->canonicalBoundCRS(); + auto l_sourceCRS = srcBoundCRS ? NN_NO_CHECK(srcBoundCRS) : sourceCRS; + auto l_targetCRS = targetBoundCRS ? NN_NO_CHECK(targetBoundCRS) : targetCRS; + const auto &authFactory = context->getAuthorityFactory(); + + metadata::ExtentPtr sourceCRSExtent; + auto l_resolvedSourceCRS = + crs::CRS::getResolvedCRS(l_sourceCRS, authFactory, sourceCRSExtent); + metadata::ExtentPtr targetCRSExtent; + auto l_resolvedTargetCRS = + crs::CRS::getResolvedCRS(l_targetCRS, authFactory, targetCRSExtent); + Private::Context contextPrivate(sourceCRSExtent, targetCRSExtent, context); + + if (context->getSourceAndTargetCRSExtentUse() == + CoordinateOperationContext::SourceTargetCRSExtentUse::INTERSECTION) { + if (sourceCRSExtent && targetCRSExtent && + !sourceCRSExtent->intersects(NN_NO_CHECK(targetCRSExtent))) { + return std::vector<CoordinateOperationNNPtr>(); + } + } + + return filterAndSort(Private::createOperations(l_resolvedSourceCRS, + l_resolvedTargetCRS, + contextPrivate), + context, sourceCRSExtent, targetCRSExtent); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a CoordinateOperationFactory. + */ +CoordinateOperationFactoryNNPtr CoordinateOperationFactory::create() { + return NN_NO_CHECK( + CoordinateOperationFactory::make_unique<CoordinateOperationFactory>()); +} + +// --------------------------------------------------------------------------- + +} // namespace operation + +namespace crs { +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +crs::CRSNNPtr CRS::getResolvedCRS(const crs::CRSNNPtr &crs, + const io::AuthorityFactoryPtr &authFactory, + metadata::ExtentPtr &extentOut) { + const auto &ids = crs->identifiers(); + const auto &name = crs->nameStr(); + + bool approxExtent; + extentOut = operation::getExtentPossiblySynthetized(crs, approxExtent); + + // We try to "identify" the provided CRS with the ones of the database, + // but in a more restricted way that what identify() does. + // If we get a match from id in priority, and from name as a fallback, and + // that they are equivalent to the input CRS, then use the identified CRS. + // Even if they aren't equivalent, we update extentOut with the one of the + // identified CRS if our input one is absent/not reliable. + + const auto tryToIdentifyByName = [&crs, &name, &authFactory, approxExtent, + &extentOut]( + io::AuthorityFactory::ObjectType objectType) { + if (name != "unknown" && name != "unnamed") { + auto matches = authFactory->createObjectsFromName( + name, {objectType}, false, 2); + if (matches.size() == 1) { + const auto match = + util::nn_static_pointer_cast<crs::CRS>(matches.front()); + if (approxExtent || !extentOut) { + extentOut = operation::getExtent(match); + } + if (match->isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT)) { + return match; + } + } + } + return crs; + }; + + auto geogCRS = dynamic_cast<crs::GeographicCRS *>(crs.get()); + if (geogCRS && authFactory) { + if (!ids.empty()) { + const auto tmpAuthFactory = io::AuthorityFactory::create( + authFactory->databaseContext(), *ids.front()->codeSpace()); + try { + auto resolvedCrs( + tmpAuthFactory->createGeographicCRS(ids.front()->code())); + if (approxExtent || !extentOut) { + extentOut = operation::getExtent(resolvedCrs); + } + if (resolvedCrs->isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT)) { + return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs); + } + } catch (const std::exception &) { + } + } else { + return tryToIdentifyByName( + geogCRS->coordinateSystem()->axisList().size() == 2 + ? io::AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS + : io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS); + } + } + + auto projectedCrs = dynamic_cast<crs::ProjectedCRS *>(crs.get()); + if (projectedCrs && authFactory) { + if (!ids.empty()) { + const auto tmpAuthFactory = io::AuthorityFactory::create( + authFactory->databaseContext(), *ids.front()->codeSpace()); + try { + auto resolvedCrs( + tmpAuthFactory->createProjectedCRS(ids.front()->code())); + if (approxExtent || !extentOut) { + extentOut = operation::getExtent(resolvedCrs); + } + if (resolvedCrs->isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT)) { + return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs); + } + } catch (const std::exception &) { + } + } else { + return tryToIdentifyByName( + io::AuthorityFactory::ObjectType::PROJECTED_CRS); + } + } + + auto compoundCrs = dynamic_cast<crs::CompoundCRS *>(crs.get()); + if (compoundCrs && authFactory) { + if (!ids.empty()) { + const auto tmpAuthFactory = io::AuthorityFactory::create( + authFactory->databaseContext(), *ids.front()->codeSpace()); + try { + auto resolvedCrs( + tmpAuthFactory->createCompoundCRS(ids.front()->code())); + if (approxExtent || !extentOut) { + extentOut = operation::getExtent(resolvedCrs); + } + if (resolvedCrs->isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT)) { + return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs); + } + } catch (const std::exception &) { + } + } else { + auto outCrs = tryToIdentifyByName( + io::AuthorityFactory::ObjectType::COMPOUND_CRS); + const auto &components = compoundCrs->componentReferenceSystems(); + if (outCrs.get() != crs.get()) { + bool hasGeoid = false; + if (components.size() == 2) { + auto vertCRS = + dynamic_cast<crs::VerticalCRS *>(components[1].get()); + if (vertCRS && !vertCRS->geoidModel().empty()) { + hasGeoid = true; + } + } + if (!hasGeoid) { + return outCrs; + } + } + if (approxExtent || !extentOut) { + // If we still did not get a reliable extent, then try to + // resolve the components of the compoundCRS, and take the + // intersection of their extent. + extentOut = metadata::ExtentPtr(); + for (const auto &component : components) { + metadata::ExtentPtr componentExtent; + getResolvedCRS(component, authFactory, componentExtent); + if (extentOut && componentExtent) + extentOut = extentOut->intersection( + NN_NO_CHECK(componentExtent)); + else if (componentExtent) + extentOut = componentExtent; + } + } + } + } + return crs; +} + +//! @endcond + +} // namespace crs +NS_PROJ_END diff --git a/src/iso19111/operation/esriparammappings.cpp b/src/iso19111/operation/esriparammappings.cpp new file mode 100644 index 00000000..44886e95 --- /dev/null +++ b/src/iso19111/operation/esriparammappings.cpp @@ -0,0 +1,1123 @@ +// This file was generated by scripts/build_esri_projection_mapping.py. DO NOT +// EDIT ! + +/****************************************************************************** + * + * Project: PROJ + * Purpose: Mappings between ESRI projection and parameters names and WKT2 + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2019, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "esriparammappings.hpp" +#include "proj_constants.h" + +#include "proj/internal/internal.hpp" + +NS_PROJ_START + +using namespace internal; + +namespace operation { + +//! @cond Doxygen_Suppress + +const ESRIParamMapping paramsESRI_Plate_Carree[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +const ESRIParamMapping paramsESRI_Equidistant_Cylindrical[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Miller_Cylindrical[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Mercator[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +const ESRIParamMapping paramsESRI_Gauss_Kruger[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +const ESRIParamMapping paramsESRI_Transverse_Mercator[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Transverse_Mercator_Complex[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Albers[] = { + {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Sinusoidal[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Mollweide[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Eckert_I[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Eckert_II[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Eckert_III[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Eckert_IV[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Eckert_V[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Eckert_VI[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Gall_Stereographic[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Behrmann[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", true}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "30.0", true}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Winkel_I[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Winkel_II[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt1[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; +static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt2[] = { + {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; +static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt3[] = { + {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, + {"Scale_Factor", nullptr, 0, "1.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; +static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt4[] = { + {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_ELLIPSOID_SCALE_FACTOR, + EPSG_CODE_PARAMETER_ELLIPSOID_SCALE_FACTOR, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Polyconic[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Quartic_Authalic[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Loximuthal[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Central_Parallel", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Bonne[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping + paramsESRI_Hotine_Oblique_Mercator_Two_Point_Natural_Origin[] = { + {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false}, + {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false}, + {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false}, + {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false}, + {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Stereographic[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Equidistant_Conic[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Cassini[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", nullptr, 0, "1.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Van_der_Grinten_I[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Robinson[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Two_Point_Equidistant[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false}, + {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false}, + {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false}, + {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Azimuthal_Equidistant[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Lambert_Azimuthal_Equal_Area[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Cylindrical_Equal_Area[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping + paramsESRI_Hotine_Oblique_Mercator_Two_Point_Center[] = { + {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false}, + {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false}, + {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false}, + {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false}, + {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +const ESRIParamMapping + paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false}, + {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE, + EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +const ESRIParamMapping paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center[] = { + {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false}, + {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE, + EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Double_Stereographic[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Krovak_alt1[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Pseudo_Standard_Parallel_1", + EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, + EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, "0.0", false}, + {"Azimuth", EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS, + EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, + {"X_Scale", nullptr, 0, "1.0", false}, + {"Y_Scale", nullptr, 0, "1.0", false}, + {"XY_Plane_Rotation", nullptr, 0, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; +static const ESRIParamMapping paramsESRI_Krovak_alt2[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Pseudo_Standard_Parallel_1", + EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, + EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, "0.0", false}, + {"Azimuth", EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS, + EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, + {"X_Scale", nullptr, 0, "-1.0", false}, + {"Y_Scale", nullptr, 0, "1.0", false}, + {"XY_Plane_Rotation", nullptr, 0, "90.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_New_Zealand_Map_Grid[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Longitude_Of_Origin", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Orthographic[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Local[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Scale_Factor", nullptr, 0, "1.0", false}, + {"Azimuth", nullptr, 0, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Winkel_Tripel[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Aitoff[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Flat_Polar_Quartic[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Craster_Parabolic[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Gnomonic[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Times[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Vertical_Near_Side_Perspective[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, "0.0", false}, + {"Height", EPSG_NAME_PARAMETER_VIEWPOINT_HEIGHT, + EPSG_CODE_PARAMETER_VIEWPOINT_HEIGHT, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Stereographic_North_Pole[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Stereographic_South_Pole[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin[] = + {{"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false}, + {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE, + EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, + {"XY_Plane_Rotation", EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, + EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Center[] = { + {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false}, + {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE, + EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, + {"XY_Plane_Rotation", EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, + EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Goode_Homolosine_alt1[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Option", nullptr, 0, "1.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; +static const ESRIParamMapping paramsESRI_Goode_Homolosine_alt2[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Option", nullptr, 0, "2.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Equidistant_Cylindrical_Ellipsoidal[] = + {{"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Laborde_Oblique_Mercator[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false}, + {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE, + EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Gnomonic_Ellipsoidal[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Wagner_IV[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", nullptr, 0, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Wagner_V[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Wagner_VII[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Natural_Earth[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Natural_Earth_II[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Patterson[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Compact_Miller[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Geostationary_Satellite[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Height", "Satellite Height", 0, "0.0", false}, + {"Option", nullptr, 0, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Mercator_Auxiliary_Sphere[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Auxiliary_Sphere_Type", nullptr, 0, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Mercator_Variant_A[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Mercator_Variant_C[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, + {"Latitude_Of_Origin", nullptr, 0, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_Transverse_Cylindrical_Equal_Area[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIParamMapping paramsESRI_IGAC_Plano_Cartesiano[] = { + {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, + {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, + {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, + {"Height", EPSG_NAME_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, + EPSG_CODE_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, "0.0", false}, + {nullptr, nullptr, 0, "0.0", false}}; + +static const ESRIMethodMapping esriMappings[] = { + {"Plate_Carree", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, paramsESRI_Plate_Carree}, + {"Plate_Carree", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, + paramsESRI_Plate_Carree}, + {"Equidistant_Cylindrical", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, + paramsESRI_Equidistant_Cylindrical}, + {"Miller_Cylindrical", PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL, 0, + paramsESRI_Miller_Cylindrical}, + {"Mercator", EPSG_NAME_METHOD_MERCATOR_VARIANT_B, + EPSG_CODE_METHOD_MERCATOR_VARIANT_B, paramsESRI_Mercator}, + {"Gauss_Kruger", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, paramsESRI_Gauss_Kruger}, + {"Transverse_Mercator", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, paramsESRI_Transverse_Mercator}, + {"Transverse_Mercator_Complex", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, + paramsESRI_Transverse_Mercator_Complex}, + {"Albers", EPSG_NAME_METHOD_ALBERS_EQUAL_AREA, + EPSG_CODE_METHOD_ALBERS_EQUAL_AREA, paramsESRI_Albers}, + {"Sinusoidal", PROJ_WKT2_NAME_METHOD_SINUSOIDAL, 0, paramsESRI_Sinusoidal}, + {"Mollweide", PROJ_WKT2_NAME_METHOD_MOLLWEIDE, 0, paramsESRI_Mollweide}, + {"Eckert_I", PROJ_WKT2_NAME_METHOD_ECKERT_I, 0, paramsESRI_Eckert_I}, + {"Eckert_II", PROJ_WKT2_NAME_METHOD_ECKERT_II, 0, paramsESRI_Eckert_II}, + {"Eckert_III", PROJ_WKT2_NAME_METHOD_ECKERT_III, 0, paramsESRI_Eckert_III}, + {"Eckert_IV", PROJ_WKT2_NAME_METHOD_ECKERT_IV, 0, paramsESRI_Eckert_IV}, + {"Eckert_V", PROJ_WKT2_NAME_METHOD_ECKERT_V, 0, paramsESRI_Eckert_V}, + {"Eckert_VI", PROJ_WKT2_NAME_METHOD_ECKERT_VI, 0, paramsESRI_Eckert_VI}, + {"Gall_Stereographic", PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC, 0, + paramsESRI_Gall_Stereographic}, + {"Behrmann", EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, + EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, paramsESRI_Behrmann}, + {"Winkel_I", "Winkel I", 0, paramsESRI_Winkel_I}, + {"Winkel_II", "Winkel II", 0, paramsESRI_Winkel_II}, + {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, + paramsESRI_Lambert_Conformal_Conic_alt1}, + {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + paramsESRI_Lambert_Conformal_Conic_alt2}, + {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + paramsESRI_Lambert_Conformal_Conic_alt3}, + {"Lambert_Conformal_Conic", + EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, + paramsESRI_Lambert_Conformal_Conic_alt4}, + {"Polyconic", EPSG_NAME_METHOD_AMERICAN_POLYCONIC, + EPSG_CODE_METHOD_AMERICAN_POLYCONIC, paramsESRI_Polyconic}, + {"Quartic_Authalic", "Quartic Authalic", 0, paramsESRI_Quartic_Authalic}, + {"Loximuthal", "Loximuthal", 0, paramsESRI_Loximuthal}, + {"Bonne", EPSG_NAME_METHOD_BONNE, EPSG_CODE_METHOD_BONNE, paramsESRI_Bonne}, + {"Hotine_Oblique_Mercator_Two_Point_Natural_Origin", + PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0, + paramsESRI_Hotine_Oblique_Mercator_Two_Point_Natural_Origin}, + {"Stereographic", PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC, 0, + paramsESRI_Stereographic}, + {"Equidistant_Conic", PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC, 0, + paramsESRI_Equidistant_Conic}, + {"Cassini", EPSG_NAME_METHOD_CASSINI_SOLDNER, + EPSG_CODE_METHOD_CASSINI_SOLDNER, paramsESRI_Cassini}, + {"Van_der_Grinten_I", PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN, 0, + paramsESRI_Van_der_Grinten_I}, + {"Robinson", PROJ_WKT2_NAME_METHOD_ROBINSON, 0, paramsESRI_Robinson}, + {"Two_Point_Equidistant", PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, 0, + paramsESRI_Two_Point_Equidistant}, + {"Azimuthal_Equidistant", EPSG_NAME_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, + EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, + paramsESRI_Azimuthal_Equidistant}, + {"Lambert_Azimuthal_Equal_Area", + EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, + EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, + paramsESRI_Lambert_Azimuthal_Equal_Area}, + {"Cylindrical_Equal_Area", EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, + EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, + paramsESRI_Cylindrical_Equal_Area}, + {"Hotine_Oblique_Mercator_Two_Point_Center", + PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0, + paramsESRI_Hotine_Oblique_Mercator_Two_Point_Center}, + {"Hotine_Oblique_Mercator_Azimuth_Natural_Origin", + EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, + paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin}, + {"Hotine_Oblique_Mercator_Azimuth_Center", + EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, + paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center}, + {"Double_Stereographic", EPSG_NAME_METHOD_OBLIQUE_STEREOGRAPHIC, + EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC, paramsESRI_Double_Stereographic}, + {"Krovak", EPSG_NAME_METHOD_KROVAK, EPSG_CODE_METHOD_KROVAK, + paramsESRI_Krovak_alt1}, + {"Krovak", EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED, + EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED, paramsESRI_Krovak_alt2}, + {"New_Zealand_Map_Grid", EPSG_NAME_METHOD_NZMG, EPSG_CODE_METHOD_NZMG, + paramsESRI_New_Zealand_Map_Grid}, + {"Orthographic", PROJ_WKT2_NAME_ORTHOGRAPHIC_SPHERICAL, 0, + paramsESRI_Orthographic}, + {"Local", EPSG_NAME_METHOD_ORTHOGRAPHIC, EPSG_CODE_METHOD_ORTHOGRAPHIC, + paramsESRI_Local}, + {"Winkel_Tripel", "Winkel Tripel", 0, paramsESRI_Winkel_Tripel}, + {"Aitoff", "Aitoff", 0, paramsESRI_Aitoff}, + {"Flat_Polar_Quartic", PROJ_WKT2_NAME_METHOD_FLAT_POLAR_QUARTIC, 0, + paramsESRI_Flat_Polar_Quartic}, + {"Craster_Parabolic", "Craster Parabolic", 0, paramsESRI_Craster_Parabolic}, + {"Gnomonic", PROJ_WKT2_NAME_METHOD_GNOMONIC, 0, paramsESRI_Gnomonic}, + {"Times", PROJ_WKT2_NAME_METHOD_TIMES, 0, paramsESRI_Times}, + {"Vertical_Near_Side_Perspective", EPSG_NAME_METHOD_VERTICAL_PERSPECTIVE, + EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE, + paramsESRI_Vertical_Near_Side_Perspective}, + {"Stereographic_North_Pole", EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, + paramsESRI_Stereographic_North_Pole}, + {"Stereographic_South_Pole", EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, + paramsESRI_Stereographic_South_Pole}, + {"Rectified_Skew_Orthomorphic_Natural_Origin", + EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, + paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin}, + {"Rectified_Skew_Orthomorphic_Center", + EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, + paramsESRI_Rectified_Skew_Orthomorphic_Center}, + {"Goode_Homolosine", PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, 0, + paramsESRI_Goode_Homolosine_alt1}, + {"Goode_Homolosine", + PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE_OCEAN, 0, + paramsESRI_Goode_Homolosine_alt2}, + {"Equidistant_Cylindrical_Ellipsoidal", + EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, + paramsESRI_Equidistant_Cylindrical_Ellipsoidal}, + {"Laborde_Oblique_Mercator", EPSG_NAME_METHOD_LABORDE_OBLIQUE_MERCATOR, + EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR, + paramsESRI_Laborde_Oblique_Mercator}, + {"Gnomonic_Ellipsoidal", PROJ_WKT2_NAME_METHOD_GNOMONIC, 0, + paramsESRI_Gnomonic_Ellipsoidal}, + {"Wagner_IV", PROJ_WKT2_NAME_METHOD_WAGNER_IV, 0, paramsESRI_Wagner_IV}, + {"Wagner_V", PROJ_WKT2_NAME_METHOD_WAGNER_V, 0, paramsESRI_Wagner_V}, + {"Wagner_VII", PROJ_WKT2_NAME_METHOD_WAGNER_VII, 0, paramsESRI_Wagner_VII}, + {"Natural_Earth", PROJ_WKT2_NAME_METHOD_NATURAL_EARTH, 0, + paramsESRI_Natural_Earth}, + {"Natural_Earth_II", PROJ_WKT2_NAME_METHOD_NATURAL_EARTH_II, 0, + paramsESRI_Natural_Earth_II}, + {"Patterson", PROJ_WKT2_NAME_METHOD_PATTERSON, 0, paramsESRI_Patterson}, + {"Compact_Miller", PROJ_WKT2_NAME_METHOD_COMPACT_MILLER, 0, + paramsESRI_Compact_Miller}, + {"Geostationary_Satellite", + PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y, 0, + paramsESRI_Geostationary_Satellite}, + {"Mercator_Auxiliary_Sphere", + EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, + EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, + paramsESRI_Mercator_Auxiliary_Sphere}, + {"Mercator_Variant_A", EPSG_NAME_METHOD_MERCATOR_VARIANT_A, + EPSG_CODE_METHOD_MERCATOR_VARIANT_A, paramsESRI_Mercator_Variant_A}, + {"Mercator_Variant_C", EPSG_NAME_METHOD_MERCATOR_VARIANT_B, + EPSG_CODE_METHOD_MERCATOR_VARIANT_B, paramsESRI_Mercator_Variant_C}, + {"Transverse_Cylindrical_Equal_Area", "Transverse Cylindrical Equal Area", + 0, paramsESRI_Transverse_Cylindrical_Equal_Area}, + {"IGAC_Plano_Cartesiano", EPSG_NAME_METHOD_COLOMBIA_URBAN, + EPSG_CODE_METHOD_COLOMBIA_URBAN, paramsESRI_IGAC_Plano_Cartesiano}, +}; + +// --------------------------------------------------------------------------- + +const ESRIMethodMapping *getEsriMappings(size_t &nElts) { + nElts = sizeof(esriMappings) / sizeof(esriMappings[0]); + return esriMappings; +} + +// --------------------------------------------------------------------------- + +std::vector<const ESRIMethodMapping *> +getMappingsFromESRI(const std::string &esri_name) { + std::vector<const ESRIMethodMapping *> res; + for (const auto &mapping : esriMappings) { + if (ci_equal(esri_name, mapping.esri_name)) { + res.push_back(&mapping); + } + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END diff --git a/src/iso19111/operation/esriparammappings.hpp b/src/iso19111/operation/esriparammappings.hpp new file mode 100644 index 00000000..373c1f22 --- /dev/null +++ b/src/iso19111/operation/esriparammappings.hpp @@ -0,0 +1,86 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef ESRIPARAMMAPPINGS_HPP +#define ESRIPARAMMAPPINGS_HPP + +#include "proj/coordinateoperation.hpp" +#include "proj/util.hpp" + +#include "esriparammappings.hpp" +#include <vector> + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +struct ESRIParamMapping { + const char *esri_name; + const char *wkt2_name; + int epsg_code; + const char *fixed_value; + bool is_fixed_value; +}; + +struct ESRIMethodMapping { + const char *esri_name; + const char *wkt2_name; + int epsg_code; + const ESRIParamMapping *const params; +}; + +extern const ESRIParamMapping paramsESRI_Plate_Carree[]; +extern const ESRIParamMapping paramsESRI_Equidistant_Cylindrical[]; +extern const ESRIParamMapping paramsESRI_Gauss_Kruger[]; +extern const ESRIParamMapping paramsESRI_Transverse_Mercator[]; +extern const ESRIParamMapping + paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin[]; +extern const ESRIParamMapping + paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin[]; +extern const ESRIParamMapping + paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center[]; +extern const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Center[]; + +const ESRIMethodMapping *getEsriMappings(size_t &nElts); + +std::vector<const ESRIMethodMapping *> +getMappingsFromESRI(const std::string &esri_name); + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END + +#endif // ESRIPARAMMAPPINGS_HPP diff --git a/src/iso19111/operation/operationmethod_private.hpp b/src/iso19111/operation/operationmethod_private.hpp new file mode 100644 index 00000000..83d29026 --- /dev/null +++ b/src/iso19111/operation/operationmethod_private.hpp @@ -0,0 +1,56 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef OPERATIONMETHOD_PRIVATE_HPP +#define OPERATIONMETHOD_PRIVATE_HPP + +#include "proj/coordinateoperation.hpp" +#include "proj/util.hpp" + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct OperationMethod::Private { + util::optional<std::string> formula_{}; + util::optional<metadata::Citation> formulaCitation_{}; + std::vector<GeneralOperationParameterNNPtr> parameters_{}; + std::string projMethodOverride_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END + +#endif // OPERATIONMETHOD_PRIVATE_HPP diff --git a/src/iso19111/operation/oputils.cpp b/src/iso19111/operation/oputils.cpp new file mode 100644 index 00000000..b5834edf --- /dev/null +++ b/src/iso19111/operation/oputils.cpp @@ -0,0 +1,643 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include <string.h> + +#include "proj/coordinateoperation.hpp" +#include "proj/crs.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include "oputils.hpp" +#include "parammappings.hpp" + +#include "proj_constants.h" + +// --------------------------------------------------------------------------- + +NS_PROJ_START + +using namespace internal; + +namespace operation { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +const char *BALLPARK_GEOCENTRIC_TRANSLATION = "Ballpark geocentric translation"; +const char *NULL_GEOGRAPHIC_OFFSET = "Null geographic offset"; +const char *NULL_GEOCENTRIC_TRANSLATION = "Null geocentric translation"; +const char *BALLPARK_GEOGRAPHIC_OFFSET = "Ballpark geographic offset"; +const char *BALLPARK_VERTICAL_TRANSFORMATION = + " (ballpark vertical transformation)"; +const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT = + " (ballpark vertical transformation, without ellipsoid height to vertical " + "height correction)"; + +// --------------------------------------------------------------------------- + +OperationParameterNNPtr createOpParamNameEPSGCode(int code) { + const char *name = OperationParameter::getNameForEPSGCode(code); + assert(name); + return OperationParameter::create(createMapNameEPSGCode(name, code)); +} + +// --------------------------------------------------------------------------- + +util::PropertyMap createMethodMapNameEPSGCode(int code) { + const char *name = nullptr; + size_t nMethodNameCodes = 0; + const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes); + for (size_t i = 0; i < nMethodNameCodes; ++i) { + const auto &tuple = methodNameCodes[i]; + if (tuple.epsg_code == code) { + name = tuple.name; + break; + } + } + assert(name); + return createMapNameEPSGCode(name, code); +} + +// --------------------------------------------------------------------------- + +util::PropertyMap createMapNameEPSGCode(const std::string &name, int code) { + return util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, name) + .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, code); +} + +// --------------------------------------------------------------------------- + +util::PropertyMap createMapNameEPSGCode(const char *name, int code) { + return util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, name) + .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, code); +} + +// --------------------------------------------------------------------------- + +util::PropertyMap &addDomains(util::PropertyMap &map, + const common::ObjectUsage *obj) { + + auto ar = util::ArrayOfBaseObject::create(); + for (const auto &domain : obj->domains()) { + ar->add(domain); + } + if (!ar->empty()) { + map.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, ar); + } + return map; +} + +// --------------------------------------------------------------------------- + +static const char *getCRSQualifierStr(const crs::CRSPtr &crs) { + auto geod = dynamic_cast<crs::GeodeticCRS *>(crs.get()); + if (geod) { + if (geod->isGeocentric()) { + return " (geocentric)"; + } + auto geog = dynamic_cast<crs::GeographicCRS *>(geod); + if (geog) { + if (geog->coordinateSystem()->axisList().size() == 2) { + return " (geog2D)"; + } else { + return " (geog3D)"; + } + } + } + return ""; +} + +// --------------------------------------------------------------------------- + +std::string buildOpName(const char *opType, const crs::CRSPtr &source, + const crs::CRSPtr &target) { + std::string res(opType); + const auto &srcName = source->nameStr(); + const auto &targetName = target->nameStr(); + const char *srcQualifier = ""; + const char *targetQualifier = ""; + if (srcName == targetName) { + srcQualifier = getCRSQualifierStr(source); + targetQualifier = getCRSQualifierStr(target); + if (strcmp(srcQualifier, targetQualifier) == 0) { + srcQualifier = ""; + targetQualifier = ""; + } + } + res += " from "; + res += srcName; + res += srcQualifier; + res += " to "; + res += targetName; + res += targetQualifier; + return res; +} + +// --------------------------------------------------------------------------- + +void addModifiedIdentifier(util::PropertyMap &map, + const common::IdentifiedObject *obj, bool inverse, + bool derivedFrom) { + // If original operation is AUTH:CODE, then assign INVERSE(AUTH):CODE + // as identifier. + + auto ar = util::ArrayOfBaseObject::create(); + for (const auto &idSrc : obj->identifiers()) { + auto authName = *(idSrc->codeSpace()); + const auto &srcCode = idSrc->code(); + if (derivedFrom) { + authName = concat("DERIVED_FROM(", authName, ")"); + } + if (inverse) { + if (starts_with(authName, "INVERSE(") && authName.back() == ')') { + authName = authName.substr(strlen("INVERSE(")); + authName.resize(authName.size() - 1); + } else { + authName = concat("INVERSE(", authName, ")"); + } + } + auto idsProp = util::PropertyMap().set( + metadata::Identifier::CODESPACE_KEY, authName); + ar->add(metadata::Identifier::create(srcCode, idsProp)); + } + if (!ar->empty()) { + map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar); + } +} + +// --------------------------------------------------------------------------- + +util::PropertyMap +createPropertiesForInverse(const OperationMethodNNPtr &method) { + util::PropertyMap map; + + const std::string &forwardName = method->nameStr(); + if (!forwardName.empty()) { + if (starts_with(forwardName, INVERSE_OF)) { + map.set(common::IdentifiedObject::NAME_KEY, + forwardName.substr(INVERSE_OF.size())); + } else { + map.set(common::IdentifiedObject::NAME_KEY, + INVERSE_OF + forwardName); + } + } + + addModifiedIdentifier(map, method.get(), true, false); + + return map; +} + +// --------------------------------------------------------------------------- + +util::PropertyMap createPropertiesForInverse(const CoordinateOperation *op, + bool derivedFrom, + bool approximateInversion) { + assert(op); + util::PropertyMap map; + + // The domain(s) are unchanged by the inverse operation + addDomains(map, op); + + const std::string &forwardName = op->nameStr(); + + // Forge a name for the inverse, either from the forward name, or + // from the source and target CRS names + const char *opType; + if (starts_with(forwardName, BALLPARK_GEOCENTRIC_TRANSLATION)) { + opType = BALLPARK_GEOCENTRIC_TRANSLATION; + } else if (starts_with(forwardName, BALLPARK_GEOGRAPHIC_OFFSET)) { + opType = BALLPARK_GEOGRAPHIC_OFFSET; + } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) { + opType = NULL_GEOGRAPHIC_OFFSET; + } else if (starts_with(forwardName, NULL_GEOCENTRIC_TRANSLATION)) { + opType = NULL_GEOCENTRIC_TRANSLATION; + } else if (dynamic_cast<const Transformation *>(op) || + starts_with(forwardName, "Transformation from ")) { + opType = "Transformation"; + } else if (dynamic_cast<const Conversion *>(op)) { + opType = "Conversion"; + } else { + opType = "Operation"; + } + + auto sourceCRS = op->sourceCRS(); + auto targetCRS = op->targetCRS(); + std::string name; + if (!forwardName.empty()) { + if (dynamic_cast<const Transformation *>(op) == nullptr && + dynamic_cast<const ConcatenatedOperation *>(op) == nullptr && + (starts_with(forwardName, INVERSE_OF) || + forwardName.find(" + ") != std::string::npos)) { + std::vector<std::string> tokens; + std::string curToken; + bool inString = false; + for (size_t i = 0; i < forwardName.size(); ++i) { + if (inString) { + curToken += forwardName[i]; + if (forwardName[i] == '\'') { + inString = false; + } + } else if (i + 3 < forwardName.size() && + memcmp(&forwardName[i], " + ", 3) == 0) { + tokens.push_back(curToken); + curToken.clear(); + i += 2; + } else if (forwardName[i] == '\'') { + inString = true; + curToken += forwardName[i]; + } else { + curToken += forwardName[i]; + } + } + if (!curToken.empty()) { + tokens.push_back(curToken); + } + for (size_t i = tokens.size(); i > 0;) { + i--; + if (!name.empty()) { + name += " + "; + } + if (starts_with(tokens[i], INVERSE_OF)) { + name += tokens[i].substr(INVERSE_OF.size()); + } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME || + tokens[i] == AXIS_ORDER_CHANGE_3D_NAME) { + name += tokens[i]; + } else { + name += INVERSE_OF + tokens[i]; + } + } + } else if (!sourceCRS || !targetCRS || + forwardName != buildOpName(opType, sourceCRS, targetCRS)) { + if (forwardName.find(" + ") != std::string::npos) { + name = INVERSE_OF + '\'' + forwardName + '\''; + } else { + name = INVERSE_OF + forwardName; + } + } + } + if (name.empty() && sourceCRS && targetCRS) { + name = buildOpName(opType, targetCRS, sourceCRS); + } + if (approximateInversion) { + name += " (approx. inversion)"; + } + + if (!name.empty()) { + map.set(common::IdentifiedObject::NAME_KEY, name); + } + + const std::string &remarks = op->remarks(); + if (!remarks.empty()) { + map.set(common::IdentifiedObject::REMARKS_KEY, remarks); + } + + addModifiedIdentifier(map, op, true, derivedFrom); + + const auto so = dynamic_cast<const SingleOperation *>(op); + if (so) { + const int soMethodEPSGCode = so->method()->getEPSGCode(); + if (soMethodEPSGCode > 0) { + map.set("OPERATION_METHOD_EPSG_CODE", soMethodEPSGCode); + } + } + + return map; +} + +// --------------------------------------------------------------------------- + +util::PropertyMap addDefaultNameIfNeeded(const util::PropertyMap &properties, + const std::string &defaultName) { + if (!properties.get(common::IdentifiedObject::NAME_KEY)) { + return util::PropertyMap(properties) + .set(common::IdentifiedObject::NAME_KEY, defaultName); + } else { + return properties; + } +} + +// --------------------------------------------------------------------------- + +static std::string createEntryEqParam(const std::string &a, + const std::string &b) { + return a < b ? a + b : b + a; +} + +static std::set<std::string> buildSetEquivalentParameters() { + + std::set<std::string> set; + + const char *const listOfEquivalentParameterNames[][7] = { + {"latitude_of_point_1", "Latitude_Of_1st_Point", nullptr}, + {"longitude_of_point_1", "Longitude_Of_1st_Point", nullptr}, + {"latitude_of_point_2", "Latitude_Of_2nd_Point", nullptr}, + {"longitude_of_point_2", "Longitude_Of_2nd_Point", nullptr}, + + {"satellite_height", "height", nullptr}, + + {EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, + EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, nullptr}, + + {EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, + EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, nullptr}, + + {EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR, + EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, + EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, nullptr}, + + {WKT1_LATITUDE_OF_ORIGIN, WKT1_LATITUDE_OF_CENTER, + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, + EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, "Central_Parallel", + nullptr}, + + {WKT1_CENTRAL_MERIDIAN, WKT1_LONGITUDE_OF_CENTER, + EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, + EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, nullptr}, + + {"pseudo_standard_parallel_1", WKT1_STANDARD_PARALLEL_1, nullptr}, + }; + + for (const auto ¶mList : listOfEquivalentParameterNames) { + for (size_t i = 0; paramList[i]; i++) { + auto a = metadata::Identifier::canonicalizeName(paramList[i]); + for (size_t j = i + 1; paramList[j]; j++) { + auto b = metadata::Identifier::canonicalizeName(paramList[j]); + set.insert(createEntryEqParam(a, b)); + } + } + } + return set; +} + +bool areEquivalentParameters(const std::string &a, const std::string &b) { + + static const std::set<std::string> setEquivalentParameters = + buildSetEquivalentParameters(); + + auto a_can = metadata::Identifier::canonicalizeName(a); + auto b_can = metadata::Identifier::canonicalizeName(b); + return setEquivalentParameters.find(createEntryEqParam(a_can, b_can)) != + setEquivalentParameters.end(); +} + +// --------------------------------------------------------------------------- + +bool isTimeDependent(const std::string &methodName) { + return ci_find(methodName, "Time dependent") != std::string::npos || + ci_find(methodName, "Time-dependent") != std::string::npos; +} + +// --------------------------------------------------------------------------- + +std::string computeConcatenatedName( + const std::vector<CoordinateOperationNNPtr> &flattenOps) { + std::string name; + for (const auto &subOp : flattenOps) { + if (!name.empty()) { + name += " + "; + } + const auto &l_name = subOp->nameStr(); + if (l_name.empty()) { + name += "unnamed"; + } else { + name += l_name; + } + } + return name; +} + +// --------------------------------------------------------------------------- + +metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op, + bool conversionExtentIsWorld, + bool &emptyIntersection) { + auto conv = dynamic_cast<const Conversion *>(op.get()); + if (conv) { + emptyIntersection = false; + return metadata::Extent::WORLD; + } + const auto &domains = op->domains(); + if (!domains.empty()) { + emptyIntersection = false; + return domains[0]->domainOfValidity(); + } + auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get()); + if (!concatenated) { + emptyIntersection = false; + return nullptr; + } + return getExtent(concatenated->operations(), conversionExtentIsWorld, + emptyIntersection); +} + +// --------------------------------------------------------------------------- + +static const metadata::ExtentPtr nullExtent{}; + +const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs) { + const auto &domains = crs->domains(); + if (!domains.empty()) { + return domains[0]->domainOfValidity(); + } + const auto *boundCRS = dynamic_cast<const crs::BoundCRS *>(crs.get()); + if (boundCRS) { + return getExtent(boundCRS->baseCRS()); + } + return nullExtent; +} + +const metadata::ExtentPtr getExtentPossiblySynthetized(const crs::CRSNNPtr &crs, + bool &approxOut) { + const auto &rawExtent(getExtent(crs)); + approxOut = false; + if (rawExtent) + return rawExtent; + const auto compoundCRS = dynamic_cast<const crs::CompoundCRS *>(crs.get()); + if (compoundCRS) { + // For a compoundCRS, take the intersection of the extent of its + // components. + const auto &components = compoundCRS->componentReferenceSystems(); + metadata::ExtentPtr extent; + approxOut = true; + for (const auto &component : components) { + const auto &componentExtent(getExtent(component)); + if (extent && componentExtent) + extent = extent->intersection(NN_NO_CHECK(componentExtent)); + else if (componentExtent) + extent = componentExtent; + } + return extent; + } + return rawExtent; +} + +// --------------------------------------------------------------------------- + +metadata::ExtentPtr getExtent(const std::vector<CoordinateOperationNNPtr> &ops, + bool conversionExtentIsWorld, + bool &emptyIntersection) { + metadata::ExtentPtr res = nullptr; + for (const auto &subop : ops) { + + const auto &subExtent = + getExtent(subop, conversionExtentIsWorld, emptyIntersection); + if (!subExtent) { + if (emptyIntersection) { + return nullptr; + } + continue; + } + if (res == nullptr) { + res = subExtent; + } else { + res = res->intersection(NN_NO_CHECK(subExtent)); + if (!res) { + emptyIntersection = true; + return nullptr; + } + } + } + emptyIntersection = false; + return res; +} + +// --------------------------------------------------------------------------- + +// Returns the accuracy of an operation, or -1 if unknown +double getAccuracy(const CoordinateOperationNNPtr &op) { + + if (dynamic_cast<const Conversion *>(op.get())) { + // A conversion is perfectly accurate. + return 0.0; + } + + double accuracy = -1.0; + const auto &accuracies = op->coordinateOperationAccuracies(); + if (!accuracies.empty()) { + try { + accuracy = c_locale_stod(accuracies[0]->value()); + } catch (const std::exception &) { + } + } else { + auto concatenated = + dynamic_cast<const ConcatenatedOperation *>(op.get()); + if (concatenated) { + accuracy = getAccuracy(concatenated->operations()); + } + } + return accuracy; +} + +// --------------------------------------------------------------------------- + +// Returns the accuracy of a set of concatenated operations, or -1 if unknown +double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops) { + double accuracy = -1.0; + for (const auto &subop : ops) { + const double subops_accuracy = getAccuracy(subop); + if (subops_accuracy < 0.0) { + return -1.0; + } + if (accuracy < 0.0) { + accuracy = 0.0; + } + accuracy += subops_accuracy; + } + return accuracy; +} + +// --------------------------------------------------------------------------- + +void exportSourceCRSAndTargetCRSToWKT(const CoordinateOperation *co, + io::WKTFormatter *formatter) { + auto l_sourceCRS = co->sourceCRS(); + assert(l_sourceCRS); + auto l_targetCRS = co->targetCRS(); + assert(l_targetCRS); + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + const bool canExportCRSId = + (isWKT2 && formatter->use2019Keywords() && + !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId())); + + const bool hasDomains = !co->domains().empty(); + if (hasDomains) { + formatter->pushDisableUsage(); + } + + formatter->startNode(io::WKTConstants::SOURCECRS, false); + if (canExportCRSId && !l_sourceCRS->identifiers().empty()) { + // fake that top node has no id, so that the sourceCRS id is + // considered + formatter->pushHasId(false); + l_sourceCRS->_exportToWKT(formatter); + formatter->popHasId(); + } else { + l_sourceCRS->_exportToWKT(formatter); + } + formatter->endNode(); + + formatter->startNode(io::WKTConstants::TARGETCRS, false); + if (canExportCRSId && !l_targetCRS->identifiers().empty()) { + // fake that top node has no id, so that the targetCRS id is + // considered + formatter->pushHasId(false); + l_targetCRS->_exportToWKT(formatter); + formatter->popHasId(); + } else { + l_targetCRS->_exportToWKT(formatter); + } + formatter->endNode(); + + if (hasDomains) { + formatter->popDisableUsage(); + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END diff --git a/src/iso19111/operation/oputils.hpp b/src/iso19111/operation/oputils.hpp new file mode 100644 index 00000000..8c2ad1f0 --- /dev/null +++ b/src/iso19111/operation/oputils.hpp @@ -0,0 +1,121 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef OPUTILS_HPP +#define OPUTILS_HPP + +#include "proj/coordinateoperation.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +extern const common::Measure nullMeasure; + +extern const std::string INVERSE_OF; + +extern const char *BALLPARK_GEOCENTRIC_TRANSLATION; +extern const char *NULL_GEOGRAPHIC_OFFSET; +extern const char *NULL_GEOCENTRIC_TRANSLATION; +extern const char *BALLPARK_GEOGRAPHIC_OFFSET; +extern const char *BALLPARK_VERTICAL_TRANSFORMATION; +extern const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT; + +extern const std::string AXIS_ORDER_CHANGE_2D_NAME; +extern const std::string AXIS_ORDER_CHANGE_3D_NAME; + +OperationParameterNNPtr createOpParamNameEPSGCode(int code); + +util::PropertyMap createMethodMapNameEPSGCode(int code); + +util::PropertyMap createMapNameEPSGCode(const std::string &name, int code); + +util::PropertyMap createMapNameEPSGCode(const char *name, int code); + +util::PropertyMap &addDomains(util::PropertyMap &map, + const common::ObjectUsage *obj); + +std::string buildOpName(const char *opType, const crs::CRSPtr &source, + const crs::CRSPtr &target); + +void addModifiedIdentifier(util::PropertyMap &map, + const common::IdentifiedObject *obj, bool inverse, + bool derivedFrom); +util::PropertyMap +createPropertiesForInverse(const OperationMethodNNPtr &method); + +util::PropertyMap createPropertiesForInverse(const CoordinateOperation *op, + bool derivedFrom, + bool approximateInversion); + +util::PropertyMap addDefaultNameIfNeeded(const util::PropertyMap &properties, + const std::string &defaultName); + +bool areEquivalentParameters(const std::string &a, const std::string &b); + +bool isTimeDependent(const std::string &methodName); + +std::string computeConcatenatedName( + const std::vector<CoordinateOperationNNPtr> &flattenOps); + +metadata::ExtentPtr getExtent(const std::vector<CoordinateOperationNNPtr> &ops, + bool conversionExtentIsWorld, + bool &emptyIntersection); + +metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op, + bool conversionExtentIsWorld, + bool &emptyIntersection); + +const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs); + +const metadata::ExtentPtr getExtentPossiblySynthetized(const crs::CRSNNPtr &crs, + bool &approxOut); + +double getAccuracy(const CoordinateOperationNNPtr &op); + +double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops); + +void exportSourceCRSAndTargetCRSToWKT(const CoordinateOperation *co, + io::WKTFormatter *formatter); + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END + +#endif // OPUTILS_HPP diff --git a/src/iso19111/operation/parammappings.cpp b/src/iso19111/operation/parammappings.cpp new file mode 100644 index 00000000..de5f4c7e --- /dev/null +++ b/src/iso19111/operation/parammappings.cpp @@ -0,0 +1,1551 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "parammappings.hpp" +#include "oputils.hpp" +#include "proj_constants.h" + +#include "proj/internal/internal.hpp" + +NS_PROJ_START + +using namespace internal; + +namespace operation { + +//! @cond Doxygen_Suppress + +const char *WKT1_LATITUDE_OF_ORIGIN = "latitude_of_origin"; +const char *WKT1_CENTRAL_MERIDIAN = "central_meridian"; +const char *WKT1_SCALE_FACTOR = "scale_factor"; +const char *WKT1_FALSE_EASTING = "false_easting"; +const char *WKT1_FALSE_NORTHING = "false_northing"; +const char *WKT1_STANDARD_PARALLEL_1 = "standard_parallel_1"; +const char *WKT1_STANDARD_PARALLEL_2 = "standard_parallel_2"; +const char *WKT1_LATITUDE_OF_CENTER = "latitude_of_center"; +const char *WKT1_LONGITUDE_OF_CENTER = "longitude_of_center"; +const char *WKT1_AZIMUTH = "azimuth"; +const char *WKT1_RECTIFIED_GRID_ANGLE = "rectified_grid_angle"; + +static const char *lat_0 = "lat_0"; +static const char *lat_1 = "lat_1"; +static const char *lat_2 = "lat_2"; +static const char *lat_ts = "lat_ts"; +static const char *lon_0 = "lon_0"; +static const char *lon_1 = "lon_1"; +static const char *lon_2 = "lon_2"; +static const char *lonc = "lonc"; +static const char *alpha = "alpha"; +static const char *gamma = "gamma"; +static const char *k_0 = "k_0"; +static const char *k = "k"; +static const char *x_0 = "x_0"; +static const char *y_0 = "y_0"; +static const char *h = "h"; + +// --------------------------------------------------------------------------- + +const ParamMapping paramLatitudeNatOrigin = { + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_ORIGIN, + common::UnitOfMeasure::Type::ANGULAR, lat_0}; + +static const ParamMapping paramLongitudeNatOrigin = { + EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, WKT1_CENTRAL_MERIDIAN, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping paramScaleFactor = { + EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR, + common::UnitOfMeasure::Type::SCALE, k_0}; + +static const ParamMapping paramScaleFactorK = { + EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR, + common::UnitOfMeasure::Type::SCALE, k}; + +static const ParamMapping paramFalseEasting = { + EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, + WKT1_FALSE_EASTING, common::UnitOfMeasure::Type::LINEAR, x_0}; + +static const ParamMapping paramFalseNorthing = { + EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, + WKT1_FALSE_NORTHING, common::UnitOfMeasure::Type::LINEAR, y_0}; + +static const ParamMapping paramLatitudeFalseOrigin = { + EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, WKT1_LATITUDE_OF_ORIGIN, + common::UnitOfMeasure::Type::ANGULAR, lat_0}; + +static const ParamMapping paramLongitudeFalseOrigin = { + EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, WKT1_CENTRAL_MERIDIAN, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping paramFalseEastingOrigin = { + EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, WKT1_FALSE_EASTING, + common::UnitOfMeasure::Type::LINEAR, x_0}; + +static const ParamMapping paramFalseNorthingOrigin = { + EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, WKT1_FALSE_NORTHING, + common::UnitOfMeasure::Type::LINEAR, y_0}; + +static const ParamMapping paramLatitude1stStdParallel = { + EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, WKT1_STANDARD_PARALLEL_1, + common::UnitOfMeasure::Type::ANGULAR, lat_1}; + +static const ParamMapping paramLatitude2ndStdParallel = { + EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, WKT1_STANDARD_PARALLEL_2, + common::UnitOfMeasure::Type::ANGULAR, lat_2}; + +static const ParamMapping *const paramsNatOriginScale[] = { + ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mScaleFactor, + ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsNatOriginScaleK[] = { + ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mScaleFactorK, + ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLatFirstPoint = { + "Latitude of 1st point", 0, "Latitude_Of_1st_Point", + common::UnitOfMeasure::Type::ANGULAR, lat_1}; +static const ParamMapping paramLongFirstPoint = { + "Longitude of 1st point", 0, "Longitude_Of_1st_Point", + common::UnitOfMeasure::Type::ANGULAR, lon_1}; +static const ParamMapping paramLatSecondPoint = { + "Latitude of 2nd point", 0, "Latitude_Of_2nd_Point", + common::UnitOfMeasure::Type::ANGULAR, lat_2}; +static const ParamMapping paramLongSecondPoint = { + "Longitude of 2nd point", 0, "Longitude_Of_2nd_Point", + common::UnitOfMeasure::Type::ANGULAR, lon_2}; + +static const ParamMapping *const paramsTPEQD[] = {¶mLatFirstPoint, + ¶mLongFirstPoint, + ¶mLatSecondPoint, + ¶mLongSecondPoint, + ¶mFalseEasting, + ¶mFalseNorthing, + nullptr}; + +static const ParamMapping *const paramsTMG[] = { + ¶mLatitudeFalseOrigin, ¶mLongitudeFalseOrigin, + ¶mFalseEastingOrigin, ¶mFalseNorthingOrigin, nullptr}; + +static const ParamMapping paramLatFalseOriginLatOfCenter = { + EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, WKT1_LATITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lat_0}; + +static const ParamMapping paramLongFalseOriginLongOfCenter = { + EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, WKT1_LONGITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping *const paramsAEA[] = { + ¶mLatFalseOriginLatOfCenter, + ¶mLongFalseOriginLongOfCenter, + ¶mLatitude1stStdParallel, + ¶mLatitude2ndStdParallel, + ¶mFalseEastingOrigin, + ¶mFalseNorthingOrigin, + nullptr}; + +static const ParamMapping *const paramsLCC2SP[] = { + ¶mLatitudeFalseOrigin, + ¶mLongitudeFalseOrigin, + ¶mLatitude1stStdParallel, + ¶mLatitude2ndStdParallel, + ¶mFalseEastingOrigin, + ¶mFalseNorthingOrigin, + nullptr, +}; + +static const ParamMapping paramEllipsoidScaleFactor = { + EPSG_NAME_PARAMETER_ELLIPSOID_SCALE_FACTOR, + EPSG_CODE_PARAMETER_ELLIPSOID_SCALE_FACTOR, nullptr, + common::UnitOfMeasure::Type::SCALE, k_0}; + +static const ParamMapping *const paramsLCC2SPMichigan[] = { + ¶mLatitudeFalseOrigin, ¶mLongitudeFalseOrigin, + ¶mLatitude1stStdParallel, ¶mLatitude2ndStdParallel, + ¶mFalseEastingOrigin, ¶mFalseNorthingOrigin, + ¶mEllipsoidScaleFactor, nullptr, +}; + +static const ParamMapping paramLatNatLatCenter = { + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lat_0}; + +static const ParamMapping paramLonNatLonCenter = { + EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, WKT1_LONGITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping *const paramsAEQD[]{ + ¶mLatNatLatCenter, ¶mLonNatLonCenter, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsNatOrigin[] = { + ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLatNatOriginLat1 = { + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_STANDARD_PARALLEL_1, + common::UnitOfMeasure::Type::ANGULAR, lat_1}; + +static const ParamMapping *const paramsBonne[] = { + ¶mLatNatOriginLat1, ¶mLongitudeNatOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLat1stParallelLatTs = { + EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, WKT1_STANDARD_PARALLEL_1, + common::UnitOfMeasure::Type::ANGULAR, lat_ts}; + +static const ParamMapping *const paramsCEA[] = { + ¶mLat1stParallelLatTs, ¶mLongitudeNatOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsEQDC[] = {¶mLatNatLatCenter, + ¶mLonNatLonCenter, + ¶mLatitude1stStdParallel, + ¶mLatitude2ndStdParallel, + ¶mFalseEasting, + ¶mFalseNorthing, + nullptr}; + +static const ParamMapping *const paramsLonNatOrigin[] = { + ¶mLongitudeNatOrigin, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsEqc[] = { + ¶mLat1stParallelLatTs, + ¶mLatitudeNatOrigin, // extension of EPSG, but used by GDAL / PROJ + ¶mLongitudeNatOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramSatelliteHeight = { + "Satellite Height", 0, "satellite_height", + common::UnitOfMeasure::Type::LINEAR, h}; + +static const ParamMapping *const paramsGeos[] = { + ¶mLongitudeNatOrigin, ¶mSatelliteHeight, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLatCentreLatCenter = { + EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, WKT1_LATITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lat_0}; + +static const ParamMapping paramLonCentreLonCenterLonc = { + EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, WKT1_LONGITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lonc}; + +static const ParamMapping paramAzimuth = { + EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE, + EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, WKT1_AZIMUTH, + common::UnitOfMeasure::Type::ANGULAR, alpha}; + +static const ParamMapping paramAngleToSkewGrid = { + EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, + EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, WKT1_RECTIFIED_GRID_ANGLE, + common::UnitOfMeasure::Type::ANGULAR, gamma}; +static const ParamMapping paramScaleFactorInitialLine = { + EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, WKT1_SCALE_FACTOR, + common::UnitOfMeasure::Type::SCALE, k}; + +static const ParamMapping *const paramsHomVariantA[] = { + ¶mLatCentreLatCenter, + ¶mLonCentreLonCenterLonc, + ¶mAzimuth, + ¶mAngleToSkewGrid, + ¶mScaleFactorInitialLine, + ¶mFalseEasting, + ¶mFalseNorthing, + nullptr}; + +static const ParamMapping paramFalseEastingProjectionCentre = { + EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, WKT1_FALSE_EASTING, + common::UnitOfMeasure::Type::LINEAR, x_0}; + +static const ParamMapping paramFalseNorthingProjectionCentre = { + EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, WKT1_FALSE_NORTHING, + common::UnitOfMeasure::Type::LINEAR, y_0}; + +static const ParamMapping *const paramsHomVariantB[] = { + ¶mLatCentreLatCenter, + ¶mLonCentreLonCenterLonc, + ¶mAzimuth, + ¶mAngleToSkewGrid, + ¶mScaleFactorInitialLine, + ¶mFalseEastingProjectionCentre, + ¶mFalseNorthingProjectionCentre, + nullptr}; + +static const ParamMapping paramLatPoint1 = { + "Latitude of 1st point", 0, "latitude_of_point_1", + common::UnitOfMeasure::Type::ANGULAR, lat_1}; + +static const ParamMapping paramLonPoint1 = { + "Longitude of 1st point", 0, "longitude_of_point_1", + common::UnitOfMeasure::Type::ANGULAR, lon_1}; + +static const ParamMapping paramLatPoint2 = { + "Latitude of 2nd point", 0, "latitude_of_point_2", + common::UnitOfMeasure::Type::ANGULAR, lat_2}; + +static const ParamMapping paramLonPoint2 = { + "Longitude of 2nd point", 0, "longitude_of_point_2", + common::UnitOfMeasure::Type::ANGULAR, lon_2}; + +static const ParamMapping *const paramsHomTwoPoint[] = { + ¶mLatCentreLatCenter, + ¶mLatPoint1, + ¶mLonPoint1, + ¶mLatPoint2, + ¶mLonPoint2, + ¶mScaleFactorInitialLine, + ¶mFalseEastingProjectionCentre, + ¶mFalseNorthingProjectionCentre, + nullptr}; + +static const ParamMapping *const paramsIMWP[] = { + ¶mLongitudeNatOrigin, ¶mLatFirstPoint, ¶mLatSecondPoint, + ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLonCentreLonCenter = { + EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, WKT1_LONGITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping paramColatitudeConeAxis = { + EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS, + EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, WKT1_AZIMUTH, + common::UnitOfMeasure::Type::ANGULAR, + "alpha"}; /* ignored by PROJ currently */ + +static const ParamMapping paramLatitudePseudoStdParallel = { + EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, + "pseudo_standard_parallel_1", common::UnitOfMeasure::Type::ANGULAR, + nullptr}; /* ignored by PROJ currently */ + +static const ParamMapping paramScaleFactorPseudoStdParallel = { + EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, + EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, + WKT1_SCALE_FACTOR, common::UnitOfMeasure::Type::SCALE, + k}; /* ignored by PROJ currently */ + +static const ParamMapping *const krovakParameters[] = { + ¶mLatCentreLatCenter, + ¶mLonCentreLonCenter, + ¶mColatitudeConeAxis, + ¶mLatitudePseudoStdParallel, + ¶mScaleFactorPseudoStdParallel, + ¶mFalseEasting, + ¶mFalseNorthing, + nullptr}; + +static const ParamMapping *const paramsLaea[] = { + ¶mLatNatLatCenter, ¶mLonNatLonCenter, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsMiller[] = { + ¶mLonNatLonCenter, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLatMerc1SP = { + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + nullptr, // always set to zero, not to be exported in WKT1 + common::UnitOfMeasure::Type::ANGULAR, + nullptr}; // always set to zero, not to be exported in PROJ strings + +static const ParamMapping *const paramsMerc1SP[] = { + ¶mLatMerc1SP, ¶mLongitudeNatOrigin, ¶mScaleFactorK, + ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsMerc2SP[] = { + ¶mLat1stParallelLatTs, ¶mLongitudeNatOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsObliqueStereo[] = { + ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mScaleFactorK, + ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLatStdParallel = { + EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL, + EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, WKT1_LATITUDE_OF_ORIGIN, + common::UnitOfMeasure::Type::ANGULAR, lat_ts}; + +static const ParamMapping paramsLonOrigin = { + EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, WKT1_CENTRAL_MERIDIAN, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping *const paramsPolarStereo[] = { + ¶mLatStdParallel, ¶msLonOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsLonNatOriginLongitudeCentre[] = { + ¶mLonNatLonCenter, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLatTrueScaleWag3 = { + "Latitude of true scale", 0, WKT1_LATITUDE_OF_ORIGIN, + common::UnitOfMeasure::Type::ANGULAR, lat_ts}; + +static const ParamMapping *const paramsWag3[] = { + ¶mLatTrueScaleWag3, ¶mLongitudeNatOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramPegLat = { + "Peg point latitude", 0, "peg_point_latitude", + common::UnitOfMeasure::Type::ANGULAR, "plat_0"}; + +static const ParamMapping paramPegLon = { + "Peg point longitude", 0, "peg_point_longitude", + common::UnitOfMeasure::Type::ANGULAR, "plon_0"}; + +static const ParamMapping paramPegHeading = { + "Peg point heading", 0, "peg_point_heading", + common::UnitOfMeasure::Type::ANGULAR, "phdg_0"}; + +static const ParamMapping paramPegHeight = { + "Peg point height", 0, "peg_point_height", + common::UnitOfMeasure::Type::LINEAR, "h_0"}; + +static const ParamMapping *const paramsSch[] = { + ¶mPegLat, ¶mPegLon, ¶mPegHeading, ¶mPegHeight, nullptr}; + +static const ParamMapping *const paramsWink1[] = { + ¶mLongitudeNatOrigin, ¶mLat1stParallelLatTs, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping *const paramsWink2[] = { + ¶mLongitudeNatOrigin, ¶mLatitude1stStdParallel, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLatLoxim = { + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_ORIGIN, + common::UnitOfMeasure::Type::ANGULAR, lat_1}; + +static const ParamMapping *const paramsLoxim[] = { + ¶mLatLoxim, ¶mLongitudeNatOrigin, ¶mFalseEasting, + ¶mFalseNorthing, nullptr}; + +static const ParamMapping paramLonCentre = { + EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, WKT1_LONGITUDE_OF_CENTER, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping paramLabordeObliqueMercatorAzimuth = { + EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE, + EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, WKT1_AZIMUTH, + common::UnitOfMeasure::Type::ANGULAR, "azi"}; + +static const ParamMapping *const paramsLabordeObliqueMercator[] = { + ¶mLatCentreLatCenter, + ¶mLonCentre, + ¶mLabordeObliqueMercatorAzimuth, + ¶mScaleFactorInitialLine, + ¶mFalseEasting, + ¶mFalseNorthing, + nullptr}; + +static const ParamMapping paramLatTopoOrigin = { + EPSG_NAME_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, nullptr, + common::UnitOfMeasure::Type::ANGULAR, lat_0}; + +static const ParamMapping paramLonTopoOrigin = { + EPSG_NAME_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, + EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, nullptr, + common::UnitOfMeasure::Type::ANGULAR, lon_0}; + +static const ParamMapping paramHeightTopoOrigin = { + EPSG_NAME_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, + EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, nullptr, + common::UnitOfMeasure::Type::LINEAR, + nullptr}; // unsupported by PROJ right now + +static const ParamMapping paramViewpointHeight = { + EPSG_NAME_PARAMETER_VIEWPOINT_HEIGHT, EPSG_CODE_PARAMETER_VIEWPOINT_HEIGHT, + nullptr, common::UnitOfMeasure::Type::LINEAR, "h"}; + +static const ParamMapping *const paramsVerticalPerspective[] = { + ¶mLatTopoOrigin, + ¶mLonTopoOrigin, + ¶mHeightTopoOrigin, // unsupported by PROJ right now + ¶mViewpointHeight, + ¶mFalseEasting, // PROJ addition + ¶mFalseNorthing, // PROJ addition + nullptr}; + +static const ParamMapping paramProjectionPlaneOriginHeight = { + EPSG_NAME_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, + EPSG_CODE_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, nullptr, + common::UnitOfMeasure::Type::LINEAR, "h_0"}; + +static const ParamMapping *const paramsColombiaUrban[] = { + ¶mLatitudeNatOrigin, + ¶mLongitudeNatOrigin, + ¶mFalseEasting, + ¶mFalseNorthing, + ¶mProjectionPlaneOriginHeight, + nullptr}; + +static const ParamMapping paramGeocentricXTopocentricOrigin = { + EPSG_NAME_PARAMETER_GEOCENTRIC_X_TOPOCENTRIC_ORIGIN, + EPSG_CODE_PARAMETER_GEOCENTRIC_X_TOPOCENTRIC_ORIGIN, nullptr, + common::UnitOfMeasure::Type::LINEAR, "X_0"}; + +static const ParamMapping paramGeocentricYTopocentricOrigin = { + EPSG_NAME_PARAMETER_GEOCENTRIC_Y_TOPOCENTRIC_ORIGIN, + EPSG_CODE_PARAMETER_GEOCENTRIC_Y_TOPOCENTRIC_ORIGIN, nullptr, + common::UnitOfMeasure::Type::LINEAR, "Y_0"}; + +static const ParamMapping paramGeocentricZTopocentricOrigin = { + EPSG_NAME_PARAMETER_GEOCENTRIC_Z_TOPOCENTRIC_ORIGIN, + EPSG_CODE_PARAMETER_GEOCENTRIC_Z_TOPOCENTRIC_ORIGIN, nullptr, + common::UnitOfMeasure::Type::LINEAR, "Z_0"}; + +static const ParamMapping *const paramsGeocentricTopocentric[] = { + ¶mGeocentricXTopocentricOrigin, ¶mGeocentricYTopocentricOrigin, + ¶mGeocentricZTopocentricOrigin, nullptr}; + +static const ParamMapping paramHeightTopoOriginWithH0 = { + EPSG_NAME_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, + EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, nullptr, + common::UnitOfMeasure::Type::LINEAR, "h_0"}; + +static const ParamMapping *const paramsGeographicTopocentric[] = { + ¶mLatTopoOrigin, ¶mLonTopoOrigin, ¶mHeightTopoOriginWithH0, + nullptr}; + +static const MethodMapping projectionMethodMappings[] = { + {EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, + "Transverse_Mercator", "tmerc", nullptr, paramsNatOriginScaleK}, + + {EPSG_NAME_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED, + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED, + "Transverse_Mercator_South_Orientated", "tmerc", "axis=wsu", + paramsNatOriginScaleK}, + + {PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, 0, "Two_Point_Equidistant", + "tpeqd", nullptr, paramsTPEQD}, + + {EPSG_NAME_METHOD_TUNISIA_MAPPING_GRID, + EPSG_CODE_METHOD_TUNISIA_MAPPING_GRID, "Tunisia_Mapping_Grid", nullptr, + nullptr, // no proj equivalent + paramsTMG}, + + {EPSG_NAME_METHOD_ALBERS_EQUAL_AREA, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA, + "Albers_Conic_Equal_Area", "aea", nullptr, paramsAEA}, + + {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, + "Lambert_Conformal_Conic_1SP", "lcc", nullptr, + []() { + static const ParamMapping paramLatLCC1SP = { + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + WKT1_LATITUDE_OF_ORIGIN, common::UnitOfMeasure::Type::ANGULAR, + lat_1}; + + static const ParamMapping *const x[] = { + ¶mLatLCC1SP, ¶mLongitudeNatOrigin, ¶mScaleFactor, + ¶mFalseEasting, ¶mFalseNorthing, nullptr, + }; + return x; + }()}, + + {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + "Lambert_Conformal_Conic_2SP", "lcc", nullptr, paramsLCC2SP}, + + // Oracle WKT + {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, "Lambert Conformal Conic", + "lcc", nullptr, paramsLCC2SP}, + + {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, + nullptr, // no mapping to WKT1_GDAL + "lcc", nullptr, paramsLCC2SPMichigan}, + + {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM, + "Lambert_Conformal_Conic_2SP_Belgium", "lcc", + nullptr, // FIXME: this is what is done in GDAL, but the formula of + // LCC 2SP + // Belgium in the EPSG 7.2 guidance is difference from the regular + // LCC 2SP + paramsLCC2SP}, + + {EPSG_NAME_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, + EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, "Azimuthal_Equidistant", + "aeqd", nullptr, paramsAEQD}, + + {EPSG_NAME_METHOD_GUAM_PROJECTION, EPSG_CODE_METHOD_GUAM_PROJECTION, + nullptr, // no mapping to GDAL WKT1 + "aeqd", "guam", paramsNatOrigin}, + + {EPSG_NAME_METHOD_BONNE, EPSG_CODE_METHOD_BONNE, "Bonne", "bonne", nullptr, + paramsBonne}, + + {PROJ_WKT2_NAME_METHOD_COMPACT_MILLER, 0, "Compact_Miller", "comill", + nullptr, paramsLonNatOrigin}, + + {EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL, + EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL, + "Cylindrical_Equal_Area", "cea", nullptr, paramsCEA}, + + {EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, + EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, "Cylindrical_Equal_Area", + "cea", nullptr, paramsCEA}, + + {EPSG_NAME_METHOD_CASSINI_SOLDNER, EPSG_CODE_METHOD_CASSINI_SOLDNER, + "Cassini_Soldner", "cass", nullptr, paramsNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC, 0, "Equidistant_Conic", "eqdc", + nullptr, paramsEQDC}, + + {PROJ_WKT2_NAME_METHOD_ECKERT_I, 0, "Eckert_I", "eck1", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_ECKERT_II, 0, "Eckert_II", "eck2", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_ECKERT_III, 0, "Eckert_III", "eck3", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_ECKERT_IV, 0, "Eckert_IV", "eck4", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_ECKERT_V, 0, "Eckert_V", "eck5", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_ECKERT_VI, 0, "Eckert_VI", "eck6", nullptr, + paramsLonNatOrigin}, + + {EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, "Equirectangular", "eqc", + nullptr, paramsEqc}, + + {EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, "Equirectangular", + "eqc", nullptr, paramsEqc}, + + {PROJ_WKT2_NAME_METHOD_FLAT_POLAR_QUARTIC, 0, "Flat_Polar_Quartic", + "mbtfpq", nullptr, paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC, 0, "Gall_Stereographic", "gall", + nullptr, paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE, 0, "Goode_Homolosine", "goode", + nullptr, paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, 0, + "Interrupted_Goode_Homolosine", "igh", nullptr, paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE_OCEAN, 0, nullptr, + "igh_o", nullptr, paramsLonNatOrigin}, + + // No proper WKT1 representation fr sweep=x + {PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X, 0, nullptr, "geos", + "sweep=x", paramsGeos}, + + {PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y, 0, + "Geostationary_Satellite", "geos", nullptr, paramsGeos}, + + {PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR, 0, + "Gauss_Schreiber_Transverse_Mercator", "gstmerc", nullptr, + paramsNatOriginScale}, + + {PROJ_WKT2_NAME_METHOD_GNOMONIC, 0, "Gnomonic", "gnom", nullptr, + paramsNatOrigin}, + + {EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, + "Hotine_Oblique_Mercator", "omerc", "no_uoff", paramsHomVariantA}, + + {EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, + "Hotine_Oblique_Mercator_Azimuth_Center", "omerc", nullptr, + paramsHomVariantB}, + + {PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0, + "Hotine_Oblique_Mercator_Two_Point_Natural_Origin", "omerc", nullptr, + paramsHomTwoPoint}, + + {PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC, 0, + "International_Map_of_the_World_Polyconic", "imw_p", nullptr, paramsIMWP}, + + {EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED, + EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED, "Krovak", "krovak", nullptr, + krovakParameters}, + + {EPSG_NAME_METHOD_KROVAK, EPSG_CODE_METHOD_KROVAK, "Krovak", "krovak", + "axis=swu", krovakParameters}, + + {EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, + EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, + "Lambert_Azimuthal_Equal_Area", "laea", nullptr, paramsLaea}, + + {EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL, + EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL, + "Lambert_Azimuthal_Equal_Area", "laea", nullptr, paramsLaea}, + + {PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL, 0, "Miller_Cylindrical", "mill", + "R_A", paramsMiller}, + + {EPSG_NAME_METHOD_MERCATOR_VARIANT_A, EPSG_CODE_METHOD_MERCATOR_VARIANT_A, + "Mercator_1SP", "merc", nullptr, paramsMerc1SP}, + + {EPSG_NAME_METHOD_MERCATOR_VARIANT_B, EPSG_CODE_METHOD_MERCATOR_VARIANT_B, + "Mercator_2SP", "merc", nullptr, paramsMerc2SP}, + + {EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, + EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, + "Popular_Visualisation_Pseudo_Mercator", // particular case actually + // handled manually + "webmerc", nullptr, paramsNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_MOLLWEIDE, 0, "Mollweide", "moll", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_NATURAL_EARTH, 0, "Natural_Earth", "natearth", + nullptr, paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_NATURAL_EARTH_II, 0, "Natural_Earth_II", "natearth2", + nullptr, paramsLonNatOrigin}, + + {EPSG_NAME_METHOD_NZMG, EPSG_CODE_METHOD_NZMG, "New_Zealand_Map_Grid", + "nzmg", nullptr, paramsNatOrigin}, + + { + EPSG_NAME_METHOD_OBLIQUE_STEREOGRAPHIC, + EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC, "Oblique_Stereographic", + "sterea", nullptr, paramsObliqueStereo, + }, + + {EPSG_NAME_METHOD_ORTHOGRAPHIC, EPSG_CODE_METHOD_ORTHOGRAPHIC, + "Orthographic", "ortho", nullptr, paramsNatOrigin}, + + {PROJ_WKT2_NAME_ORTHOGRAPHIC_SPHERICAL, 0, "Orthographic", "ortho", "f=0", + paramsNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_PATTERSON, 0, "Patterson", "patterson", nullptr, + paramsLonNatOrigin}, + + {EPSG_NAME_METHOD_AMERICAN_POLYCONIC, EPSG_CODE_METHOD_AMERICAN_POLYCONIC, + "Polyconic", "poly", nullptr, paramsNatOrigin}, + + {EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, "Polar_Stereographic", + "stere", nullptr, paramsObliqueStereo}, + + {EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, "Polar_Stereographic", + "stere", nullptr, paramsPolarStereo}, + + {PROJ_WKT2_NAME_METHOD_ROBINSON, 0, "Robinson", "robin", nullptr, + paramsLonNatOriginLongitudeCentre}, + + {PROJ_WKT2_NAME_METHOD_SINUSOIDAL, 0, "Sinusoidal", "sinu", nullptr, + paramsLonNatOriginLongitudeCentre}, + + {PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC, 0, "Stereographic", "stere", nullptr, + paramsObliqueStereo}, + + {PROJ_WKT2_NAME_METHOD_TIMES, 0, "Times", "times", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN, 0, "VanDerGrinten", "vandg", "R_A", + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_WAGNER_I, 0, "Wagner_I", "wag1", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_WAGNER_II, 0, "Wagner_II", "wag2", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_WAGNER_III, 0, "Wagner_III", "wag3", nullptr, + paramsWag3}, + + {PROJ_WKT2_NAME_METHOD_WAGNER_IV, 0, "Wagner_IV", "wag4", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_WAGNER_V, 0, "Wagner_V", "wag5", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_WAGNER_VI, 0, "Wagner_VI", "wag6", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_WAGNER_VII, 0, "Wagner_VII", "wag7", nullptr, + paramsLonNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE, 0, + "Quadrilateralized_Spherical_Cube", "qsc", nullptr, paramsNatOrigin}, + + {PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT, 0, + "Spherical_Cross_Track_Height", "sch", nullptr, paramsSch}, + + // The following methods have just the WKT <--> PROJ string mapping, but + // no setter. Similarly to GDAL + + {"Aitoff", 0, "Aitoff", "aitoff", nullptr, paramsLonNatOrigin}, + + {"Winkel I", 0, "Winkel_I", "wink1", nullptr, paramsWink1}, + + {"Winkel II", 0, "Winkel_II", "wink2", nullptr, paramsWink2}, + + {"Winkel Tripel", 0, "Winkel_Tripel", "wintri", nullptr, paramsWink2}, + + {"Craster Parabolic", 0, "Craster_Parabolic", "crast", nullptr, + paramsLonNatOrigin}, + + {"Loximuthal", 0, "Loximuthal", "loxim", nullptr, paramsLoxim}, + + {"Quartic Authalic", 0, "Quartic_Authalic", "qua_aut", nullptr, + paramsLonNatOrigin}, + + {"Transverse Cylindrical Equal Area", 0, + "Transverse_Cylindrical_Equal_Area", "tcea", nullptr, paramsObliqueStereo}, + + {EPSG_NAME_METHOD_EQUAL_EARTH, EPSG_CODE_METHOD_EQUAL_EARTH, nullptr, + "eqearth", nullptr, paramsLonNatOrigin}, + + {EPSG_NAME_METHOD_LABORDE_OBLIQUE_MERCATOR, + EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR, "Laborde_Oblique_Mercator", + "labrd", nullptr, paramsLabordeObliqueMercator}, + + {EPSG_NAME_METHOD_VERTICAL_PERSPECTIVE, + EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE, nullptr, "nsper", nullptr, + paramsVerticalPerspective}, + + {EPSG_NAME_METHOD_COLOMBIA_URBAN, EPSG_CODE_METHOD_COLOMBIA_URBAN, nullptr, + "col_urban", nullptr, paramsColombiaUrban}, + + {EPSG_NAME_METHOD_GEOCENTRIC_TOPOCENTRIC, + EPSG_CODE_METHOD_GEOCENTRIC_TOPOCENTRIC, nullptr, "topocentric", nullptr, + paramsGeocentricTopocentric}, + + {EPSG_NAME_METHOD_GEOGRAPHIC_TOPOCENTRIC, + EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC, nullptr, nullptr, nullptr, + paramsGeographicTopocentric}, +}; + +const MethodMapping *getProjectionMethodMappings(size_t &nElts) { + nElts = + sizeof(projectionMethodMappings) / sizeof(projectionMethodMappings[0]); + return projectionMethodMappings; +} + +#define METHOD_NAME_CODE(method) \ + { EPSG_NAME_METHOD_##method, EPSG_CODE_METHOD_##method } + +const struct MethodNameCode methodNameCodes[] = { + // Projection methods + METHOD_NAME_CODE(TRANSVERSE_MERCATOR), + METHOD_NAME_CODE(TRANSVERSE_MERCATOR_SOUTH_ORIENTATED), + METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_1SP), METHOD_NAME_CODE(NZMG), + METHOD_NAME_CODE(TUNISIA_MAPPING_GRID), METHOD_NAME_CODE(ALBERS_EQUAL_AREA), + METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP), + METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM), + METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN), + METHOD_NAME_CODE(MODIFIED_AZIMUTHAL_EQUIDISTANT), + METHOD_NAME_CODE(GUAM_PROJECTION), METHOD_NAME_CODE(BONNE), + METHOD_NAME_CODE(LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL), + METHOD_NAME_CODE(LAMBERT_CYLINDRICAL_EQUAL_AREA), + METHOD_NAME_CODE(CASSINI_SOLDNER), + METHOD_NAME_CODE(EQUIDISTANT_CYLINDRICAL), + METHOD_NAME_CODE(EQUIDISTANT_CYLINDRICAL_SPHERICAL), + METHOD_NAME_CODE(HOTINE_OBLIQUE_MERCATOR_VARIANT_A), + METHOD_NAME_CODE(HOTINE_OBLIQUE_MERCATOR_VARIANT_B), + METHOD_NAME_CODE(KROVAK_NORTH_ORIENTED), METHOD_NAME_CODE(KROVAK), + METHOD_NAME_CODE(LAMBERT_AZIMUTHAL_EQUAL_AREA), + METHOD_NAME_CODE(POPULAR_VISUALISATION_PSEUDO_MERCATOR), + METHOD_NAME_CODE(MERCATOR_VARIANT_A), METHOD_NAME_CODE(MERCATOR_VARIANT_B), + METHOD_NAME_CODE(OBLIQUE_STEREOGRAPHIC), + METHOD_NAME_CODE(AMERICAN_POLYCONIC), + METHOD_NAME_CODE(POLAR_STEREOGRAPHIC_VARIANT_A), + METHOD_NAME_CODE(POLAR_STEREOGRAPHIC_VARIANT_B), + METHOD_NAME_CODE(EQUAL_EARTH), METHOD_NAME_CODE(LABORDE_OBLIQUE_MERCATOR), + METHOD_NAME_CODE(VERTICAL_PERSPECTIVE), METHOD_NAME_CODE(COLOMBIA_URBAN), + // Other conversions + METHOD_NAME_CODE(CHANGE_VERTICAL_UNIT), + METHOD_NAME_CODE(HEIGHT_DEPTH_REVERSAL), + METHOD_NAME_CODE(AXIS_ORDER_REVERSAL_2D), + METHOD_NAME_CODE(AXIS_ORDER_REVERSAL_3D), + METHOD_NAME_CODE(GEOGRAPHIC_GEOCENTRIC), + METHOD_NAME_CODE(GEOCENTRIC_TOPOCENTRIC), + METHOD_NAME_CODE(GEOGRAPHIC_TOPOCENTRIC), + // Transformations + METHOD_NAME_CODE(LONGITUDE_ROTATION), + METHOD_NAME_CODE(AFFINE_PARAMETRIC_TRANSFORMATION), + METHOD_NAME_CODE(COORDINATE_FRAME_GEOCENTRIC), + METHOD_NAME_CODE(COORDINATE_FRAME_GEOGRAPHIC_2D), + METHOD_NAME_CODE(COORDINATE_FRAME_GEOGRAPHIC_3D), + METHOD_NAME_CODE(POSITION_VECTOR_GEOCENTRIC), + METHOD_NAME_CODE(POSITION_VECTOR_GEOGRAPHIC_2D), + METHOD_NAME_CODE(POSITION_VECTOR_GEOGRAPHIC_3D), + METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOCENTRIC), + METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D), + METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D), + METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC), + METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D), + METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D), + METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC), + METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D), + METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D), + METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOCENTRIC), + METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D), + METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D), + METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOCENTRIC), + METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D), + METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D), + METHOD_NAME_CODE(MOLODENSKY), METHOD_NAME_CODE(ABRIDGED_MOLODENSKY), + METHOD_NAME_CODE(GEOGRAPHIC2D_OFFSETS), + METHOD_NAME_CODE(GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS), + METHOD_NAME_CODE(GEOGRAPHIC3D_OFFSETS), METHOD_NAME_CODE(VERTICAL_OFFSET), + METHOD_NAME_CODE(NTV2), METHOD_NAME_CODE(NTV1), METHOD_NAME_CODE(NADCON), + METHOD_NAME_CODE(VERTCON), + METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN), +}; + +const MethodNameCode *getMethodNameCodes(size_t &nElts) { + nElts = sizeof(methodNameCodes) / sizeof(methodNameCodes[0]); + return methodNameCodes; +} + +#define PARAM_NAME_CODE(method) \ + { EPSG_NAME_PARAMETER_##method, EPSG_CODE_PARAMETER_##method } + +const struct ParamNameCode paramNameCodes[] = { + // Parameters of projection methods + PARAM_NAME_CODE(COLATITUDE_CONE_AXIS), + PARAM_NAME_CODE(LATITUDE_OF_NATURAL_ORIGIN), + PARAM_NAME_CODE(LONGITUDE_OF_NATURAL_ORIGIN), + PARAM_NAME_CODE(SCALE_FACTOR_AT_NATURAL_ORIGIN), + PARAM_NAME_CODE(FALSE_EASTING), PARAM_NAME_CODE(FALSE_NORTHING), + PARAM_NAME_CODE(LATITUDE_PROJECTION_CENTRE), + PARAM_NAME_CODE(LONGITUDE_PROJECTION_CENTRE), + PARAM_NAME_CODE(AZIMUTH_INITIAL_LINE), + PARAM_NAME_CODE(ANGLE_RECTIFIED_TO_SKEW_GRID), + PARAM_NAME_CODE(SCALE_FACTOR_INITIAL_LINE), + PARAM_NAME_CODE(EASTING_PROJECTION_CENTRE), + PARAM_NAME_CODE(NORTHING_PROJECTION_CENTRE), + PARAM_NAME_CODE(LATITUDE_PSEUDO_STANDARD_PARALLEL), + PARAM_NAME_CODE(SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL), + PARAM_NAME_CODE(LATITUDE_FALSE_ORIGIN), + PARAM_NAME_CODE(LONGITUDE_FALSE_ORIGIN), + PARAM_NAME_CODE(LATITUDE_1ST_STD_PARALLEL), + PARAM_NAME_CODE(LATITUDE_2ND_STD_PARALLEL), + PARAM_NAME_CODE(EASTING_FALSE_ORIGIN), + PARAM_NAME_CODE(NORTHING_FALSE_ORIGIN), + PARAM_NAME_CODE(LATITUDE_STD_PARALLEL), + PARAM_NAME_CODE(LONGITUDE_OF_ORIGIN), + PARAM_NAME_CODE(ELLIPSOID_SCALE_FACTOR), + PARAM_NAME_CODE(PROJECTION_PLANE_ORIGIN_HEIGHT), + PARAM_NAME_CODE(GEOCENTRIC_X_TOPOCENTRIC_ORIGIN), + PARAM_NAME_CODE(GEOCENTRIC_Y_TOPOCENTRIC_ORIGIN), + PARAM_NAME_CODE(GEOCENTRIC_Z_TOPOCENTRIC_ORIGIN), + // Parameters of transformations + PARAM_NAME_CODE(SEMI_MAJOR_AXIS_DIFFERENCE), + PARAM_NAME_CODE(FLATTENING_DIFFERENCE), + PARAM_NAME_CODE(LATITUDE_LONGITUDE_DIFFERENCE_FILE), + PARAM_NAME_CODE(GEOID_CORRECTION_FILENAME), + PARAM_NAME_CODE(VERTICAL_OFFSET_FILE), + PARAM_NAME_CODE(LATITUDE_DIFFERENCE_FILE), + PARAM_NAME_CODE(LONGITUDE_DIFFERENCE_FILE), + PARAM_NAME_CODE(UNIT_CONVERSION_SCALAR), PARAM_NAME_CODE(LATITUDE_OFFSET), + PARAM_NAME_CODE(LONGITUDE_OFFSET), PARAM_NAME_CODE(VERTICAL_OFFSET), + PARAM_NAME_CODE(GEOID_UNDULATION), PARAM_NAME_CODE(A0), PARAM_NAME_CODE(A1), + PARAM_NAME_CODE(A2), PARAM_NAME_CODE(B0), PARAM_NAME_CODE(B1), + PARAM_NAME_CODE(B2), PARAM_NAME_CODE(X_AXIS_TRANSLATION), + PARAM_NAME_CODE(Y_AXIS_TRANSLATION), PARAM_NAME_CODE(Z_AXIS_TRANSLATION), + PARAM_NAME_CODE(X_AXIS_ROTATION), PARAM_NAME_CODE(Y_AXIS_ROTATION), + PARAM_NAME_CODE(Z_AXIS_ROTATION), PARAM_NAME_CODE(SCALE_DIFFERENCE), + PARAM_NAME_CODE(RATE_X_AXIS_TRANSLATION), + PARAM_NAME_CODE(RATE_Y_AXIS_TRANSLATION), + PARAM_NAME_CODE(RATE_Z_AXIS_TRANSLATION), + PARAM_NAME_CODE(RATE_X_AXIS_ROTATION), + PARAM_NAME_CODE(RATE_Y_AXIS_ROTATION), + PARAM_NAME_CODE(RATE_Z_AXIS_ROTATION), + PARAM_NAME_CODE(RATE_SCALE_DIFFERENCE), PARAM_NAME_CODE(REFERENCE_EPOCH), + PARAM_NAME_CODE(TRANSFORMATION_REFERENCE_EPOCH), + PARAM_NAME_CODE(ORDINATE_1_EVAL_POINT), + PARAM_NAME_CODE(ORDINATE_2_EVAL_POINT), + PARAM_NAME_CODE(ORDINATE_3_EVAL_POINT), + PARAM_NAME_CODE(GEOCENTRIC_TRANSLATION_FILE), +}; + +const ParamNameCode *getParamNameCodes(size_t &nElts) { + nElts = sizeof(paramNameCodes) / sizeof(paramNameCodes[0]); + return paramNameCodes; +} + +static const ParamMapping paramUnitConversionScalar = { + EPSG_NAME_PARAMETER_UNIT_CONVERSION_SCALAR, + EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR, nullptr, + common::UnitOfMeasure::Type::SCALE, nullptr}; + +static const ParamMapping *const paramsChangeVerticalUnit[] = { + ¶mUnitConversionScalar, nullptr}; + +static const ParamMapping paramLongitudeOffset = { + EPSG_NAME_PARAMETER_LONGITUDE_OFFSET, EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, + nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; + +static const ParamMapping *const paramsLongitudeRotation[] = { + ¶mLongitudeOffset, nullptr}; + +static const ParamMapping paramA0 = { + EPSG_NAME_PARAMETER_A0, EPSG_CODE_PARAMETER_A0, nullptr, + common::UnitOfMeasure::Type::UNKNOWN, nullptr}; + +static const ParamMapping paramA1 = { + EPSG_NAME_PARAMETER_A1, EPSG_CODE_PARAMETER_A1, nullptr, + common::UnitOfMeasure::Type::UNKNOWN, nullptr}; + +static const ParamMapping paramA2 = { + EPSG_NAME_PARAMETER_A2, EPSG_CODE_PARAMETER_A2, nullptr, + common::UnitOfMeasure::Type::UNKNOWN, nullptr}; + +static const ParamMapping paramB0 = { + EPSG_NAME_PARAMETER_B0, EPSG_CODE_PARAMETER_B0, nullptr, + common::UnitOfMeasure::Type::UNKNOWN, nullptr}; + +static const ParamMapping paramB1 = { + EPSG_NAME_PARAMETER_B1, EPSG_CODE_PARAMETER_B1, nullptr, + common::UnitOfMeasure::Type::UNKNOWN, nullptr}; + +static const ParamMapping paramB2 = { + EPSG_NAME_PARAMETER_B2, EPSG_CODE_PARAMETER_B2, nullptr, + common::UnitOfMeasure::Type::UNKNOWN, nullptr}; + +static const ParamMapping *const paramsAffineParametricTransformation[] = { + ¶mA0, ¶mA1, ¶mA2, ¶mB0, ¶mB1, ¶mB2, nullptr}; + +static const ParamMapping paramXTranslation = { + EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION, + EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramYTranslation = { + EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION, + EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramZTranslation = { + EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION, + EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramXRotation = { + EPSG_NAME_PARAMETER_X_AXIS_ROTATION, EPSG_CODE_PARAMETER_X_AXIS_ROTATION, + nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramYRotation = { + EPSG_NAME_PARAMETER_Y_AXIS_ROTATION, EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, + nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramZRotation = { + EPSG_NAME_PARAMETER_Z_AXIS_ROTATION, EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, + nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramScaleDifference = { + EPSG_NAME_PARAMETER_SCALE_DIFFERENCE, EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, + nullptr, common::UnitOfMeasure::Type::SCALE, nullptr}; + +static const ParamMapping *const paramsHelmert3[] = { + ¶mXTranslation, ¶mYTranslation, ¶mZTranslation, nullptr}; + +static const ParamMapping *const paramsHelmert7[] = { + ¶mXTranslation, ¶mYTranslation, + ¶mZTranslation, ¶mXRotation, + ¶mYRotation, ¶mZRotation, + ¶mScaleDifference, nullptr}; + +static const ParamMapping paramRateXTranslation = { + EPSG_NAME_PARAMETER_RATE_X_AXIS_TRANSLATION, + EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramRateYTranslation = { + EPSG_NAME_PARAMETER_RATE_Y_AXIS_TRANSLATION, + EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramRateZTranslation = { + EPSG_NAME_PARAMETER_RATE_Z_AXIS_TRANSLATION, + EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramRateXRotation = { + EPSG_NAME_PARAMETER_RATE_X_AXIS_ROTATION, + EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramRateYRotation = { + EPSG_NAME_PARAMETER_RATE_Y_AXIS_ROTATION, + EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramRateZRotation = { + EPSG_NAME_PARAMETER_RATE_Z_AXIS_ROTATION, + EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramRateScaleDifference = { + EPSG_NAME_PARAMETER_RATE_SCALE_DIFFERENCE, + EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE, nullptr, + common::UnitOfMeasure::Type::SCALE, nullptr}; + +static const ParamMapping paramReferenceEpoch = { + EPSG_NAME_PARAMETER_REFERENCE_EPOCH, EPSG_CODE_PARAMETER_REFERENCE_EPOCH, + nullptr, common::UnitOfMeasure::Type::TIME, nullptr}; + +static const ParamMapping *const paramsHelmert15[] = { + ¶mXTranslation, ¶mYTranslation, + ¶mZTranslation, ¶mXRotation, + ¶mYRotation, ¶mZRotation, + ¶mScaleDifference, ¶mRateXTranslation, + ¶mRateYTranslation, ¶mRateZTranslation, + ¶mRateXRotation, ¶mRateYRotation, + ¶mRateZRotation, ¶mRateScaleDifference, + ¶mReferenceEpoch, nullptr}; + +static const ParamMapping paramOrdinate1EvalPoint = { + EPSG_NAME_PARAMETER_ORDINATE_1_EVAL_POINT, + EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramOrdinate2EvalPoint = { + EPSG_NAME_PARAMETER_ORDINATE_2_EVAL_POINT, + EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramOrdinate3EvalPoint = { + EPSG_NAME_PARAMETER_ORDINATE_3_EVAL_POINT, + EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping *const paramsMolodenskyBadekas[] = { + ¶mXTranslation, + ¶mYTranslation, + ¶mZTranslation, + ¶mXRotation, + ¶mYRotation, + ¶mZRotation, + ¶mScaleDifference, + ¶mOrdinate1EvalPoint, + ¶mOrdinate2EvalPoint, + ¶mOrdinate3EvalPoint, + nullptr}; + +static const ParamMapping paramSemiMajorAxisDifference = { + EPSG_NAME_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE, + EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE, nullptr, + common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping paramFlatteningDifference = { + EPSG_NAME_PARAMETER_FLATTENING_DIFFERENCE, + EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE, nullptr, + common::UnitOfMeasure::Type::NONE, nullptr}; + +static const ParamMapping *const paramsMolodensky[] = { + ¶mXTranslation, ¶mYTranslation, + ¶mZTranslation, ¶mSemiMajorAxisDifference, + ¶mFlatteningDifference, nullptr}; + +static const ParamMapping paramLatitudeOffset = { + EPSG_NAME_PARAMETER_LATITUDE_OFFSET, EPSG_CODE_PARAMETER_LATITUDE_OFFSET, + nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; + +static const ParamMapping *const paramsGeographic2DOffsets[] = { + ¶mLatitudeOffset, ¶mLongitudeOffset, nullptr}; + +static const ParamMapping paramGeoidUndulation = { + EPSG_NAME_PARAMETER_GEOID_UNDULATION, EPSG_CODE_PARAMETER_GEOID_UNDULATION, + nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping *const paramsGeographic2DWithHeightOffsets[] = { + ¶mLatitudeOffset, ¶mLongitudeOffset, ¶mGeoidUndulation, + nullptr}; + +static const ParamMapping paramVerticalOffset = { + EPSG_NAME_PARAMETER_VERTICAL_OFFSET, EPSG_CODE_PARAMETER_VERTICAL_OFFSET, + nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; + +static const ParamMapping *const paramsGeographic3DOffsets[] = { + ¶mLatitudeOffset, ¶mLongitudeOffset, ¶mVerticalOffset, nullptr}; + +static const ParamMapping *const paramsVerticalOffsets[] = { + ¶mVerticalOffset, nullptr}; + +static const ParamMapping paramLatitudeLongitudeDifferenceFile = { + EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, nullptr, + common::UnitOfMeasure::Type::NONE, nullptr}; + +static const ParamMapping *const paramsNTV2[] = { + ¶mLatitudeLongitudeDifferenceFile, nullptr}; + +static const ParamMapping paramGeocentricTranslationFile = { + EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, + EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, nullptr, + common::UnitOfMeasure::Type::NONE, nullptr}; + +static const ParamMapping + *const paramsGeocentricTranslationGridInterpolationIGN[] = { + ¶mGeocentricTranslationFile, nullptr}; + +static const ParamMapping paramLatitudeDifferenceFile = { + EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE, nullptr, + common::UnitOfMeasure::Type::NONE, nullptr}; + +static const ParamMapping paramLongitudeDifferenceFile = { + EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE, nullptr, + common::UnitOfMeasure::Type::NONE, nullptr}; + +static const ParamMapping *const paramsNADCON[] = { + ¶mLatitudeDifferenceFile, ¶mLongitudeDifferenceFile, nullptr}; + +static const ParamMapping paramVerticalOffsetFile = { + EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, + EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE, nullptr, + common::UnitOfMeasure::Type::NONE, nullptr}; + +static const ParamMapping *const paramsVERTCON[] = {¶mVerticalOffsetFile, + nullptr}; + +static const ParamMapping paramSouthPoleLatGRIB = { + PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION, 0, nullptr, + common::UnitOfMeasure::Type::ANGULAR, nullptr}; + +static const ParamMapping paramSouthPoleLonGRIB = { + PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION, 0, nullptr, + common::UnitOfMeasure::Type::ANGULAR, nullptr}; + +static const ParamMapping paramAxisRotationGRIB = { + PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION, 0, nullptr, + common::UnitOfMeasure::Type::ANGULAR, nullptr}; + +static const ParamMapping *const paramsPoleRotationGRIBConvention[] = { + ¶mSouthPoleLatGRIB, ¶mSouthPoleLonGRIB, ¶mAxisRotationGRIB, + nullptr}; + +static const MethodMapping otherMethodMappings[] = { + {EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT, + EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT, nullptr, nullptr, nullptr, + paramsChangeVerticalUnit}, + {EPSG_NAME_METHOD_HEIGHT_DEPTH_REVERSAL, + EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL, nullptr, nullptr, nullptr, + paramsChangeVerticalUnit}, + {EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_2D, + EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D, nullptr, nullptr, nullptr, + nullptr}, + {EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_3D, + EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D, nullptr, nullptr, nullptr, + nullptr}, + {EPSG_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC, + EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC, nullptr, nullptr, nullptr, + nullptr}, + {EPSG_NAME_METHOD_LONGITUDE_ROTATION, EPSG_CODE_METHOD_LONGITUDE_ROTATION, + nullptr, nullptr, nullptr, paramsLongitudeRotation}, + {EPSG_NAME_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION, + EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION, nullptr, nullptr, + nullptr, paramsAffineParametricTransformation}, + + {PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION, 0, nullptr, nullptr, + nullptr, paramsPoleRotationGRIBConvention}, + + {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC, + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC, nullptr, nullptr, + nullptr, paramsHelmert3}, + {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D, nullptr, nullptr, + nullptr, paramsHelmert3}, + {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D, + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D, nullptr, nullptr, + nullptr, paramsHelmert3}, + + {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOCENTRIC, + EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC, nullptr, nullptr, nullptr, + paramsHelmert7}, + {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, + paramsHelmert7}, + {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D, + EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, + paramsHelmert7}, + + {EPSG_NAME_METHOD_POSITION_VECTOR_GEOCENTRIC, + EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC, nullptr, nullptr, nullptr, + paramsHelmert7}, + {EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, + paramsHelmert7}, + {EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D, + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, + paramsHelmert7}, + + {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC, + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC, nullptr, + nullptr, nullptr, paramsHelmert15}, + {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D, nullptr, + nullptr, nullptr, paramsHelmert15}, + {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D, + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D, nullptr, + nullptr, nullptr, paramsHelmert15}, + + {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC, + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC, nullptr, + nullptr, nullptr, paramsHelmert15}, + {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D, nullptr, + nullptr, nullptr, paramsHelmert15}, + {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D, + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D, nullptr, + nullptr, nullptr, paramsHelmert15}, + + {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC, + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC, nullptr, nullptr, + nullptr, paramsMolodenskyBadekas}, + {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D, nullptr, nullptr, + nullptr, paramsMolodenskyBadekas}, + {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D, + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D, nullptr, nullptr, + nullptr, paramsMolodenskyBadekas}, + + {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC, + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC, nullptr, nullptr, + nullptr, paramsMolodenskyBadekas}, + {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D, + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D, nullptr, nullptr, + nullptr, paramsMolodenskyBadekas}, + {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D, + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D, nullptr, nullptr, + nullptr, paramsMolodenskyBadekas}, + + {EPSG_NAME_METHOD_MOLODENSKY, EPSG_CODE_METHOD_MOLODENSKY, nullptr, nullptr, + nullptr, paramsMolodensky}, + + {EPSG_NAME_METHOD_ABRIDGED_MOLODENSKY, EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY, + nullptr, nullptr, nullptr, paramsMolodensky}, + + {EPSG_NAME_METHOD_GEOGRAPHIC2D_OFFSETS, + EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS, nullptr, nullptr, nullptr, + paramsGeographic2DOffsets}, + + {EPSG_NAME_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS, + EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS, nullptr, nullptr, + nullptr, paramsGeographic2DWithHeightOffsets}, + + {EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSETS, + EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS, nullptr, nullptr, nullptr, + paramsGeographic3DOffsets}, + + {EPSG_NAME_METHOD_VERTICAL_OFFSET, EPSG_CODE_METHOD_VERTICAL_OFFSET, + nullptr, nullptr, nullptr, paramsVerticalOffsets}, + + {EPSG_NAME_METHOD_NTV2, EPSG_CODE_METHOD_NTV2, nullptr, nullptr, nullptr, + paramsNTV2}, + + {EPSG_NAME_METHOD_NTV1, EPSG_CODE_METHOD_NTV1, nullptr, nullptr, nullptr, + paramsNTV2}, + + {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN, + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN, nullptr, + nullptr, nullptr, paramsGeocentricTranslationGridInterpolationIGN}, + + {EPSG_NAME_METHOD_NADCON, EPSG_CODE_METHOD_NADCON, nullptr, nullptr, + nullptr, paramsNADCON}, + + {EPSG_NAME_METHOD_VERTCON, EPSG_CODE_METHOD_VERTCON, nullptr, nullptr, + nullptr, paramsVERTCON}, + {EPSG_NAME_METHOD_VERTCON_OLDNAME, EPSG_CODE_METHOD_VERTCON, nullptr, + nullptr, nullptr, paramsVERTCON}, +}; + +const MethodMapping *getOtherMethodMappings(size_t &nElts) { + nElts = sizeof(otherMethodMappings) / sizeof(otherMethodMappings[0]); + return otherMethodMappings; +} + +// --------------------------------------------------------------------------- + +PROJ_NO_INLINE const MethodMapping *getMapping(int epsg_code) noexcept { + for (const auto &mapping : projectionMethodMappings) { + if (mapping.epsg_code == epsg_code) { + return &mapping; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +const MethodMapping *getMapping(const OperationMethod *method) noexcept { + const std::string &name(method->nameStr()); + const int epsg_code = method->getEPSGCode(); + for (const auto &mapping : projectionMethodMappings) { + if ((epsg_code != 0 && mapping.epsg_code == epsg_code) || + metadata::Identifier::isEquivalentName(mapping.wkt2_name, + name.c_str())) { + return &mapping; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +const MethodMapping *getMappingFromWKT1(const std::string &wkt1_name) noexcept { + // Unusual for a WKT1 projection name, but mentioned in OGC 12-063r5 C.4.2 + if (ci_starts_with(wkt1_name, "UTM zone")) { + return getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR); + } + + for (const auto &mapping : projectionMethodMappings) { + if (mapping.wkt1_name && metadata::Identifier::isEquivalentName( + mapping.wkt1_name, wkt1_name.c_str())) { + return &mapping; + } + } + return nullptr; +} +// --------------------------------------------------------------------------- + +const MethodMapping *getMapping(const char *wkt2_name) noexcept { + for (const auto &mapping : projectionMethodMappings) { + if (metadata::Identifier::isEquivalentName(mapping.wkt2_name, + wkt2_name)) { + return &mapping; + } + } + for (const auto &mapping : otherMethodMappings) { + if (metadata::Identifier::isEquivalentName(mapping.wkt2_name, + wkt2_name)) { + return &mapping; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +std::vector<const MethodMapping *> +getMappingsFromPROJName(const std::string &projName) { + std::vector<const MethodMapping *> res; + for (const auto &mapping : projectionMethodMappings) { + if (mapping.proj_name_main && projName == mapping.proj_name_main) { + res.push_back(&mapping); + } + } + return res; +} + +// --------------------------------------------------------------------------- + +const ParamMapping *getMapping(const MethodMapping *mapping, + const OperationParameterNNPtr ¶m) { + if (mapping->params == nullptr) { + return nullptr; + } + + // First try with id + const int epsg_code = param->getEPSGCode(); + if (epsg_code) { + for (int i = 0; mapping->params[i] != nullptr; ++i) { + const auto *paramMapping = mapping->params[i]; + if (paramMapping->epsg_code == epsg_code) { + return paramMapping; + } + } + } + + // then equivalent name + const std::string &name = param->nameStr(); + for (int i = 0; mapping->params[i] != nullptr; ++i) { + const auto *paramMapping = mapping->params[i]; + if (metadata::Identifier::isEquivalentName(paramMapping->wkt2_name, + name.c_str())) { + return paramMapping; + } + } + + // and finally different name, but equivalent parameter + for (int i = 0; mapping->params[i] != nullptr; ++i) { + const auto *paramMapping = mapping->params[i]; + if (areEquivalentParameters(paramMapping->wkt2_name, name)) { + return paramMapping; + } + } + + return nullptr; +} + +// --------------------------------------------------------------------------- + +const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping, + const std::string &wkt1_name) { + for (int i = 0; mapping->params[i] != nullptr; ++i) { + const auto *paramMapping = mapping->params[i]; + if (paramMapping->wkt1_name && + (metadata::Identifier::isEquivalentName(paramMapping->wkt1_name, + wkt1_name.c_str()) || + areEquivalentParameters(paramMapping->wkt1_name, wkt1_name))) { + return paramMapping; + } + } + return nullptr; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END diff --git a/src/iso19111/operation/parammappings.hpp b/src/iso19111/operation/parammappings.hpp new file mode 100644 index 00000000..05fe8a2d --- /dev/null +++ b/src/iso19111/operation/parammappings.hpp @@ -0,0 +1,114 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef PARAMMAPPINGS_HPP +#define PARAMMAPPINGS_HPP + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/coordinateoperation.hpp" +#include "proj/util.hpp" + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +extern const char *WKT1_LATITUDE_OF_ORIGIN; +extern const char *WKT1_CENTRAL_MERIDIAN; +extern const char *WKT1_SCALE_FACTOR; +extern const char *WKT1_FALSE_EASTING; +extern const char *WKT1_FALSE_NORTHING; +extern const char *WKT1_STANDARD_PARALLEL_1; +extern const char *WKT1_STANDARD_PARALLEL_2; +extern const char *WKT1_LATITUDE_OF_CENTER; +extern const char *WKT1_LONGITUDE_OF_CENTER; +extern const char *WKT1_AZIMUTH; +extern const char *WKT1_RECTIFIED_GRID_ANGLE; + +struct ParamMapping { + const char *wkt2_name; + const int epsg_code; + const char *wkt1_name; + const common::UnitOfMeasure::Type unit_type; + const char *proj_name; +}; + +struct MethodMapping { + const char *wkt2_name; + const int epsg_code; + const char *wkt1_name; + const char *proj_name_main; + const char *proj_name_aux; + const ParamMapping *const *params; +}; + +extern const ParamMapping paramLatitudeNatOrigin; + +const MethodMapping *getProjectionMethodMappings(size_t &nElts); +const MethodMapping *getOtherMethodMappings(size_t &nElts); + +struct MethodNameCode { + const char *name; + int epsg_code; +}; + +const MethodNameCode *getMethodNameCodes(size_t &nElts); + +struct ParamNameCode { + const char *name; + int epsg_code; +}; + +const ParamNameCode *getParamNameCodes(size_t &nElts); + +const MethodMapping *getMapping(int epsg_code) noexcept; +const MethodMapping *getMappingFromWKT1(const std::string &wkt1_name) noexcept; +const MethodMapping *getMapping(const char *wkt2_name) noexcept; +const MethodMapping *getMapping(const OperationMethod *method) noexcept; +std::vector<const MethodMapping *> +getMappingsFromPROJName(const std::string &projName); +const ParamMapping *getMapping(const MethodMapping *mapping, + const OperationParameterNNPtr ¶m); +const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping, + const std::string &wkt1_name); + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END + +#endif // PARAMMAPPINGS_HPP diff --git a/src/iso19111/operation/projbasedoperation.cpp b/src/iso19111/operation/projbasedoperation.cpp new file mode 100644 index 00000000..6e0fd109 --- /dev/null +++ b/src/iso19111/operation/projbasedoperation.cpp @@ -0,0 +1,316 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/common.hpp" +#include "proj/coordinateoperation.hpp" +#include "proj/crs.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include "coordinateoperation_internal.hpp" +#include "oputils.hpp" + +// PROJ include order is sensitive +// clang-format off +#include "proj.h" +#include "proj_internal.h" // M_PI +// clang-format on +#include "proj_constants.h" +#include "proj_json_streaming_writer.hpp" + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <cstring> +#include <memory> +#include <set> +#include <string> +#include <vector> + +using namespace NS_PROJ::internal; + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +PROJBasedOperation::~PROJBasedOperation() = default; + +// --------------------------------------------------------------------------- + +PROJBasedOperation::PROJBasedOperation(const OperationMethodNNPtr &methodIn) + : SingleOperation(methodIn) {} + +// --------------------------------------------------------------------------- + +PROJBasedOperationNNPtr PROJBasedOperation::create( + const util::PropertyMap &properties, const std::string &PROJString, + const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + auto method = OperationMethod::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + "PROJ-based operation method: " + PROJString), + std::vector<GeneralOperationParameterNNPtr>{}); + auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(method); + op->assignSelf(op); + op->projString_ = PROJString; + if (sourceCRS && targetCRS) { + op->setCRSs(NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), nullptr); + } + op->setProperties( + addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation")); + op->setAccuracies(accuracies); + return op; +} + +// --------------------------------------------------------------------------- + +PROJBasedOperationNNPtr PROJBasedOperation::create( + const util::PropertyMap &properties, + const io::IPROJStringExportableNNPtr &projExportable, bool inverse, + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const crs::CRSPtr &interpolationCRS, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies, + bool hasBallparkTransformation) { + + auto formatter = io::PROJStringFormatter::create(); + if (inverse) { + formatter->startInversion(); + } + projExportable->_exportToPROJString(formatter.get()); + if (inverse) { + formatter->stopInversion(); + } + auto projString = formatter->toString(); + + auto method = OperationMethod::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + "PROJ-based operation method (approximate): " + + projString), + std::vector<GeneralOperationParameterNNPtr>{}); + auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(method); + op->assignSelf(op); + op->projString_ = projString; + op->setCRSs(sourceCRS, targetCRS, interpolationCRS); + op->setProperties( + addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation")); + op->setAccuracies(accuracies); + op->projStringExportable_ = projExportable.as_nullable(); + op->inverse_ = inverse; + op->setHasBallparkTransformation(hasBallparkTransformation); + return op; +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr PROJBasedOperation::inverse() const { + + if (projStringExportable_ && sourceCRS() && targetCRS()) { + return util::nn_static_pointer_cast<CoordinateOperation>( + PROJBasedOperation::create( + createPropertiesForInverse(this, false, false), + NN_NO_CHECK(projStringExportable_), !inverse_, + NN_NO_CHECK(targetCRS()), NN_NO_CHECK(sourceCRS()), + interpolationCRS(), coordinateOperationAccuracies(), + hasBallparkTransformation())); + } + + auto formatter = io::PROJStringFormatter::create(); + formatter->startInversion(); + try { + formatter->ingestPROJString(projString_); + } catch (const io::ParsingException &e) { + throw util::UnsupportedOperationException( + std::string("PROJBasedOperation::inverse() failed: ") + e.what()); + } + formatter->stopInversion(); + + auto op = PROJBasedOperation::create( + createPropertiesForInverse(this, false, false), formatter->toString(), + targetCRS(), sourceCRS(), coordinateOperationAccuracies()); + if (sourceCRS() && targetCRS()) { + op->setCRSs(NN_NO_CHECK(targetCRS()), NN_NO_CHECK(sourceCRS()), + interpolationCRS()); + } + op->setHasBallparkTransformation(hasBallparkTransformation()); + return util::nn_static_pointer_cast<CoordinateOperation>(op); +} + +// --------------------------------------------------------------------------- + +void PROJBasedOperation::_exportToWKT(io::WKTFormatter *formatter) const { + + if (sourceCRS() && targetCRS()) { + exportTransformationToWKT(formatter); + return; + } + + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + throw io::FormattingException( + "PROJBasedOperation can only be exported to WKT2"); + } + + formatter->startNode(io::WKTConstants::CONVERSION, false); + formatter->addQuotedString(nameStr()); + method()->_exportToWKT(formatter); + + for (const auto ¶mValue : parameterValues()) { + paramValue->_exportToWKT(formatter); + } + formatter->endNode(); +} + +// --------------------------------------------------------------------------- + +void PROJBasedOperation::_exportToJSON( + io::JSONFormatter *formatter) const // throw(FormattingException) +{ + auto writer = formatter->writer(); + auto objectContext(formatter->MakeObjectContext( + (sourceCRS() && targetCRS()) ? "Transformation" : "Conversion", + !identifiers().empty())); + + writer->AddObjKey("name"); + auto l_name = nameStr(); + if (l_name.empty()) { + writer->Add("unnamed"); + } else { + writer->Add(l_name); + } + + if (sourceCRS() && targetCRS()) { + writer->AddObjKey("source_crs"); + formatter->setAllowIDInImmediateChild(); + sourceCRS()->_exportToJSON(formatter); + + writer->AddObjKey("target_crs"); + formatter->setAllowIDInImmediateChild(); + targetCRS()->_exportToJSON(formatter); + } + + writer->AddObjKey("method"); + formatter->setOmitTypeInImmediateChild(); + formatter->setAllowIDInImmediateChild(); + method()->_exportToJSON(formatter); + + const auto &l_parameterValues = parameterValues(); + if (!l_parameterValues.empty()) { + writer->AddObjKey("parameters"); + { + auto parametersContext(writer->MakeArrayContext(false)); + for (const auto &genOpParamvalue : l_parameterValues) { + formatter->setAllowIDInImmediateChild(); + formatter->setOmitTypeInImmediateChild(); + genOpParamvalue->_exportToJSON(formatter); + } + } + } +} + +// --------------------------------------------------------------------------- + +void PROJBasedOperation::_exportToPROJString( + io::PROJStringFormatter *formatter) const { + if (projStringExportable_) { + if (inverse_) { + formatter->startInversion(); + } + projStringExportable_->_exportToPROJString(formatter); + if (inverse_) { + formatter->stopInversion(); + } + return; + } + + try { + formatter->ingestPROJString(projString_); + } catch (const io::ParsingException &e) { + throw io::FormattingException( + std::string("PROJBasedOperation::exportToPROJString() failed: ") + + e.what()); + } +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr PROJBasedOperation::_shallowClone() const { + auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(*this); + op->assignSelf(op); + op->setCRSs(this, false); + return util::nn_static_pointer_cast<CoordinateOperation>(op); +} + +// --------------------------------------------------------------------------- + +std::set<GridDescription> +PROJBasedOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext, + bool considerKnownGridsAsAvailable) const { + std::set<GridDescription> res; + + try { + auto formatterOut = io::PROJStringFormatter::create(); + auto formatter = io::PROJStringFormatter::create(); + formatter->ingestPROJString(exportToPROJString(formatterOut.get())); + const auto usedGridNames = formatter->getUsedGridNames(); + for (const auto &shortName : usedGridNames) { + GridDescription desc; + desc.shortName = shortName; + if (databaseContext) { + databaseContext->lookForGridInfo( + desc.shortName, considerKnownGridsAsAvailable, + desc.fullName, desc.packageName, desc.url, + desc.directDownload, desc.openLicense, desc.available); + } + res.insert(desc); + } + } catch (const io::ParsingException &) { + } + + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation + +NS_PROJ_END diff --git a/src/iso19111/operation/singleoperation.cpp b/src/iso19111/operation/singleoperation.cpp new file mode 100644 index 00000000..0cd7b57a --- /dev/null +++ b/src/iso19111/operation/singleoperation.cpp @@ -0,0 +1,2218 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/common.hpp" +#include "proj/coordinateoperation.hpp" +#include "proj/crs.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include "coordinateoperation_internal.hpp" +#include "coordinateoperation_private.hpp" +#include "operationmethod_private.hpp" +#include "oputils.hpp" +#include "parammappings.hpp" + +// PROJ include order is sensitive +// clang-format off +#include "proj.h" +#include "proj_internal.h" // M_PI +// clang-format on +#include "proj_constants.h" +#include "proj_json_streaming_writer.hpp" + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <cstring> +#include <memory> +#include <set> +#include <string> +#include <vector> + +using namespace NS_PROJ::internal; + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection( + const std::string &message) + : InvalidOperation(message) {} + +InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection( + const InvalidOperationEmptyIntersection &) = default; + +InvalidOperationEmptyIntersection::~InvalidOperationEmptyIntersection() = + default; + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +GridDescription::GridDescription() + : shortName{}, fullName{}, packageName{}, url{}, directDownload(false), + openLicense(false), available(false) {} + +GridDescription::~GridDescription() = default; + +GridDescription::GridDescription(const GridDescription &) = default; + +GridDescription::GridDescription(GridDescription &&other) noexcept + : shortName(std::move(other.shortName)), + fullName(std::move(other.fullName)), + packageName(std::move(other.packageName)), + url(std::move(other.url)), + directDownload(other.directDownload), + openLicense(other.openLicense), + available(other.available) {} + +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateOperation::CoordinateOperation() + : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +CoordinateOperation::CoordinateOperation(const CoordinateOperation &other) + : ObjectUsage(other), d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateOperation::~CoordinateOperation() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the version of the coordinate transformation (i.e. + * instantiation + * due to the stochastic nature of the parameters). + * + * Mandatory when describing a coordinate transformation or point motion + * operation, and should not be supplied for a coordinate conversion. + * + * @return version or empty. + */ +const util::optional<std::string> & +CoordinateOperation::operationVersion() const { + return d->operationVersion_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return estimate(s) of the impact of this coordinate operation on + * point accuracy. + * + * Gives position error estimates for target coordinates of this coordinate + * operation, assuming no errors in source coordinates. + * + * @return estimate(s) or empty vector. + */ +const std::vector<metadata::PositionalAccuracyNNPtr> & +CoordinateOperation::coordinateOperationAccuracies() const { + return d->coordinateOperationAccuracies_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the source CRS of this coordinate operation. + * + * This should not be null, expect for of a derivingConversion of a DerivedCRS + * when the owning DerivedCRS has been destroyed. + * + * @return source CRS, or null. + */ +const crs::CRSPtr CoordinateOperation::sourceCRS() const { + return d->sourceCRSWeak_.lock(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the target CRS of this coordinate operation. + * + * This should not be null, expect for of a derivingConversion of a DerivedCRS + * when the owning DerivedCRS has been destroyed. + * + * @return target CRS, or null. + */ +const crs::CRSPtr CoordinateOperation::targetCRS() const { + return d->targetCRSWeak_.lock(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the interpolation CRS of this coordinate operation. + * + * @return interpolation CRS, or null. + */ +const crs::CRSPtr &CoordinateOperation::interpolationCRS() const { + return d->interpolationCRS_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the source epoch of coordinates. + * + * @return source epoch of coordinates, or empty. + */ +const util::optional<common::DataEpoch> & +CoordinateOperation::sourceCoordinateEpoch() const { + return d->sourceCoordinateEpoch_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the target epoch of coordinates. + * + * @return target epoch of coordinates, or empty. + */ +const util::optional<common::DataEpoch> & +CoordinateOperation::targetCoordinateEpoch() const { + return d->targetCoordinateEpoch_; +} + +// --------------------------------------------------------------------------- + +void CoordinateOperation::setWeakSourceTargetCRS( + std::weak_ptr<crs::CRS> sourceCRSIn, std::weak_ptr<crs::CRS> targetCRSIn) { + d->sourceCRSWeak_ = sourceCRSIn; + d->targetCRSWeak_ = targetCRSIn; +} + +// --------------------------------------------------------------------------- + +void CoordinateOperation::setCRSs(const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, + const crs::CRSPtr &interpolationCRSIn) { + d->strongRef_ = + internal::make_unique<Private::CRSStrongRef>(sourceCRSIn, targetCRSIn); + d->sourceCRSWeak_ = sourceCRSIn.as_nullable(); + d->targetCRSWeak_ = targetCRSIn.as_nullable(); + d->interpolationCRS_ = interpolationCRSIn; +} +// --------------------------------------------------------------------------- + +void CoordinateOperation::setCRSs(const CoordinateOperation *in, + bool inverseSourceTarget) { + auto l_sourceCRS = in->sourceCRS(); + auto l_targetCRS = in->targetCRS(); + if (l_sourceCRS && l_targetCRS) { + auto nn_sourceCRS = NN_NO_CHECK(l_sourceCRS); + auto nn_targetCRS = NN_NO_CHECK(l_targetCRS); + if (inverseSourceTarget) { + setCRSs(nn_targetCRS, nn_sourceCRS, in->interpolationCRS()); + } else { + setCRSs(nn_sourceCRS, nn_targetCRS, in->interpolationCRS()); + } + } +} + +// --------------------------------------------------------------------------- + +void CoordinateOperation::setAccuracies( + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + d->coordinateOperationAccuracies_ = accuracies; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether a coordinate operation can be instantiated as + * a PROJ pipeline, checking in particular that referenced grids are + * available. + */ +bool CoordinateOperation::isPROJInstantiable( + const io::DatabaseContextPtr &databaseContext, + bool considerKnownGridsAsAvailable) const { + try { + exportToPROJString(io::PROJStringFormatter::create().get()); + } catch (const std::exception &) { + return false; + } + for (const auto &gridDesc : + gridsNeeded(databaseContext, considerKnownGridsAsAvailable)) { + if (!gridDesc.available) { + return false; + } + } + return true; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether a coordinate operation has a "ballpark" + * transformation, + * that is a very approximate one, due to lack of more accurate transformations. + * + * Typically a null geographic offset between two horizontal datum, or a + * null vertical offset (or limited to unit changes) between two vertical + * datum. Errors of several tens to one hundred meters might be expected, + * compared to more accurate transformations. + */ +bool CoordinateOperation::hasBallparkTransformation() const { + return d->hasBallparkTransformation_; +} + +// --------------------------------------------------------------------------- + +void CoordinateOperation::setHasBallparkTransformation(bool b) { + d->hasBallparkTransformation_ = b; +} + +// --------------------------------------------------------------------------- + +void CoordinateOperation::setProperties( + const util::PropertyMap &properties) // throw(InvalidValueTypeException) +{ + ObjectUsage::setProperties(properties); + properties.getStringValue(OPERATION_VERSION_KEY, d->operationVersion_); +} + +// --------------------------------------------------------------------------- + +/** \brief Return a variation of the current coordinate operation whose axis + * order is the one expected for visualization purposes. + */ +CoordinateOperationNNPtr +CoordinateOperation::normalizeForVisualization() const { + auto l_sourceCRS = sourceCRS(); + auto l_targetCRS = targetCRS(); + if (!l_sourceCRS || !l_targetCRS) { + throw util::UnsupportedOperationException( + "Cannot retrieve source or target CRS"); + } + const bool swapSource = + l_sourceCRS->mustAxisOrderBeSwitchedForVisualization(); + const bool swapTarget = + l_targetCRS->mustAxisOrderBeSwitchedForVisualization(); + auto l_this = NN_NO_CHECK(std::dynamic_pointer_cast<CoordinateOperation>( + shared_from_this().as_nullable())); + if (!swapSource && !swapTarget) { + return l_this; + } + std::vector<CoordinateOperationNNPtr> subOps; + if (swapSource) { + auto op = Conversion::createAxisOrderReversal(false); + op->setCRSs(l_sourceCRS->normalizeForVisualization(), + NN_NO_CHECK(l_sourceCRS), nullptr); + subOps.emplace_back(op); + } + subOps.emplace_back(l_this); + if (swapTarget) { + auto op = Conversion::createAxisOrderReversal(false); + op->setCRSs(NN_NO_CHECK(l_targetCRS), + l_targetCRS->normalizeForVisualization(), nullptr); + subOps.emplace_back(op); + } + return util::nn_static_pointer_cast<CoordinateOperation>( + ConcatenatedOperation::createComputeMetadata(subOps, true)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateOperationNNPtr CoordinateOperation::shallowClone() const { + return _shallowClone(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +OperationMethod::OperationMethod() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +OperationMethod::OperationMethod(const OperationMethod &other) + : IdentifiedObject(other), d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +OperationMethod::~OperationMethod() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the formula(s) or procedure used by this coordinate operation + * method. + * + * This may be a reference to a publication (in which case use + * formulaCitation()). + * + * Note that the operation method may not be analytic, in which case this + * attribute references or contains the procedure, not an analytic formula. + * + * @return the formula, or empty. + */ +const util::optional<std::string> &OperationMethod::formula() PROJ_PURE_DEFN { + return d->formula_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return a reference to a publication giving the formula(s) or + * procedure + * used by the coordinate operation method. + * + * @return the formula citation, or empty. + */ +const util::optional<metadata::Citation> & +OperationMethod::formulaCitation() PROJ_PURE_DEFN { + return d->formulaCitation_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the parameters of this operation method. + * + * @return the parameters. + */ +const std::vector<GeneralOperationParameterNNPtr> & +OperationMethod::parameters() PROJ_PURE_DEFN { + return d->parameters_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a operation method from a vector of + * GeneralOperationParameter. + * + * @param properties See \ref general_properties. At minimum the name should be + * defined. + * @param parameters Vector of GeneralOperationParameterNNPtr. + * @return a new OperationMethod. + */ +OperationMethodNNPtr OperationMethod::create( + const util::PropertyMap &properties, + const std::vector<GeneralOperationParameterNNPtr> ¶meters) { + OperationMethodNNPtr method( + OperationMethod::nn_make_shared<OperationMethod>()); + method->assignSelf(method); + method->setProperties(properties); + method->d->parameters_ = parameters; + properties.getStringValue("proj_method", method->d->projMethodOverride_); + return method; +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a operation method from a vector of OperationParameter. + * + * @param properties See \ref general_properties. At minimum the name should be + * defined. + * @param parameters Vector of OperationParameterNNPtr. + * @return a new OperationMethod. + */ +OperationMethodNNPtr OperationMethod::create( + const util::PropertyMap &properties, + const std::vector<OperationParameterNNPtr> ¶meters) { + std::vector<GeneralOperationParameterNNPtr> parametersGeneral; + parametersGeneral.reserve(parameters.size()); + for (const auto &p : parameters) { + parametersGeneral.push_back(p); + } + return create(properties, parametersGeneral); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the EPSG code, either directly, or through the name + * @return code, or 0 if not found + */ +int OperationMethod::getEPSGCode() PROJ_PURE_DEFN { + int epsg_code = IdentifiedObject::getEPSGCode(); + if (epsg_code == 0) { + auto l_name = nameStr(); + if (ends_with(l_name, " (3D)")) { + l_name.resize(l_name.size() - strlen(" (3D)")); + } + size_t nMethodNameCodes = 0; + const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes); + for (size_t i = 0; i < nMethodNameCodes; ++i) { + const auto &tuple = methodNameCodes[i]; + if (metadata::Identifier::isEquivalentName(l_name.c_str(), + tuple.name)) { + return tuple.epsg_code; + } + } + } + return epsg_code; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void OperationMethod::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::METHOD + : io::WKTConstants::PROJECTION, + !identifiers().empty()); + std::string l_name(nameStr()); + if (!isWKT2) { + const MethodMapping *mapping = getMapping(this); + if (mapping == nullptr) { + l_name = replaceAll(l_name, " ", "_"); + } else { + if (l_name == + PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) { + l_name = "Geostationary_Satellite"; + } else { + if (mapping->wkt1_name == nullptr) { + throw io::FormattingException( + std::string("Unsupported conversion method: ") + + mapping->wkt2_name); + } + l_name = mapping->wkt1_name; + } + } + } + formatter->addQuotedString(l_name); + if (formatter->outputId()) { + formatID(formatter); + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void OperationMethod::_exportToJSON( + io::JSONFormatter *formatter) const // throw(FormattingException) +{ + auto writer = formatter->writer(); + auto objectContext(formatter->MakeObjectContext("OperationMethod", + !identifiers().empty())); + + writer->AddObjKey("name"); + writer->Add(nameStr()); + + if (formatter->outputId()) { + formatID(formatter); + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool OperationMethod::_isEquivalentTo( + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { + auto otherOM = dynamic_cast<const OperationMethod *>(other); + if (otherOM == nullptr || + !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { + return false; + } + // TODO test formula and formulaCitation + const auto ¶ms = parameters(); + const auto &otherParams = otherOM->parameters(); + const auto paramsSize = params.size(); + if (paramsSize != otherParams.size()) { + return false; + } + if (criterion == util::IComparable::Criterion::STRICT) { + for (size_t i = 0; i < paramsSize; i++) { + if (!params[i]->_isEquivalentTo(otherParams[i].get(), criterion, + dbContext)) { + return false; + } + } + } else { + std::vector<bool> candidateIndices(paramsSize, true); + for (size_t i = 0; i < paramsSize; i++) { + bool found = false; + for (size_t j = 0; j < paramsSize; j++) { + if (candidateIndices[j] && + params[i]->_isEquivalentTo(otherParams[j].get(), criterion, + dbContext)) { + candidateIndices[j] = false; + found = true; + break; + } + } + if (!found) { + return false; + } + } + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeneralParameterValue::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeneralParameterValue::GeneralParameterValue() : d(nullptr) {} + +// --------------------------------------------------------------------------- + +GeneralParameterValue::GeneralParameterValue(const GeneralParameterValue &) + : d(nullptr) {} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeneralParameterValue::~GeneralParameterValue() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct OperationParameterValue::Private { + OperationParameterNNPtr parameter; + ParameterValueNNPtr parameterValue; + + Private(const OperationParameterNNPtr ¶meterIn, + const ParameterValueNNPtr &valueIn) + : parameter(parameterIn), parameterValue(valueIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +OperationParameterValue::OperationParameterValue( + const OperationParameterValue &other) + : GeneralParameterValue(other), + d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +OperationParameterValue::OperationParameterValue( + const OperationParameterNNPtr ¶meterIn, + const ParameterValueNNPtr &valueIn) + : GeneralParameterValue(), + d(internal::make_unique<Private>(parameterIn, valueIn)) {} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a OperationParameterValue. + * + * @param parameterIn Parameter (definition). + * @param valueIn Parameter value. + * @return a new OperationParameterValue. + */ +OperationParameterValueNNPtr +OperationParameterValue::create(const OperationParameterNNPtr ¶meterIn, + const ParameterValueNNPtr &valueIn) { + return OperationParameterValue::nn_make_shared<OperationParameterValue>( + parameterIn, valueIn); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +OperationParameterValue::~OperationParameterValue() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the parameter (definition) + * + * @return the parameter (definition). + */ +const OperationParameterNNPtr & +OperationParameterValue::parameter() PROJ_PURE_DEFN { + return d->parameter; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the parameter value. + * + * @return the parameter value. + */ +const ParameterValueNNPtr & +OperationParameterValue::parameterValue() PROJ_PURE_DEFN { + return d->parameterValue; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void OperationParameterValue::_exportToWKT( + // cppcheck-suppress passedByValue + io::WKTFormatter *formatter) const { + _exportToWKT(formatter, nullptr); +} + +void OperationParameterValue::_exportToWKT(io::WKTFormatter *formatter, + const MethodMapping *mapping) const { + const ParamMapping *paramMapping = + mapping ? getMapping(mapping, d->parameter) : nullptr; + if (paramMapping && paramMapping->wkt1_name == nullptr) { + return; + } + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (isWKT2 && parameterValue()->type() == ParameterValue::Type::FILENAME) { + formatter->startNode(io::WKTConstants::PARAMETERFILE, + !parameter()->identifiers().empty()); + } else { + formatter->startNode(io::WKTConstants::PARAMETER, + !parameter()->identifiers().empty()); + } + if (paramMapping) { + formatter->addQuotedString(paramMapping->wkt1_name); + } else { + formatter->addQuotedString(parameter()->nameStr()); + } + parameterValue()->_exportToWKT(formatter); + if (formatter->outputId()) { + parameter()->formatID(formatter); + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void OperationParameterValue::_exportToJSON( + io::JSONFormatter *formatter) const { + auto writer = formatter->writer(); + auto objectContext(formatter->MakeObjectContext( + "ParameterValue", !parameter()->identifiers().empty())); + + writer->AddObjKey("name"); + writer->Add(parameter()->nameStr()); + + const auto &l_value(parameterValue()); + if (l_value->type() == ParameterValue::Type::MEASURE) { + writer->AddObjKey("value"); + writer->Add(l_value->value().value(), 15); + writer->AddObjKey("unit"); + const auto &l_unit(l_value->value().unit()); + if (l_unit == common::UnitOfMeasure::METRE || + l_unit == common::UnitOfMeasure::DEGREE || + l_unit == common::UnitOfMeasure::SCALE_UNITY) { + writer->Add(l_unit.name()); + } else { + l_unit._exportToJSON(formatter); + } + } else if (l_value->type() == ParameterValue::Type::FILENAME) { + writer->AddObjKey("value"); + writer->Add(l_value->valueFile()); + } + + if (formatter->outputId()) { + parameter()->formatID(formatter); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +/** Utility method used on WKT2 import to convert from abridged transformation + * to "normal" transformation parameters. + */ +bool OperationParameterValue::convertFromAbridged( + const std::string ¶mName, double &val, + const common::UnitOfMeasure *&unit, int ¶mEPSGCode) { + if (metadata::Identifier::isEquivalentName( + paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION) || + paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) { + unit = &common::UnitOfMeasure::METRE; + paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION; + return true; + } else if (metadata::Identifier::isEquivalentName( + paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION) || + paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) { + unit = &common::UnitOfMeasure::METRE; + paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION; + return true; + } else if (metadata::Identifier::isEquivalentName( + paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION) || + paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) { + unit = &common::UnitOfMeasure::METRE; + paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION; + return true; + } else if (metadata::Identifier::isEquivalentName( + paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_ROTATION) || + paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_ROTATION) { + unit = &common::UnitOfMeasure::ARC_SECOND; + paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_ROTATION; + return true; + } else if (metadata::Identifier::isEquivalentName( + paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_ROTATION) || + paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) { + unit = &common::UnitOfMeasure::ARC_SECOND; + paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_ROTATION; + return true; + + } else if (metadata::Identifier::isEquivalentName( + paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_ROTATION) || + paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) { + unit = &common::UnitOfMeasure::ARC_SECOND; + paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_ROTATION; + return true; + + } else if (metadata::Identifier::isEquivalentName( + paramName.c_str(), EPSG_NAME_PARAMETER_SCALE_DIFFERENCE) || + paramEPSGCode == EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) { + val = (val - 1.0) * 1e6; + unit = &common::UnitOfMeasure::PARTS_PER_MILLION; + paramEPSGCode = EPSG_CODE_PARAMETER_SCALE_DIFFERENCE; + return true; + } + return false; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool OperationParameterValue::_isEquivalentTo( + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { + auto otherOPV = dynamic_cast<const OperationParameterValue *>(other); + if (otherOPV == nullptr) { + return false; + } + if (!d->parameter->_isEquivalentTo(otherOPV->d->parameter.get(), criterion, + dbContext)) { + return false; + } + if (criterion == util::IComparable::Criterion::STRICT) { + return d->parameterValue->_isEquivalentTo( + otherOPV->d->parameterValue.get(), criterion); + } + if (d->parameterValue->_isEquivalentTo(otherOPV->d->parameterValue.get(), + criterion, dbContext)) { + return true; + } + if (d->parameter->getEPSGCode() == + EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE || + d->parameter->getEPSGCode() == + EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) { + if (parameterValue()->type() == ParameterValue::Type::MEASURE && + otherOPV->parameterValue()->type() == + ParameterValue::Type::MEASURE) { + const double a = std::fmod(parameterValue()->value().convertToUnit( + common::UnitOfMeasure::DEGREE) + + 360.0, + 360.0); + const double b = + std::fmod(otherOPV->parameterValue()->value().convertToUnit( + common::UnitOfMeasure::DEGREE) + + 360.0, + 360.0); + return std::fabs(a - b) <= 1e-10 * std::fabs(a); + } + } + return false; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeneralOperationParameter::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +GeneralOperationParameter::GeneralOperationParameter() : d(nullptr) {} + +// --------------------------------------------------------------------------- + +GeneralOperationParameter::GeneralOperationParameter( + const GeneralOperationParameter &other) + : IdentifiedObject(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeneralOperationParameter::~GeneralOperationParameter() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct OperationParameter::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +OperationParameter::OperationParameter() : d(nullptr) {} + +// --------------------------------------------------------------------------- + +OperationParameter::OperationParameter(const OperationParameter &other) + : GeneralOperationParameter(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +OperationParameter::~OperationParameter() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a OperationParameter. + * + * @param properties See \ref general_properties. At minimum the name should be + * defined. + * @return a new OperationParameter. + */ +OperationParameterNNPtr +OperationParameter::create(const util::PropertyMap &properties) { + OperationParameterNNPtr op( + OperationParameter::nn_make_shared<OperationParameter>()); + op->assignSelf(op); + op->setProperties(properties); + return op; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool OperationParameter::_isEquivalentTo( + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { + auto otherOP = dynamic_cast<const OperationParameter *>(other); + if (otherOP == nullptr) { + return false; + } + if (criterion == util::IComparable::Criterion::STRICT) { + return IdentifiedObject::_isEquivalentTo(other, criterion, dbContext); + } + if (IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { + return true; + } + auto l_epsgCode = getEPSGCode(); + return l_epsgCode != 0 && l_epsgCode == otherOP->getEPSGCode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void OperationParameter::_exportToWKT(io::WKTFormatter *) const {} + +// --------------------------------------------------------------------------- + +/** \brief Return the name of a parameter designed by its EPSG code + * @return name, or nullptr if not found + */ +const char *OperationParameter::getNameForEPSGCode(int epsg_code) noexcept { + size_t nParamNameCodes = 0; + const auto paramNameCodes = getParamNameCodes(nParamNameCodes); + for (size_t i = 0; i < nParamNameCodes; ++i) { + const auto &tuple = paramNameCodes[i]; + if (tuple.epsg_code == epsg_code) { + return tuple.name; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the EPSG code, either directly, or through the name + * @return code, or 0 if not found + */ +int OperationParameter::getEPSGCode() PROJ_PURE_DEFN { + int epsg_code = IdentifiedObject::getEPSGCode(); + if (epsg_code == 0) { + const auto &l_name = nameStr(); + size_t nParamNameCodes = 0; + const auto paramNameCodes = getParamNameCodes(nParamNameCodes); + for (size_t i = 0; i < nParamNameCodes; ++i) { + const auto &tuple = paramNameCodes[i]; + if (metadata::Identifier::isEquivalentName(l_name.c_str(), + tuple.name)) { + return tuple.epsg_code; + } + } + if (metadata::Identifier::isEquivalentName(l_name.c_str(), + "Latitude of origin")) { + return EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN; + } + if (metadata::Identifier::isEquivalentName(l_name.c_str(), + "Scale factor")) { + return EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN; + } + } + return epsg_code; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct SingleOperation::Private { + std::vector<GeneralParameterValueNNPtr> parameterValues_{}; + OperationMethodNNPtr method_; + + explicit Private(const OperationMethodNNPtr &methodIn) + : method_(methodIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +SingleOperation::SingleOperation(const OperationMethodNNPtr &methodIn) + : d(internal::make_unique<Private>(methodIn)) {} + +// --------------------------------------------------------------------------- + +SingleOperation::SingleOperation(const SingleOperation &other) + : +#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) + CoordinateOperation(other), +#endif + d(internal::make_unique<Private>(*other.d)) { +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +SingleOperation::~SingleOperation() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the parameter values. + * + * @return the parameter values. + */ +const std::vector<GeneralParameterValueNNPtr> & +SingleOperation::parameterValues() PROJ_PURE_DEFN { + return d->parameterValues_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the operation method associated to the operation. + * + * @return the operation method. + */ +const OperationMethodNNPtr &SingleOperation::method() PROJ_PURE_DEFN { + return d->method_; +} + +// --------------------------------------------------------------------------- + +void SingleOperation::setParameterValues( + const std::vector<GeneralParameterValueNNPtr> &values) { + d->parameterValues_ = values; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const ParameterValuePtr nullParameterValue; +//! @endcond + +/** \brief Return the parameter value corresponding to a parameter name or + * EPSG code + * + * @param paramName the parameter name (or empty, in which case epsg_code + * should be non zero) + * @param epsg_code the parameter EPSG code (possibly zero) + * @return the value, or nullptr if not found. + */ +const ParameterValuePtr & +SingleOperation::parameterValue(const std::string ¶mName, + int epsg_code) const noexcept { + if (epsg_code) { + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = opParamvalue->parameter(); + if (parameter->getEPSGCode() == epsg_code) { + return opParamvalue->parameterValue(); + } + } + } + } + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = opParamvalue->parameter(); + if (metadata::Identifier::isEquivalentName( + paramName.c_str(), parameter->nameStr().c_str())) { + return opParamvalue->parameterValue(); + } + } + } + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = opParamvalue->parameter(); + if (areEquivalentParameters(paramName, parameter->nameStr())) { + return opParamvalue->parameterValue(); + } + } + } + return nullParameterValue; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the parameter value corresponding to a EPSG code + * + * @param epsg_code the parameter EPSG code + * @return the value, or nullptr if not found. + */ +const ParameterValuePtr &SingleOperation::parameterValue(int epsg_code) const + noexcept { + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = opParamvalue->parameter(); + if (parameter->getEPSGCode() == epsg_code) { + return opParamvalue->parameterValue(); + } + } + } + return nullParameterValue; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the parameter value, as a measure, corresponding to a + * parameter name or EPSG code + * + * @param paramName the parameter name (or empty, in which case epsg_code + * should be non zero) + * @param epsg_code the parameter EPSG code (possibly zero) + * @return the measure, or the empty Measure() object if not found. + */ +const common::Measure & +SingleOperation::parameterValueMeasure(const std::string ¶mName, + int epsg_code) const noexcept { + const auto &val = parameterValue(paramName, epsg_code); + if (val && val->type() == ParameterValue::Type::MEASURE) { + return val->value(); + } + return nullMeasure; +} + +/** \brief Return the parameter value, as a measure, corresponding to a + * EPSG code + * + * @param epsg_code the parameter EPSG code + * @return the measure, or the empty Measure() object if not found. + */ +const common::Measure & +SingleOperation::parameterValueMeasure(int epsg_code) const noexcept { + const auto &val = parameterValue(epsg_code); + if (val && val->type() == ParameterValue::Type::MEASURE) { + return val->value(); + } + return nullMeasure; +} + +//! @cond Doxygen_Suppress + +double SingleOperation::parameterValueNumericAsSI(int epsg_code) const + noexcept { + const auto &val = parameterValue(epsg_code); + if (val && val->type() == ParameterValue::Type::MEASURE) { + return val->value().getSIValue(); + } + return 0.0; +} + +double SingleOperation::parameterValueNumeric( + int epsg_code, const common::UnitOfMeasure &targetUnit) const noexcept { + const auto &val = parameterValue(epsg_code); + if (val && val->type() == ParameterValue::Type::MEASURE) { + return val->value().convertToUnit(targetUnit); + } + return 0.0; +} + +double SingleOperation::parameterValueNumeric( + const char *param_name, const common::UnitOfMeasure &targetUnit) const + noexcept { + const auto &val = parameterValue(param_name, 0); + if (val && val->type() == ParameterValue::Type::MEASURE) { + return val->value().convertToUnit(targetUnit); + } + return 0.0; +} + +//! @endcond +// --------------------------------------------------------------------------- + +/** \brief Instantiate a PROJ-based single operation. + * + * \note The operation might internally be a pipeline chaining several + * operations. + * The use of the SingleOperation modeling here is mostly to be able to get + * the PROJ string as a parameter. + * + * @param properties Properties + * @param PROJString the PROJ string. + * @param sourceCRS source CRS (might be null). + * @param targetCRS target CRS (might be null). + * @param accuracies Vector of positional accuracy (might be empty). + * @return the new instance + */ +SingleOperationNNPtr SingleOperation::createPROJBased( + const util::PropertyMap &properties, const std::string &PROJString, + const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return util::nn_static_pointer_cast<SingleOperation>( + PROJBasedOperation::create(properties, PROJString, sourceCRS, targetCRS, + accuracies)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool SingleOperation::_isEquivalentTo( + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { + return _isEquivalentTo(other, criterion, dbContext, false); +} + +bool SingleOperation::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext, + bool inOtherDirection) const { + + auto otherSO = dynamic_cast<const SingleOperation *>(other); + if (otherSO == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { + return false; + } + + const int methodEPSGCode = d->method_->getEPSGCode(); + const int otherMethodEPSGCode = otherSO->d->method_->getEPSGCode(); + + bool equivalentMethods = + (criterion == util::IComparable::Criterion::EQUIVALENT && + methodEPSGCode != 0 && methodEPSGCode == otherMethodEPSGCode) || + d->method_->_isEquivalentTo(otherSO->d->method_.get(), criterion, + dbContext); + if (!equivalentMethods && + criterion == util::IComparable::Criterion::EQUIVALENT) { + if ((methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA && + otherMethodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) || + (otherMethodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA && + methodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) || + (methodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA && + otherMethodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) || + (otherMethodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA && + methodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) || + (methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL && + otherMethodEPSGCode == + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) || + (otherMethodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL && + methodEPSGCode == + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) { + auto geodCRS = + dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get()); + auto otherGeodCRS = dynamic_cast<const crs::GeodeticCRS *>( + otherSO->sourceCRS().get()); + if (geodCRS && otherGeodCRS && geodCRS->ellipsoid()->isSphere() && + otherGeodCRS->ellipsoid()->isSphere()) { + equivalentMethods = true; + } + } + } + + if (!equivalentMethods) { + if (criterion == util::IComparable::Criterion::EQUIVALENT) { + + const auto isTOWGS84Transf = [](int code) { + return code == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || + code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || + code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || + code == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || + code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || + code == + EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || + code == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D || + code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D || + code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D; + }; + + // Translation vs (PV or CF) + // or different PV vs CF convention + if (isTOWGS84Transf(methodEPSGCode) && + isTOWGS84Transf(otherMethodEPSGCode)) { + auto transf = static_cast<const Transformation *>(this); + auto otherTransf = static_cast<const Transformation *>(otherSO); + auto params = transf->getTOWGS84Parameters(); + auto otherParams = otherTransf->getTOWGS84Parameters(); + assert(params.size() == 7); + assert(otherParams.size() == 7); + for (size_t i = 0; i < 7; i++) { + if (std::fabs(params[i] - otherParams[i]) > + 1e-10 * std::fabs(params[i])) { + return false; + } + } + return true; + } + + // _1SP methods can sometimes be equivalent to _2SP ones + // Check it by using convertToOtherMethod() + if (methodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP && + otherMethodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { + // Convert from 2SP to 1SP as the other direction has more + // degree of liberties. + return otherSO->_isEquivalentTo(this, criterion, dbContext); + } else if ((methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A && + otherMethodEPSGCode == + EPSG_CODE_METHOD_MERCATOR_VARIANT_B) || + (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && + otherMethodEPSGCode == + EPSG_CODE_METHOD_MERCATOR_VARIANT_A) || + (methodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP && + otherMethodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)) { + auto conv = dynamic_cast<const Conversion *>(this); + if (conv) { + auto eqConv = + conv->convertToOtherMethod(otherMethodEPSGCode); + if (eqConv) { + return eqConv->_isEquivalentTo(other, criterion, + dbContext); + } + } + } + } + + return false; + } + + const auto &values = d->parameterValues_; + const auto &otherValues = otherSO->d->parameterValues_; + const auto valuesSize = values.size(); + const auto otherValuesSize = otherValues.size(); + if (criterion == util::IComparable::Criterion::STRICT) { + if (valuesSize != otherValuesSize) { + return false; + } + for (size_t i = 0; i < valuesSize; i++) { + if (!values[i]->_isEquivalentTo(otherValues[i].get(), criterion, + dbContext)) { + return false; + } + } + return true; + } + + std::vector<bool> candidateIndices(otherValuesSize, true); + bool equivalent = true; + bool foundMissingArgs = valuesSize != otherValuesSize; + + for (size_t i = 0; equivalent && i < valuesSize; i++) { + auto opParamvalue = + dynamic_cast<const OperationParameterValue *>(values[i].get()); + if (!opParamvalue) + return false; + + equivalent = false; + bool sameNameDifferentValue = false; + for (size_t j = 0; j < otherValuesSize; j++) { + if (candidateIndices[j] && + values[i]->_isEquivalentTo(otherValues[j].get(), criterion, + dbContext)) { + candidateIndices[j] = false; + equivalent = true; + break; + } else if (candidateIndices[j]) { + auto otherOpParamvalue = + dynamic_cast<const OperationParameterValue *>( + otherValues[j].get()); + if (!otherOpParamvalue) + return false; + sameNameDifferentValue = + opParamvalue->parameter()->_isEquivalentTo( + otherOpParamvalue->parameter().get(), criterion, + dbContext); + if (sameNameDifferentValue) { + candidateIndices[j] = false; + break; + } + } + } + + if (!equivalent && + methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { + // For LCC_2SP, the standard parallels can be switched and + // this will result in the same result. + const int paramEPSGCode = opParamvalue->parameter()->getEPSGCode(); + if (paramEPSGCode == + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL || + paramEPSGCode == + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) { + auto value_1st = parameterValue( + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL); + auto value_2nd = parameterValue( + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL); + if (value_1st && value_2nd) { + equivalent = + value_1st->_isEquivalentTo( + otherSO + ->parameterValue( + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) + .get(), + criterion, dbContext) && + value_2nd->_isEquivalentTo( + otherSO + ->parameterValue( + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) + .get(), + criterion, dbContext); + } + } + } + + if (equivalent) { + continue; + } + + if (sameNameDifferentValue) { + break; + } + + // If there are parameters in this method not found in the other one, + // check that they are set to a default neutral value, that is 1 + // for scale, and 0 otherwise. + foundMissingArgs = true; + const auto &value = opParamvalue->parameterValue(); + if (value->type() != ParameterValue::Type::MEASURE) { + break; + } + if (value->value().unit().type() == + common::UnitOfMeasure::Type::SCALE) { + equivalent = value->value().getSIValue() == 1.0; + } else { + equivalent = value->value().getSIValue() == 0.0; + } + } + + // In the case the arguments don't perfectly match, try the reverse + // check. + if (equivalent && foundMissingArgs && !inOtherDirection) { + return otherSO->_isEquivalentTo(this, criterion, dbContext, true); + } + + // Equivalent formulations of 2SP can have different parameters + // Then convert to 1SP and compare. + if (!equivalent && + methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { + auto conv = dynamic_cast<const Conversion *>(this); + auto otherConv = dynamic_cast<const Conversion *>(other); + if (conv && otherConv) { + auto thisAs1SP = conv->convertToOtherMethod( + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); + auto otherAs1SP = otherConv->convertToOtherMethod( + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); + if (thisAs1SP && otherAs1SP) { + equivalent = thisAs1SP->_isEquivalentTo(otherAs1SP.get(), + criterion, dbContext); + } + } + } + return equivalent; +} +//! @endcond + +// --------------------------------------------------------------------------- + +std::set<GridDescription> +SingleOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext, + bool considerKnownGridsAsAvailable) const { + std::set<GridDescription> res; + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto &value = opParamvalue->parameterValue(); + if (value->type() == ParameterValue::Type::FILENAME) { + const auto gridNames = split(value->valueFile(), ","); + for (const auto &gridName : gridNames) { + GridDescription desc; + desc.shortName = gridName; + if (databaseContext) { + databaseContext->lookForGridInfo( + desc.shortName, considerKnownGridsAsAvailable, + desc.fullName, desc.packageName, desc.url, + desc.directDownload, desc.openLicense, + desc.available); + } + res.insert(desc); + } + } + } + } + return res; +} + +// --------------------------------------------------------------------------- + +/** \brief Validate the parameters used by a coordinate operation. + * + * Return whether the method is known or not, or a list of missing or extra + * parameters for the operations recognized by this implementation. + */ +std::list<std::string> SingleOperation::validateParameters() const { + std::list<std::string> res; + + const auto &l_method = method(); + const auto &methodName = l_method->nameStr(); + const MethodMapping *methodMapping = nullptr; + const auto methodEPSGCode = l_method->getEPSGCode(); + size_t nProjectionMethodMappings = 0; + const auto projectionMethodMappings = + getProjectionMethodMappings(nProjectionMethodMappings); + for (size_t i = 0; i < nProjectionMethodMappings; ++i) { + const auto &mapping = projectionMethodMappings[i]; + if (metadata::Identifier::isEquivalentName(mapping.wkt2_name, + methodName.c_str()) || + (methodEPSGCode != 0 && methodEPSGCode == mapping.epsg_code)) { + methodMapping = &mapping; + } + } + if (methodMapping == nullptr) { + size_t nOtherMethodMappings = 0; + const auto otherMethodMappings = + getOtherMethodMappings(nOtherMethodMappings); + for (size_t i = 0; i < nOtherMethodMappings; ++i) { + const auto &mapping = otherMethodMappings[i]; + if (metadata::Identifier::isEquivalentName(mapping.wkt2_name, + methodName.c_str()) || + (methodEPSGCode != 0 && methodEPSGCode == mapping.epsg_code)) { + methodMapping = &mapping; + } + } + } + if (!methodMapping) { + res.emplace_back("Unknown method " + methodName); + return res; + } + if (methodMapping->wkt2_name != methodName) { + if (metadata::Identifier::isEquivalentName(methodMapping->wkt2_name, + methodName.c_str())) { + std::string msg("Method name "); + msg += methodName; + msg += " is equivalent to official "; + msg += methodMapping->wkt2_name; + msg += " but not strictly equal"; + res.emplace_back(msg); + } else { + std::string msg("Method name "); + msg += methodName; + msg += ", matched to "; + msg += methodMapping->wkt2_name; + msg += ", through its EPSG code has not an equivalent name"; + res.emplace_back(msg); + } + } + if (methodEPSGCode != 0 && methodEPSGCode != methodMapping->epsg_code) { + std::string msg("Method of EPSG code "); + msg += toString(methodEPSGCode); + msg += " does not match official code ("; + msg += toString(methodMapping->epsg_code); + msg += ')'; + res.emplace_back(msg); + } + + // Check if expected parameters are found + for (int i = 0; + methodMapping->params && methodMapping->params[i] != nullptr; ++i) { + const auto *paramMapping = methodMapping->params[i]; + + const OperationParameterValue *opv = nullptr; + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = opParamvalue->parameter(); + if ((paramMapping->epsg_code != 0 && + parameter->getEPSGCode() == paramMapping->epsg_code) || + ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) { + opv = opParamvalue; + break; + } + } + } + + if (!opv) { + if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL || + methodEPSGCode == + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) && + paramMapping == ¶mLatitudeNatOrigin) { + // extension of EPSG used by GDAL/PROJ, so we should not + // warn on its absence. + continue; + } + std::string msg("Cannot find expected parameter "); + msg += paramMapping->wkt2_name; + res.emplace_back(msg); + continue; + } + const auto ¶meter = opv->parameter(); + if (paramMapping->wkt2_name != parameter->nameStr()) { + if (ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) { + std::string msg("Parameter name "); + msg += parameter->nameStr(); + msg += " is equivalent to official "; + msg += paramMapping->wkt2_name; + msg += " but not strictly equal"; + res.emplace_back(msg); + } else { + std::string msg("Parameter name "); + msg += parameter->nameStr(); + msg += ", matched to "; + msg += paramMapping->wkt2_name; + msg += ", through its EPSG code has not an equivalent name"; + res.emplace_back(msg); + } + } + const auto paramEPSGCode = parameter->getEPSGCode(); + if (paramEPSGCode != 0 && paramEPSGCode != paramMapping->epsg_code) { + std::string msg("Parameter of EPSG code "); + msg += toString(paramEPSGCode); + msg += " does not match official code ("; + msg += toString(paramMapping->epsg_code); + msg += ')'; + res.emplace_back(msg); + } + } + + // Check if there are extra parameters + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = opParamvalue->parameter(); + if (!getMapping(methodMapping, parameter)) { + std::string msg("Parameter "); + msg += parameter->nameStr(); + msg += " found but not expected for this method"; + res.emplace_back(msg); + } + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ParameterValue::Private { + ParameterValue::Type type_{ParameterValue::Type::STRING}; + std::unique_ptr<common::Measure> measure_{}; + std::unique_ptr<std::string> stringValue_{}; + int integerValue_{}; + bool booleanValue_{}; + + explicit Private(const common::Measure &valueIn) + : type_(ParameterValue::Type::MEASURE), + measure_(internal::make_unique<common::Measure>(valueIn)) {} + + Private(const std::string &stringValueIn, ParameterValue::Type typeIn) + : type_(typeIn), + stringValue_(internal::make_unique<std::string>(stringValueIn)) {} + + explicit Private(int integerValueIn) + : type_(ParameterValue::Type::INTEGER), integerValue_(integerValueIn) {} + + explicit Private(bool booleanValueIn) + : type_(ParameterValue::Type::BOOLEAN), booleanValue_(booleanValueIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ParameterValue::~ParameterValue() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +ParameterValue::ParameterValue(const common::Measure &measureIn) + : d(internal::make_unique<Private>(measureIn)) {} + +// --------------------------------------------------------------------------- + +ParameterValue::ParameterValue(const std::string &stringValueIn, + ParameterValue::Type typeIn) + : d(internal::make_unique<Private>(stringValueIn, typeIn)) {} + +// --------------------------------------------------------------------------- + +ParameterValue::ParameterValue(int integerValueIn) + : d(internal::make_unique<Private>(integerValueIn)) {} + +// --------------------------------------------------------------------------- + +ParameterValue::ParameterValue(bool booleanValueIn) + : d(internal::make_unique<Private>(booleanValueIn)) {} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a ParameterValue from a Measure (i.e. a value associated + * with a + * unit) + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr ParameterValue::create(const common::Measure &measureIn) { + return ParameterValue::nn_make_shared<ParameterValue>(measureIn); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a ParameterValue from a string value. + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr ParameterValue::create(const char *stringValueIn) { + return ParameterValue::nn_make_shared<ParameterValue>( + std::string(stringValueIn), ParameterValue::Type::STRING); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a ParameterValue from a string value. + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr ParameterValue::create(const std::string &stringValueIn) { + return ParameterValue::nn_make_shared<ParameterValue>( + stringValueIn, ParameterValue::Type::STRING); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a ParameterValue from a filename. + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr +ParameterValue::createFilename(const std::string &stringValueIn) { + return ParameterValue::nn_make_shared<ParameterValue>( + stringValueIn, ParameterValue::Type::FILENAME); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a ParameterValue from a integer value. + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr ParameterValue::create(int integerValueIn) { + return ParameterValue::nn_make_shared<ParameterValue>(integerValueIn); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a ParameterValue from a boolean value. + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr ParameterValue::create(bool booleanValueIn) { + return ParameterValue::nn_make_shared<ParameterValue>(booleanValueIn); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the type of a parameter value. + * + * @return the type. + */ +const ParameterValue::Type &ParameterValue::type() PROJ_PURE_DEFN { + return d->type_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the value as a Measure (assumes type() == Type::MEASURE) + * @return the value as a Measure. + */ +const common::Measure &ParameterValue::value() PROJ_PURE_DEFN { + return *d->measure_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the value as a string (assumes type() == Type::STRING) + * @return the value as a string. + */ +const std::string &ParameterValue::stringValue() PROJ_PURE_DEFN { + return *d->stringValue_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the value as a filename (assumes type() == Type::FILENAME) + * @return the value as a filename. + */ +const std::string &ParameterValue::valueFile() PROJ_PURE_DEFN { + return *d->stringValue_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the value as a integer (assumes type() == Type::INTEGER) + * @return the value as a integer. + */ +int ParameterValue::integerValue() PROJ_PURE_DEFN { return d->integerValue_; } + +// --------------------------------------------------------------------------- + +/** \brief Returns the value as a boolean (assumes type() == Type::BOOLEAN) + * @return the value as a boolean. + */ +bool ParameterValue::booleanValue() PROJ_PURE_DEFN { return d->booleanValue_; } + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ParameterValue::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + + const auto &l_type = type(); + if (l_type == Type::MEASURE) { + const auto &l_value = value(); + if (formatter->abridgedTransformation()) { + const auto &unit = l_value.unit(); + const auto &unitType = unit.type(); + if (unitType == common::UnitOfMeasure::Type::LINEAR) { + formatter->add(l_value.getSIValue()); + } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) { + formatter->add( + l_value.convertToUnit(common::UnitOfMeasure::ARC_SECOND)); + } else if (unit == common::UnitOfMeasure::PARTS_PER_MILLION) { + formatter->add(1.0 + l_value.value() * 1e-6); + } else { + formatter->add(l_value.value()); + } + } else { + const auto &unit = l_value.unit(); + if (isWKT2) { + formatter->add(l_value.value()); + } else { + // In WKT1, as we don't output the natural unit, output to the + // registered linear / angular unit. + const auto &unitType = unit.type(); + if (unitType == common::UnitOfMeasure::Type::LINEAR) { + const auto &targetUnit = *(formatter->axisLinearUnit()); + if (targetUnit.conversionToSI() == 0.0) { + throw io::FormattingException( + "cannot convert value to target linear unit"); + } + formatter->add(l_value.convertToUnit(targetUnit)); + } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) { + const auto &targetUnit = *(formatter->axisAngularUnit()); + if (targetUnit.conversionToSI() == 0.0) { + throw io::FormattingException( + "cannot convert value to target angular unit"); + } + formatter->add(l_value.convertToUnit(targetUnit)); + } else { + formatter->add(l_value.getSIValue()); + } + } + if (isWKT2 && unit != common::UnitOfMeasure::NONE) { + if (!formatter + ->primeMeridianOrParameterUnitOmittedIfSameAsAxis() || + (unit != common::UnitOfMeasure::SCALE_UNITY && + unit != *(formatter->axisLinearUnit()) && + unit != *(formatter->axisAngularUnit()))) { + unit._exportToWKT(formatter); + } + } + } + } else if (l_type == Type::STRING || l_type == Type::FILENAME) { + formatter->addQuotedString(stringValue()); + } else if (l_type == Type::INTEGER) { + formatter->add(integerValue()); + } else { + throw io::FormattingException("boolean parameter value not handled"); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool ParameterValue::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &) const { + auto otherPV = dynamic_cast<const ParameterValue *>(other); + if (otherPV == nullptr) { + return false; + } + if (type() != otherPV->type()) { + return false; + } + switch (type()) { + case Type::MEASURE: { + return value()._isEquivalentTo(otherPV->value(), criterion, 2e-10); + } + + case Type::STRING: + case Type::FILENAME: { + return stringValue() == otherPV->stringValue(); + } + + case Type::INTEGER: { + return integerValue() == otherPV->integerValue(); + } + + case Type::BOOLEAN: { + return booleanValue() == otherPV->booleanValue(); + } + + default: { + assert(false); + break; + } + } + return true; +} +//! @endcond + +//! @cond Doxygen_Suppress +// --------------------------------------------------------------------------- + +InvalidOperation::InvalidOperation(const char *message) : Exception(message) {} + +// --------------------------------------------------------------------------- + +InvalidOperation::InvalidOperation(const std::string &message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +InvalidOperation::InvalidOperation(const InvalidOperation &) = default; + +// --------------------------------------------------------------------------- + +InvalidOperation::~InvalidOperation() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +void SingleOperation::exportTransformationToWKT( + io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + throw io::FormattingException( + "Transformation can only be exported to WKT2"); + } + + if (formatter->abridgedTransformation()) { + formatter->startNode(io::WKTConstants::ABRIDGEDTRANSFORMATION, + !identifiers().empty()); + } else { + formatter->startNode(io::WKTConstants::COORDINATEOPERATION, + !identifiers().empty()); + } + + formatter->addQuotedString(nameStr()); + + if (formatter->use2019Keywords()) { + const auto &version = operationVersion(); + if (version.has_value()) { + formatter->startNode(io::WKTConstants::VERSION, false); + formatter->addQuotedString(*version); + formatter->endNode(); + } + } + + if (!formatter->abridgedTransformation()) { + exportSourceCRSAndTargetCRSToWKT(this, formatter); + } + + method()->_exportToWKT(formatter); + + for (const auto ¶mValue : parameterValues()) { + paramValue->_exportToWKT(formatter, nullptr); + } + + if (!formatter->abridgedTransformation()) { + if (interpolationCRS()) { + formatter->startNode(io::WKTConstants::INTERPOLATIONCRS, false); + interpolationCRS()->_exportToWKT(formatter); + formatter->endNode(); + } + + if (!coordinateOperationAccuracies().empty()) { + formatter->startNode(io::WKTConstants::OPERATIONACCURACY, false); + formatter->add(coordinateOperationAccuracies()[0]->value()); + formatter->endNode(); + } + } + + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} + +// --------------------------------------------------------------------------- + +bool SingleOperation::exportToPROJStringGeneric( + io::PROJStringFormatter *formatter) const { + const int methodEPSGCode = method()->getEPSGCode(); + + if (methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION) { + const double A0 = parameterValueMeasure(EPSG_CODE_PARAMETER_A0).value(); + const double A1 = parameterValueMeasure(EPSG_CODE_PARAMETER_A1).value(); + const double A2 = parameterValueMeasure(EPSG_CODE_PARAMETER_A2).value(); + const double B0 = parameterValueMeasure(EPSG_CODE_PARAMETER_B0).value(); + const double B1 = parameterValueMeasure(EPSG_CODE_PARAMETER_B1).value(); + const double B2 = parameterValueMeasure(EPSG_CODE_PARAMETER_B2).value(); + + // Do not mess with axis unit and order for that transformation + + formatter->addStep("affine"); + formatter->addParam("xoff", A0); + formatter->addParam("s11", A1); + formatter->addParam("s12", A2); + formatter->addParam("yoff", B0); + formatter->addParam("s21", B1); + formatter->addParam("s22", B2); + + return true; + } + + if (isAxisOrderReversal(methodEPSGCode)) { + formatter->addStep("axisswap"); + formatter->addParam("order", "2,1"); + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); + if (sourceCRSGeog && targetCRSGeog) { + const auto &unitSrc = + sourceCRSGeog->coordinateSystem()->axisList()[0]->unit(); + const auto &unitDst = + targetCRSGeog->coordinateSystem()->axisList()[0]->unit(); + if (!unitSrc._isEquivalentTo( + unitDst, util::IComparable::Criterion::EQUIVALENT)) { + formatter->addStep("unitconvert"); + auto projUnit = unitSrc.exportToPROJString(); + if (projUnit.empty()) { + formatter->addParam("xy_in", unitSrc.conversionToSI()); + } else { + formatter->addParam("xy_in", projUnit); + } + projUnit = unitDst.exportToPROJString(); + if (projUnit.empty()) { + formatter->addParam("xy_out", unitDst.conversionToSI()); + } else { + formatter->addParam("xy_out", projUnit); + } + } + } + return true; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) { + + auto sourceCRSGeod = + dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get()); + auto targetCRSGeod = + dynamic_cast<const crs::GeodeticCRS *>(targetCRS().get()); + if (sourceCRSGeod && targetCRSGeod) { + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRSGeod); + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRSGeod); + bool isSrcGeocentric = sourceCRSGeod->isGeocentric(); + bool isSrcGeographic = sourceCRSGeog != nullptr; + bool isTargetGeocentric = targetCRSGeod->isGeocentric(); + bool isTargetGeographic = targetCRSGeog != nullptr; + if ((isSrcGeocentric && isTargetGeographic) || + (isSrcGeographic && isTargetGeocentric)) { + + formatter->startInversion(); + sourceCRSGeod->_exportToPROJString(formatter); + formatter->stopInversion(); + + targetCRSGeod->_exportToPROJString(formatter); + + return true; + } + } + + throw io::FormattingException("Invalid nature of source and/or " + "targetCRS for Geographic/Geocentric " + "conversion"); + } + + if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) { + double convFactor = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); + auto uom = common::UnitOfMeasure(std::string(), convFactor, + common::UnitOfMeasure::Type::LINEAR) + .exportToPROJString(); + auto reverse_uom = + common::UnitOfMeasure(std::string(), 1.0 / convFactor, + common::UnitOfMeasure::Type::LINEAR) + .exportToPROJString(); + if (uom == "m") { + // do nothing + } else if (!uom.empty()) { + formatter->addStep("unitconvert"); + formatter->addParam("z_in", uom); + formatter->addParam("z_out", "m"); + } else if (!reverse_uom.empty()) { + formatter->addStep("unitconvert"); + formatter->addParam("z_in", "m"); + formatter->addParam("z_out", reverse_uom); + } else { + formatter->addStep("affine"); + formatter->addParam("s33", convFactor); + } + return true; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { + formatter->addStep("axisswap"); + formatter->addParam("order", "1,2,-3"); + return true; + } + + return false; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +InverseCoordinateOperation::~InverseCoordinateOperation() = default; + +// --------------------------------------------------------------------------- + +InverseCoordinateOperation::InverseCoordinateOperation( + const CoordinateOperationNNPtr &forwardOperationIn, + bool wktSupportsInversion) + : forwardOperation_(forwardOperationIn), + wktSupportsInversion_(wktSupportsInversion) {} + +// --------------------------------------------------------------------------- + +void InverseCoordinateOperation::setPropertiesFromForward() { + setProperties( + createPropertiesForInverse(forwardOperation_.get(), false, false)); + setAccuracies(forwardOperation_->coordinateOperationAccuracies()); + if (forwardOperation_->sourceCRS() && forwardOperation_->targetCRS()) { + setCRSs(forwardOperation_.get(), true); + } + setHasBallparkTransformation( + forwardOperation_->hasBallparkTransformation()); +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr InverseCoordinateOperation::inverse() const { + return forwardOperation_; +} + +// --------------------------------------------------------------------------- + +void InverseCoordinateOperation::_exportToPROJString( + io::PROJStringFormatter *formatter) const { + formatter->startInversion(); + forwardOperation_->_exportToPROJString(formatter); + formatter->stopInversion(); +} + +// --------------------------------------------------------------------------- + +bool InverseCoordinateOperation::_isEquivalentTo( + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { + auto otherICO = dynamic_cast<const InverseCoordinateOperation *>(other); + if (otherICO == nullptr || + !ObjectUsage::_isEquivalentTo(other, criterion, dbContext)) { + return false; + } + return inverse()->_isEquivalentTo(otherICO->inverse().get(), criterion, + dbContext); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PointMotionOperation::~PointMotionOperation() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation + +NS_PROJ_END diff --git a/src/iso19111/operation/transformation.cpp b/src/iso19111/operation/transformation.cpp new file mode 100644 index 00000000..fec821a1 --- /dev/null +++ b/src/iso19111/operation/transformation.cpp @@ -0,0 +1,3274 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/common.hpp" +#include "proj/coordinateoperation.hpp" +#include "proj/crs.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" + +#include "coordinateoperation_internal.hpp" +#include "coordinateoperation_private.hpp" +#include "esriparammappings.hpp" +#include "operationmethod_private.hpp" +#include "oputils.hpp" +#include "parammappings.hpp" +#include "vectorofvaluesparams.hpp" + +// PROJ include order is sensitive +// clang-format off +#include "proj.h" +#include "proj_internal.h" // M_PI +// clang-format on +#include "proj_constants.h" + +#include "proj_json_streaming_writer.hpp" + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <cstring> +#include <memory> +#include <set> +#include <string> +#include <vector> + +using namespace NS_PROJ::internal; + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Transformation::Private { + + TransformationPtr forwardOperation_{}; + + static TransformationNNPtr registerInv(const Transformation *thisIn, + TransformationNNPtr invTransform); +}; +//! @endcond + +// --------------------------------------------------------------------------- + +Transformation::Transformation( + const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, + const crs::CRSPtr &interpolationCRSIn, const OperationMethodNNPtr &methodIn, + const std::vector<GeneralParameterValueNNPtr> &values, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) + : SingleOperation(methodIn), d(internal::make_unique<Private>()) { + setParameterValues(values); + setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn); + setAccuracies(accuracies); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Transformation::~Transformation() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +Transformation::Transformation(const Transformation &other) + : CoordinateOperation(other), SingleOperation(other), + d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +/** \brief Return the source crs::CRS of the transformation. + * + * @return the source CRS. + */ +const crs::CRSNNPtr &Transformation::sourceCRS() PROJ_PURE_DEFN { + return CoordinateOperation::getPrivate()->strongRef_->sourceCRS_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the target crs::CRS of the transformation. + * + * @return the target CRS. + */ +const crs::CRSNNPtr &Transformation::targetCRS() PROJ_PURE_DEFN { + return CoordinateOperation::getPrivate()->strongRef_->targetCRS_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TransformationNNPtr Transformation::shallowClone() const { + auto transf = Transformation::nn_make_shared<Transformation>(*this); + transf->assignSelf(transf); + transf->setCRSs(this, false); + if (transf->d->forwardOperation_) { + transf->d->forwardOperation_ = + transf->d->forwardOperation_->shallowClone().as_nullable(); + } + return transf; +} + +CoordinateOperationNNPtr Transformation::_shallowClone() const { + return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone()); +} + +// --------------------------------------------------------------------------- + +TransformationNNPtr +Transformation::promoteTo3D(const std::string &, + const io::DatabaseContextPtr &dbContext) const { + auto transf = shallowClone(); + transf->setCRSs(sourceCRS()->promoteTo3D(std::string(), dbContext), + targetCRS()->promoteTo3D(std::string(), dbContext), + interpolationCRS()); + return transf; +} + +// --------------------------------------------------------------------------- + +TransformationNNPtr +Transformation::demoteTo2D(const std::string &, + const io::DatabaseContextPtr &dbContext) const { + auto transf = shallowClone(); + transf->setCRSs(sourceCRS()->demoteTo2D(std::string(), dbContext), + targetCRS()->demoteTo2D(std::string(), dbContext), + interpolationCRS()); + return transf; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +/** \brief Return the TOWGS84 parameters of the transformation. + * + * If this transformation uses Coordinate Frame Rotation, Position Vector + * transformation or Geocentric translations, a vector of 7 double values + * using the Position Vector convention (EPSG:9606) is returned. Those values + * can be used as the value of the WKT1 TOWGS84 parameter or + * PROJ +towgs84 parameter. + * + * @return a vector of 7 values if valid, otherwise a io::FormattingException + * is thrown. + * @throws io::FormattingException + */ +std::vector<double> +Transformation::getTOWGS84Parameters() const // throw(io::FormattingException) +{ + // GDAL WKT1 assumes EPSG:9606 / Position Vector convention + + bool sevenParamsTransform = false; + bool threeParamsTransform = false; + bool invertRotSigns = false; + const auto &l_method = method(); + const auto &methodName = l_method->nameStr(); + const int methodEPSGCode = l_method->getEPSGCode(); + const auto paramCount = parameterValues().size(); + if ((paramCount == 7 && + ci_find(methodName, "Coordinate Frame") != std::string::npos) || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) { + sevenParamsTransform = true; + invertRotSigns = true; + } else if ((paramCount == 7 && + ci_find(methodName, "Position Vector") != std::string::npos) || + methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) { + sevenParamsTransform = true; + invertRotSigns = false; + } else if ((paramCount == 3 && + ci_find(methodName, "Geocentric translations") != + std::string::npos) || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) { + threeParamsTransform = true; + } + + if (threeParamsTransform || sevenParamsTransform) { + std::vector<double> params(7, 0.0); + bool foundX = false; + bool foundY = false; + bool foundZ = false; + bool foundRotX = false; + bool foundRotY = false; + bool foundRotZ = false; + bool foundScale = false; + const double rotSign = invertRotSigns ? -1.0 : 1.0; + + const auto fixNegativeZero = [](double x) { + if (x == 0.0) + return 0.0; + return x; + }; + + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = opParamvalue->parameter(); + const auto epsg_code = parameter->getEPSGCode(); + const auto &l_parameterValue = opParamvalue->parameterValue(); + if (l_parameterValue->type() == ParameterValue::Type::MEASURE) { + const auto &measure = l_parameterValue->value(); + if (epsg_code == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) { + params[0] = measure.getSIValue(); + foundX = true; + } else if (epsg_code == + EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) { + params[1] = measure.getSIValue(); + foundY = true; + } else if (epsg_code == + EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) { + params[2] = measure.getSIValue(); + foundZ = true; + } else if (epsg_code == + EPSG_CODE_PARAMETER_X_AXIS_ROTATION) { + params[3] = fixNegativeZero( + rotSign * + measure.convertToUnit( + common::UnitOfMeasure::ARC_SECOND)); + foundRotX = true; + } else if (epsg_code == + EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) { + params[4] = fixNegativeZero( + rotSign * + measure.convertToUnit( + common::UnitOfMeasure::ARC_SECOND)); + foundRotY = true; + } else if (epsg_code == + EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) { + params[5] = fixNegativeZero( + rotSign * + measure.convertToUnit( + common::UnitOfMeasure::ARC_SECOND)); + foundRotZ = true; + } else if (epsg_code == + EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) { + params[6] = measure.convertToUnit( + common::UnitOfMeasure::PARTS_PER_MILLION); + foundScale = true; + } + } + } + } + if (foundX && foundY && foundZ && + (threeParamsTransform || + (foundRotX && foundRotY && foundRotZ && foundScale))) { + return params; + } else { + throw io::FormattingException( + "Missing required parameter values in transformation"); + } + } + +#if 0 + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS || + methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) { + auto offsetLat = + parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); + auto offsetLong = + parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); + + auto offsetHeight = + parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); + + if (offsetLat.getSIValue() == 0.0 && offsetLong.getSIValue() == 0.0 && + offsetHeight.getSIValue() == 0.0) { + std::vector<double> params(7, 0.0); + return params; + } + } +#endif + + throw io::FormattingException( + "Transformation cannot be formatted as WKT1 TOWGS84 parameters"); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation from a vector of GeneralParameterValue. + * + * @param properties See \ref general_properties. At minimum the name should be + * defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param interpolationCRSIn Interpolation CRS (might be null) + * @param methodIn Operation method. + * @param values Vector of GeneralOperationParameterNNPtr. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + * @throws InvalidOperation + */ +TransformationNNPtr Transformation::create( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn, + const OperationMethodNNPtr &methodIn, + const std::vector<GeneralParameterValueNNPtr> &values, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + if (methodIn->parameters().size() != values.size()) { + throw InvalidOperation( + "Inconsistent number of parameters and parameter values"); + } + auto transf = Transformation::nn_make_shared<Transformation>( + sourceCRSIn, targetCRSIn, interpolationCRSIn, methodIn, values, + accuracies); + transf->assignSelf(transf); + transf->setProperties(properties); + std::string name; + if (properties.getStringValue(common::IdentifiedObject::NAME_KEY, name) && + ci_find(name, "ballpark") != std::string::npos) { + transf->setHasBallparkTransformation(true); + } + return transf; +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation ands its OperationMethod. + * + * @param propertiesTransformation The \ref general_properties of the + * Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param interpolationCRSIn Interpolation CRS (might be null) + * @param propertiesOperationMethod The \ref general_properties of the + * OperationMethod. + * At minimum the name should be defined. + * @param parameters Vector of parameters of the operation method. + * @param values Vector of ParameterValueNNPtr. Constraint: + * values.size() == parameters.size() + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + * @throws InvalidOperation + */ +TransformationNNPtr +Transformation::create(const util::PropertyMap &propertiesTransformation, + const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, + const crs::CRSPtr &interpolationCRSIn, + const util::PropertyMap &propertiesOperationMethod, + const std::vector<OperationParameterNNPtr> ¶meters, + const std::vector<ParameterValueNNPtr> &values, + const std::vector<metadata::PositionalAccuracyNNPtr> + &accuracies) // throw InvalidOperation +{ + OperationMethodNNPtr op( + OperationMethod::create(propertiesOperationMethod, parameters)); + + if (parameters.size() != values.size()) { + throw InvalidOperation( + "Inconsistent number of parameters and parameter values"); + } + std::vector<GeneralParameterValueNNPtr> generalParameterValues; + generalParameterValues.reserve(values.size()); + for (size_t i = 0; i < values.size(); i++) { + generalParameterValues.push_back( + OperationParameterValue::create(parameters[i], values[i])); + } + return create(propertiesTransformation, sourceCRSIn, targetCRSIn, + interpolationCRSIn, op, generalParameterValues, accuracies); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +static TransformationNNPtr createSevenParamsTransform( + const util::PropertyMap &properties, + const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double rotationXArcSecond, double rotationYArcSecond, + double rotationZArcSecond, double scaleDifferencePPM, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return Transformation::create( + properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties, + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE), + }, + createParams(common::Length(translationXMetre), + common::Length(translationYMetre), + common::Length(translationZMetre), + common::Angle(rotationXArcSecond, + common::UnitOfMeasure::ARC_SECOND), + common::Angle(rotationYArcSecond, + common::UnitOfMeasure::ARC_SECOND), + common::Angle(rotationZArcSecond, + common::UnitOfMeasure::ARC_SECOND), + common::Scale(scaleDifferencePPM, + common::UnitOfMeasure::PARTS_PER_MILLION)), + accuracies); +} + +// --------------------------------------------------------------------------- + +static void getTransformationType(const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, + bool &isGeocentric, bool &isGeog2D, + bool &isGeog3D) { + auto sourceCRSGeod = + dynamic_cast<const crs::GeodeticCRS *>(sourceCRSIn.get()); + auto targetCRSGeod = + dynamic_cast<const crs::GeodeticCRS *>(targetCRSIn.get()); + isGeocentric = sourceCRSGeod && sourceCRSGeod->isGeocentric() && + targetCRSGeod && targetCRSGeod->isGeocentric(); + if (isGeocentric) { + isGeog2D = false; + isGeog3D = false; + return; + } + isGeocentric = false; + + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRSIn.get()); + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRSIn.get()); + if (!sourceCRSGeog || !targetCRSGeog) { + throw InvalidOperation("Inconsistent CRS type"); + } + const auto nSrcAxisCount = + sourceCRSGeog->coordinateSystem()->axisList().size(); + const auto nTargetAxisCount = + targetCRSGeog->coordinateSystem()->axisList().size(); + isGeog2D = nSrcAxisCount == 2 && nTargetAxisCount == 2; + isGeog3D = !isGeog2D && nSrcAxisCount >= 2 && nTargetAxisCount >= 2; +} + +// --------------------------------------------------------------------------- + +static int +useOperationMethodEPSGCodeIfPresent(const util::PropertyMap &properties, + int nDefaultOperationMethodEPSGCode) { + const auto *operationMethodEPSGCode = + properties.get("OPERATION_METHOD_EPSG_CODE"); + if (operationMethodEPSGCode) { + const auto boxedValue = dynamic_cast<const util::BoxedValue *>( + (*operationMethodEPSGCode).get()); + if (boxedValue && + boxedValue->type() == util::BoxedValue::Type::INTEGER) { + return boxedValue->integerValue(); + } + } + return nDefaultOperationMethodEPSGCode; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation with Geocentric Translations method. + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param translationXMetre Value of the Translation_X parameter (in metre). + * @param translationYMetre Value of the Translation_Y parameter (in metre). + * @param translationZMetre Value of the Translation_Z parameter (in metre). + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createGeocentricTranslations( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + bool isGeocentric; + bool isGeog2D; + bool isGeog3D; + getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, + isGeog3D); + return create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( + properties, + isGeocentric + ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC + : isGeog2D + ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D + : EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D)), + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), + }, + createParams(common::Length(translationXMetre), + common::Length(translationYMetre), + common::Length(translationZMetre)), + accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation with Position vector transformation + * method. + * + * This is similar to createCoordinateFrameRotation(), except that the sign of + * the rotation terms is inverted. + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param translationXMetre Value of the Translation_X parameter (in metre). + * @param translationYMetre Value of the Translation_Y parameter (in metre). + * @param translationZMetre Value of the Translation_Z parameter (in metre). + * @param rotationXArcSecond Value of the Rotation_X parameter (in + * arc-second). + * @param rotationYArcSecond Value of the Rotation_Y parameter (in + * arc-second). + * @param rotationZArcSecond Value of the Rotation_Z parameter (in + * arc-second). + * @param scaleDifferencePPM Value of the Scale_Difference parameter (in + * parts-per-million). + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createPositionVector( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double rotationXArcSecond, double rotationYArcSecond, + double rotationZArcSecond, double scaleDifferencePPM, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + + bool isGeocentric; + bool isGeog2D; + bool isGeog3D; + getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, + isGeog3D); + return createSevenParamsTransform( + properties, + createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( + properties, + isGeocentric + ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC + : isGeog2D ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D + : EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D)), + sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, + translationZMetre, rotationXArcSecond, rotationYArcSecond, + rotationZArcSecond, scaleDifferencePPM, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation with Coordinate Frame Rotation method. + * + * This is similar to createPositionVector(), except that the sign of + * the rotation terms is inverted. + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param translationXMetre Value of the Translation_X parameter (in metre). + * @param translationYMetre Value of the Translation_Y parameter (in metre). + * @param translationZMetre Value of the Translation_Z parameter (in metre). + * @param rotationXArcSecond Value of the Rotation_X parameter (in + * arc-second). + * @param rotationYArcSecond Value of the Rotation_Y parameter (in + * arc-second). + * @param rotationZArcSecond Value of the Rotation_Z parameter (in + * arc-second). + * @param scaleDifferencePPM Value of the Scale_Difference parameter (in + * parts-per-million). + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createCoordinateFrameRotation( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double rotationXArcSecond, double rotationYArcSecond, + double rotationZArcSecond, double scaleDifferencePPM, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + bool isGeocentric; + bool isGeog2D; + bool isGeog3D; + getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, + isGeog3D); + return createSevenParamsTransform( + properties, + createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( + properties, + isGeocentric + ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC + : isGeog2D ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D + : EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D)), + sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, + translationZMetre, rotationXArcSecond, rotationYArcSecond, + rotationZArcSecond, scaleDifferencePPM, accuracies); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static TransformationNNPtr createFifteenParamsTransform( + const util::PropertyMap &properties, + const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double rotationXArcSecond, double rotationYArcSecond, + double rotationZArcSecond, double scaleDifferencePPM, + double rateTranslationX, double rateTranslationY, double rateTranslationZ, + double rateRotationX, double rateRotationY, double rateRotationZ, + double rateScaleDifference, double referenceEpochYear, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return Transformation::create( + properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties, + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE), + + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION), + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION), + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION), + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE), + + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_REFERENCE_EPOCH), + }, + VectorOfValues{ + common::Length(translationXMetre), + common::Length(translationYMetre), + common::Length(translationZMetre), + common::Angle(rotationXArcSecond, + common::UnitOfMeasure::ARC_SECOND), + common::Angle(rotationYArcSecond, + common::UnitOfMeasure::ARC_SECOND), + common::Angle(rotationZArcSecond, + common::UnitOfMeasure::ARC_SECOND), + common::Scale(scaleDifferencePPM, + common::UnitOfMeasure::PARTS_PER_MILLION), + common::Measure(rateTranslationX, + common::UnitOfMeasure::METRE_PER_YEAR), + common::Measure(rateTranslationY, + common::UnitOfMeasure::METRE_PER_YEAR), + common::Measure(rateTranslationZ, + common::UnitOfMeasure::METRE_PER_YEAR), + common::Measure(rateRotationX, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR), + common::Measure(rateRotationY, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR), + common::Measure(rateRotationZ, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR), + common::Measure(rateScaleDifference, + common::UnitOfMeasure::PPM_PER_YEAR), + common::Measure(referenceEpochYear, common::UnitOfMeasure::YEAR), + }, + accuracies); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation with Time Dependent position vector + * transformation method. + * + * This is similar to createTimeDependentCoordinateFrameRotation(), except that + * the sign of + * the rotation terms is inverted. + * + * This method is defined as [EPSG:1053] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1053) + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param translationXMetre Value of the Translation_X parameter (in metre). + * @param translationYMetre Value of the Translation_Y parameter (in metre). + * @param translationZMetre Value of the Translation_Z parameter (in metre). + * @param rotationXArcSecond Value of the Rotation_X parameter (in + * arc-second). + * @param rotationYArcSecond Value of the Rotation_Y parameter (in + * arc-second). + * @param rotationZArcSecond Value of the Rotation_Z parameter (in + * arc-second). + * @param scaleDifferencePPM Value of the Scale_Difference parameter (in + * parts-per-million). + * @param rateTranslationX Value of the rate of change of X-axis translation (in + * metre/year) + * @param rateTranslationY Value of the rate of change of Y-axis translation (in + * metre/year) + * @param rateTranslationZ Value of the rate of change of Z-axis translation (in + * metre/year) + * @param rateRotationX Value of the rate of change of X-axis rotation (in + * arc-second/year) + * @param rateRotationY Value of the rate of change of Y-axis rotation (in + * arc-second/year) + * @param rateRotationZ Value of the rate of change of Z-axis rotation (in + * arc-second/year) + * @param rateScaleDifference Value of the rate of change of scale difference + * (in PPM/year) + * @param referenceEpochYear Parameter reference epoch (in decimal year) + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createTimeDependentPositionVector( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double rotationXArcSecond, double rotationYArcSecond, + double rotationZArcSecond, double scaleDifferencePPM, + double rateTranslationX, double rateTranslationY, double rateTranslationZ, + double rateRotationX, double rateRotationY, double rateRotationZ, + double rateScaleDifference, double referenceEpochYear, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + bool isGeocentric; + bool isGeog2D; + bool isGeog3D; + getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, + isGeog3D); + return createFifteenParamsTransform( + properties, + createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( + properties, + isGeocentric + ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC + : isGeog2D + ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D + : EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D)), + sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, + translationZMetre, rotationXArcSecond, rotationYArcSecond, + rotationZArcSecond, scaleDifferencePPM, rateTranslationX, + rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY, + rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation with Time Dependent Position coordinate + * frame rotation transformation method. + * + * This is similar to createTimeDependentPositionVector(), except that the sign + * of + * the rotation terms is inverted. + * + * This method is defined as [EPSG:1056] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1056) + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param translationXMetre Value of the Translation_X parameter (in metre). + * @param translationYMetre Value of the Translation_Y parameter (in metre). + * @param translationZMetre Value of the Translation_Z parameter (in metre). + * @param rotationXArcSecond Value of the Rotation_X parameter (in + * arc-second). + * @param rotationYArcSecond Value of the Rotation_Y parameter (in + * arc-second). + * @param rotationZArcSecond Value of the Rotation_Z parameter (in + * arc-second). + * @param scaleDifferencePPM Value of the Scale_Difference parameter (in + * parts-per-million). + * @param rateTranslationX Value of the rate of change of X-axis translation (in + * metre/year) + * @param rateTranslationY Value of the rate of change of Y-axis translation (in + * metre/year) + * @param rateTranslationZ Value of the rate of change of Z-axis translation (in + * metre/year) + * @param rateRotationX Value of the rate of change of X-axis rotation (in + * arc-second/year) + * @param rateRotationY Value of the rate of change of Y-axis rotation (in + * arc-second/year) + * @param rateRotationZ Value of the rate of change of Z-axis rotation (in + * arc-second/year) + * @param rateScaleDifference Value of the rate of change of scale difference + * (in PPM/year) + * @param referenceEpochYear Parameter reference epoch (in decimal year) + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + * @throws InvalidOperation + */ +TransformationNNPtr Transformation::createTimeDependentCoordinateFrameRotation( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double rotationXArcSecond, double rotationYArcSecond, + double rotationZArcSecond, double scaleDifferencePPM, + double rateTranslationX, double rateTranslationY, double rateTranslationZ, + double rateRotationX, double rateRotationY, double rateRotationZ, + double rateScaleDifference, double referenceEpochYear, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + + bool isGeocentric; + bool isGeog2D; + bool isGeog3D; + getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, + isGeog3D); + return createFifteenParamsTransform( + properties, + createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( + properties, + isGeocentric + ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC + : isGeog2D + ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D + : EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D)), + sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, + translationZMetre, rotationXArcSecond, rotationYArcSecond, + rotationZArcSecond, scaleDifferencePPM, rateTranslationX, + rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY, + rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static TransformationNNPtr _createMolodensky( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, int methodEPSGCode, + double translationXMetre, double translationYMetre, + double translationZMetre, double semiMajorAxisDifferenceMetre, + double flattingDifference, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return Transformation::create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(methodEPSGCode), + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE), + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE), + }, + createParams( + common::Length(translationXMetre), + common::Length(translationYMetre), + common::Length(translationZMetre), + common::Length(semiMajorAxisDifferenceMetre), + common::Measure(flattingDifference, common::UnitOfMeasure::NONE)), + accuracies); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation with Molodensky method. + * + * @see createAbridgedMolodensky() for a related method. + * + * This method is defined as [EPSG:9604] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9604) + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param translationXMetre Value of the Translation_X parameter (in metre). + * @param translationYMetre Value of the Translation_Y parameter (in metre). + * @param translationZMetre Value of the Translation_Z parameter (in metre). + * @param semiMajorAxisDifferenceMetre The difference between the semi-major + * axis values of the ellipsoids used in the target and source CRS (in metre). + * @param flattingDifference The difference between the flattening values of + * the ellipsoids used in the target and source CRS. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + * @throws InvalidOperation + */ +TransformationNNPtr Transformation::createMolodensky( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double semiMajorAxisDifferenceMetre, double flattingDifference, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return _createMolodensky( + properties, sourceCRSIn, targetCRSIn, EPSG_CODE_METHOD_MOLODENSKY, + translationXMetre, translationYMetre, translationZMetre, + semiMajorAxisDifferenceMetre, flattingDifference, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation with Abridged Molodensky method. + * + * @see createdMolodensky() for a related method. + * + * This method is defined as [EPSG:9605] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9605) + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param translationXMetre Value of the Translation_X parameter (in metre). + * @param translationYMetre Value of the Translation_Y parameter (in metre). + * @param translationZMetre Value of the Translation_Z parameter (in metre). + * @param semiMajorAxisDifferenceMetre The difference between the semi-major + * axis values of the ellipsoids used in the target and source CRS (in metre). + * @param flattingDifference The difference between the flattening values of + * the ellipsoids used in the target and source CRS. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + * @throws InvalidOperation + */ +TransformationNNPtr Transformation::createAbridgedMolodensky( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double semiMajorAxisDifferenceMetre, double flattingDifference, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return _createMolodensky(properties, sourceCRSIn, targetCRSIn, + EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY, + translationXMetre, translationYMetre, + translationZMetre, semiMajorAxisDifferenceMetre, + flattingDifference, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation from TOWGS84 parameters. + * + * This is a helper of createPositionVector() with the source CRS being the + * GeographicCRS of sourceCRSIn, and the target CRS being EPSG:4326 + * + * @param sourceCRSIn Source CRS. + * @param TOWGS84Parameters The vector of 3 double values (Translation_X,_Y,_Z) + * or 7 double values (Translation_X,_Y,_Z, Rotation_X,_Y,_Z, Scale_Difference) + * passed to createPositionVector() + * @return new Transformation. + * @throws InvalidOperation + */ +TransformationNNPtr Transformation::createTOWGS84( + const crs::CRSNNPtr &sourceCRSIn, + const std::vector<double> &TOWGS84Parameters) // throw InvalidOperation +{ + if (TOWGS84Parameters.size() != 3 && TOWGS84Parameters.size() != 7) { + throw InvalidOperation( + "Invalid number of elements in TOWGS84Parameters"); + } + + crs::CRSPtr transformSourceCRS = sourceCRSIn->extractGeodeticCRS(); + if (!transformSourceCRS) { + throw InvalidOperation( + "Cannot find GeodeticCRS in sourceCRS of TOWGS84 transformation"); + } + + util::PropertyMap properties; + properties.set(common::IdentifiedObject::NAME_KEY, + concat("Transformation from ", transformSourceCRS->nameStr(), + " to WGS84")); + + auto targetCRS = + dynamic_cast<const crs::GeographicCRS *>(transformSourceCRS.get()) + ? util::nn_static_pointer_cast<crs::CRS>( + crs::GeographicCRS::EPSG_4326) + : util::nn_static_pointer_cast<crs::CRS>( + crs::GeodeticCRS::EPSG_4978); + + if (TOWGS84Parameters.size() == 3) { + return createGeocentricTranslations( + properties, NN_NO_CHECK(transformSourceCRS), targetCRS, + TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2], + {}); + } + + return createPositionVector(properties, NN_NO_CHECK(transformSourceCRS), + targetCRS, TOWGS84Parameters[0], + TOWGS84Parameters[1], TOWGS84Parameters[2], + TOWGS84Parameters[3], TOWGS84Parameters[4], + TOWGS84Parameters[5], TOWGS84Parameters[6], {}); +} + +// --------------------------------------------------------------------------- +/** \brief Instantiate a transformation with NTv2 method. + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param filename NTv2 filename. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createNTv2( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const std::string &filename, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + + return create(properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV2), + VectorOfParameters{createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}, + VectorOfValues{ParameterValue::createFilename(filename)}, + accuracies); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static TransformationNNPtr _createGravityRelatedHeightToGeographic3D( + const util::PropertyMap &properties, bool inverse, + const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, + const crs::CRSPtr &interpolationCRSIn, const std::string &filename, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + + return Transformation::create( + properties, sourceCRSIn, targetCRSIn, interpolationCRSIn, + util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + inverse ? INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D + : PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D), + VectorOfParameters{createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)}, + VectorOfValues{ParameterValue::createFilename(filename)}, accuracies); +} +//! @endcond + +// --------------------------------------------------------------------------- +/** \brief Instantiate a transformation from GravityRelatedHeight to + * Geographic3D + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param interpolationCRSIn Interpolation CRS. (might be null) + * @param filename GRID filename. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createGravityRelatedHeightToGeographic3D( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn, + const std::string &filename, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + + return _createGravityRelatedHeightToGeographic3D( + properties, false, sourceCRSIn, targetCRSIn, interpolationCRSIn, + filename, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation with method VERTCON + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param filename GRID filename. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createVERTCON( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const std::string &filename, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + + return create(properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTCON), + VectorOfParameters{createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)}, + VectorOfValues{ParameterValue::createFilename(filename)}, + accuracies); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static inline std::vector<metadata::PositionalAccuracyNNPtr> +buildAccuracyZero() { + return std::vector<metadata::PositionalAccuracyNNPtr>{ + metadata::PositionalAccuracy::create("0")}; +} + +// --------------------------------------------------------------------------- + +//! @endcond + +/** \brief Instantiate a transformation with method Longitude rotation + * + * This method is defined as [EPSG:9601] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9601) + * * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param offset Longitude offset to add. + * @return new Transformation. + */ +TransformationNNPtr Transformation::createLongitudeRotation( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const common::Angle &offset) { + + return create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_LONGITUDE_ROTATION), + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)}, + VectorOfValues{ParameterValue::create(offset)}, buildAccuracyZero()); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool Transformation::isLongitudeRotation() const { + return method()->getEPSGCode() == EPSG_CODE_METHOD_LONGITUDE_ROTATION; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation with method Geographic 2D offsets + * + * This method is defined as [EPSG:9619] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9619) + * * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param offsetLat Latitude offset to add. + * @param offsetLon Longitude offset to add. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createGeographic2DOffsets( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, + const common::Angle &offsetLon, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS), + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)}, + VectorOfValues{offsetLat, offsetLon}, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation with method Geographic 3D offsets + * + * This method is defined as [EPSG:9660] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9660) + * * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param offsetLat Latitude offset to add. + * @param offsetLon Longitude offset to add. + * @param offsetHeight Height offset to add. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createGeographic3DOffsets( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, + const common::Angle &offsetLon, const common::Length &offsetHeight, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS), + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_VERTICAL_OFFSET)}, + VectorOfValues{offsetLat, offsetLon, offsetHeight}, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation with method Geographic 2D with + * height + * offsets + * + * This method is defined as [EPSG:9618] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9618) + * * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param offsetLat Latitude offset to add. + * @param offsetLon Longitude offset to add. + * @param offsetHeight Geoid undulation to add. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createGeographic2DWithHeightOffsets( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, + const common::Angle &offsetLon, const common::Length &offsetHeight, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode( + EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS), + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_GEOID_UNDULATION)}, + VectorOfValues{offsetLat, offsetLon, offsetHeight}, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation with method Vertical Offset. + * + * This method is defined as [EPSG:9616] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9616) + * * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param offsetHeight Geoid undulation to add. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createVerticalOffset( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const common::Length &offsetHeight, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return create(properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTICAL_OFFSET), + VectorOfParameters{createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_VERTICAL_OFFSET)}, + VectorOfValues{offsetHeight}, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a transformation based on the Change of Vertical Unit + * method. + * + * This method is defined as [EPSG:1069] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1069) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param factor Conversion factor + * @param accuracies Vector of positional accuracy (might be empty). + * @return a new Transformation. + */ +TransformationNNPtr Transformation::createChangeVerticalUnit( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const common::Scale &factor, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT), + VectorOfParameters{ + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR), + }, + VectorOfValues{ + factor, + }, + accuracies); +} + +// --------------------------------------------------------------------------- + +// to avoid -0... +static double negate(double val) { + if (val != 0) { + return -val; + } + return 0.0; +} + +// --------------------------------------------------------------------------- + +static CoordinateOperationPtr +createApproximateInverseIfPossible(const Transformation *op) { + bool sevenParamsTransform = false; + bool fifteenParamsTransform = false; + const auto &method = op->method(); + const auto &methodName = method->nameStr(); + const int methodEPSGCode = method->getEPSGCode(); + const auto paramCount = op->parameterValues().size(); + const bool isPositionVector = + ci_find(methodName, "Position Vector") != std::string::npos; + const bool isCoordinateFrame = + ci_find(methodName, "Coordinate Frame") != std::string::npos; + + // See end of "2.4.3.3 Helmert 7-parameter transformations" + // in EPSG 7-2 guidance + // For practical purposes, the inverse of 7- or 15-parameters Helmert + // can be obtained by using the forward method with all parameters + // negated + // (except reference epoch!) + // So for WKT export use that. But for PROJ string, we use the +inv flag + // so as to get "perfect" round-tripability. + if ((paramCount == 7 && isCoordinateFrame && + !isTimeDependent(methodName)) || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) { + sevenParamsTransform = true; + } else if ( + (paramCount == 15 && isCoordinateFrame && + isTimeDependent(methodName)) || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) { + fifteenParamsTransform = true; + } else if ((paramCount == 7 && isPositionVector && + !isTimeDependent(methodName)) || + methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) { + sevenParamsTransform = true; + } else if ( + (paramCount == 15 && isPositionVector && isTimeDependent(methodName)) || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) { + fifteenParamsTransform = true; + } + if (sevenParamsTransform || fifteenParamsTransform) { + double neg_x = negate(op->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION)); + double neg_y = negate(op->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION)); + double neg_z = negate(op->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION)); + double neg_rx = negate( + op->parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND)); + double neg_ry = negate( + op->parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND)); + double neg_rz = negate( + op->parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND)); + double neg_scaleDiff = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, + common::UnitOfMeasure::PARTS_PER_MILLION)); + auto methodProperties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, methodName); + int method_epsg_code = method->getEPSGCode(); + if (method_epsg_code) { + methodProperties + .set(metadata::Identifier::CODESPACE_KEY, + metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, method_epsg_code); + } + if (fifteenParamsTransform) { + double neg_rate_x = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION, + common::UnitOfMeasure::METRE_PER_YEAR)); + double neg_rate_y = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION, + common::UnitOfMeasure::METRE_PER_YEAR)); + double neg_rate_z = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION, + common::UnitOfMeasure::METRE_PER_YEAR)); + double neg_rate_rx = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR)); + double neg_rate_ry = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR)); + double neg_rate_rz = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR)); + double neg_rate_scaleDiff = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE, + common::UnitOfMeasure::PPM_PER_YEAR)); + double referenceEpochYear = + op->parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH, + common::UnitOfMeasure::YEAR); + return util::nn_static_pointer_cast<CoordinateOperation>( + createFifteenParamsTransform( + createPropertiesForInverse(op, false, true), + methodProperties, op->targetCRS(), op->sourceCRS(), + neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz, + neg_scaleDiff, neg_rate_x, neg_rate_y, neg_rate_z, + neg_rate_rx, neg_rate_ry, neg_rate_rz, + neg_rate_scaleDiff, referenceEpochYear, + op->coordinateOperationAccuracies())) + .as_nullable(); + } else { + return util::nn_static_pointer_cast<CoordinateOperation>( + createSevenParamsTransform( + createPropertiesForInverse(op, false, true), + methodProperties, op->targetCRS(), op->sourceCRS(), + neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz, + neg_scaleDiff, op->coordinateOperationAccuracies())) + .as_nullable(); + } + } + + return nullptr; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TransformationNNPtr +Transformation::Private::registerInv(const Transformation *thisIn, + TransformationNNPtr invTransform) { + invTransform->d->forwardOperation_ = thisIn->shallowClone().as_nullable(); + invTransform->setHasBallparkTransformation( + thisIn->hasBallparkTransformation()); + return invTransform; +} +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr Transformation::inverse() const { + return inverseAsTransformation(); +} + +// --------------------------------------------------------------------------- + +TransformationNNPtr Transformation::inverseAsTransformation() const { + + if (d->forwardOperation_) { + return NN_NO_CHECK(d->forwardOperation_); + } + const auto &l_method = method(); + const auto &methodName = l_method->nameStr(); + const int methodEPSGCode = l_method->getEPSGCode(); + const auto &l_sourceCRS = sourceCRS(); + const auto &l_targetCRS = targetCRS(); + + // For geocentric translation, the inverse is exactly the negation of + // the parameters. + if (ci_find(methodName, "Geocentric translations") != std::string::npos || + methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) { + double x = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); + double y = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); + double z = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); + auto properties = createPropertiesForInverse(this, false, false); + return Private::registerInv( + this, create(properties, l_targetCRS, l_sourceCRS, nullptr, + createMethodMapNameEPSGCode( + useOperationMethodEPSGCodeIfPresent( + properties, methodEPSGCode)), + VectorOfParameters{ + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), + }, + createParams(common::Length(negate(x)), + common::Length(negate(y)), + common::Length(negate(z))), + coordinateOperationAccuracies())); + } + + if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY || + methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { + double x = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); + double y = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); + double z = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); + double da = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE); + double df = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE); + + if (methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { + return Private::registerInv( + this, + createAbridgedMolodensky( + createPropertiesForInverse(this, false, false), l_targetCRS, + l_sourceCRS, negate(x), negate(y), negate(z), negate(da), + negate(df), coordinateOperationAccuracies())); + } else { + return Private::registerInv( + this, + createMolodensky(createPropertiesForInverse(this, false, false), + l_targetCRS, l_sourceCRS, negate(x), negate(y), + negate(z), negate(da), negate(df), + coordinateOperationAccuracies())); + } + } + + if (isLongitudeRotation()) { + auto offset = + parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); + const common::Angle newOffset(negate(offset.value()), offset.unit()); + return Private::registerInv( + this, createLongitudeRotation( + createPropertiesForInverse(this, false, false), + l_targetCRS, l_sourceCRS, newOffset)); + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) { + auto offsetLat = + parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); + const common::Angle newOffsetLat(negate(offsetLat.value()), + offsetLat.unit()); + + auto offsetLong = + parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); + const common::Angle newOffsetLong(negate(offsetLong.value()), + offsetLong.unit()); + + return Private::registerInv( + this, createGeographic2DOffsets( + createPropertiesForInverse(this, false, false), + l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong, + coordinateOperationAccuracies())); + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) { + auto offsetLat = + parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); + const common::Angle newOffsetLat(negate(offsetLat.value()), + offsetLat.unit()); + + auto offsetLong = + parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); + const common::Angle newOffsetLong(negate(offsetLong.value()), + offsetLong.unit()); + + auto offsetHeight = + parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); + const common::Length newOffsetHeight(negate(offsetHeight.value()), + offsetHeight.unit()); + + return Private::registerInv( + this, createGeographic3DOffsets( + createPropertiesForInverse(this, false, false), + l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong, + newOffsetHeight, coordinateOperationAccuracies())); + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) { + auto offsetLat = + parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); + const common::Angle newOffsetLat(negate(offsetLat.value()), + offsetLat.unit()); + + auto offsetLong = + parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); + const common::Angle newOffsetLong(negate(offsetLong.value()), + offsetLong.unit()); + + auto offsetHeight = + parameterValueMeasure(EPSG_CODE_PARAMETER_GEOID_UNDULATION); + const common::Length newOffsetHeight(negate(offsetHeight.value()), + offsetHeight.unit()); + + return Private::registerInv( + this, createGeographic2DWithHeightOffsets( + createPropertiesForInverse(this, false, false), + l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong, + newOffsetHeight, coordinateOperationAccuracies())); + } + + if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) { + + auto offsetHeight = + parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); + const common::Length newOffsetHeight(negate(offsetHeight.value()), + offsetHeight.unit()); + + return Private::registerInv( + this, + createVerticalOffset(createPropertiesForInverse(this, false, false), + l_targetCRS, l_sourceCRS, newOffsetHeight, + coordinateOperationAccuracies())); + } + + if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) { + const double convFactor = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); + return Private::registerInv( + this, createChangeVerticalUnit( + createPropertiesForInverse(this, false, false), + l_targetCRS, l_sourceCRS, common::Scale(1.0 / convFactor), + coordinateOperationAccuracies())); + } + +#ifdef notdef + // We don't need that currently, but we might... + if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { + return Private::registerInv( + this, + createHeightDepthReversal( + createPropertiesForInverse(this, false, false), l_targetCRS, + l_sourceCRS, coordinateOperationAccuracies())); + } +#endif + + return InverseTransformation::create(NN_NO_CHECK( + util::nn_dynamic_pointer_cast<Transformation>(shared_from_this()))); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +InverseTransformation::InverseTransformation(const TransformationNNPtr &forward) + : Transformation( + forward->targetCRS(), forward->sourceCRS(), + forward->interpolationCRS(), + OperationMethod::create(createPropertiesForInverse(forward->method()), + forward->method()->parameters()), + forward->parameterValues(), forward->coordinateOperationAccuracies()), + InverseCoordinateOperation(forward, true) { + setPropertiesFromForward(); +} + +// --------------------------------------------------------------------------- + +InverseTransformation::~InverseTransformation() = default; + +// --------------------------------------------------------------------------- + +TransformationNNPtr +InverseTransformation::create(const TransformationNNPtr &forward) { + auto conv = util::nn_make_shared<InverseTransformation>(forward); + conv->assignSelf(conv); + return conv; +} + +// --------------------------------------------------------------------------- + +TransformationNNPtr InverseTransformation::inverseAsTransformation() const { + return NN_NO_CHECK( + util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_)); +} + +// --------------------------------------------------------------------------- + +void InverseTransformation::_exportToWKT(io::WKTFormatter *formatter) const { + + auto approxInverse = createApproximateInverseIfPossible( + util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_).get()); + if (approxInverse) { + approxInverse->_exportToWKT(formatter); + } else { + Transformation::_exportToWKT(formatter); + } +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr InverseTransformation::_shallowClone() const { + auto op = InverseTransformation::nn_make_shared<InverseTransformation>( + inverseAsTransformation()->shallowClone()); + op->assignSelf(op); + op->setCRSs(this, false); + return util::nn_static_pointer_cast<CoordinateOperation>(op); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Transformation::_exportToWKT(io::WKTFormatter *formatter) const { + exportTransformationToWKT(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Transformation::_exportToJSON( + io::JSONFormatter *formatter) const // throw(FormattingException) +{ + auto writer = formatter->writer(); + auto objectContext(formatter->MakeObjectContext( + formatter->abridgedTransformation() ? "AbridgedTransformation" + : "Transformation", + !identifiers().empty())); + + writer->AddObjKey("name"); + auto l_name = nameStr(); + if (l_name.empty()) { + writer->Add("unnamed"); + } else { + writer->Add(l_name); + } + + if (!formatter->abridgedTransformation()) { + writer->AddObjKey("source_crs"); + formatter->setAllowIDInImmediateChild(); + sourceCRS()->_exportToJSON(formatter); + + writer->AddObjKey("target_crs"); + formatter->setAllowIDInImmediateChild(); + targetCRS()->_exportToJSON(formatter); + + const auto &l_interpolationCRS = interpolationCRS(); + if (l_interpolationCRS) { + writer->AddObjKey("interpolation_crs"); + formatter->setAllowIDInImmediateChild(); + l_interpolationCRS->_exportToJSON(formatter); + } + } + + writer->AddObjKey("method"); + formatter->setOmitTypeInImmediateChild(); + formatter->setAllowIDInImmediateChild(); + method()->_exportToJSON(formatter); + + writer->AddObjKey("parameters"); + { + auto parametersContext(writer->MakeArrayContext(false)); + for (const auto &genOpParamvalue : parameterValues()) { + formatter->setAllowIDInImmediateChild(); + formatter->setOmitTypeInImmediateChild(); + genOpParamvalue->_exportToJSON(formatter); + } + } + + if (!formatter->abridgedTransformation()) { + if (!coordinateOperationAccuracies().empty()) { + writer->AddObjKey("accuracy"); + writer->Add(coordinateOperationAccuracies()[0]->value()); + } + } + + if (formatter->abridgedTransformation()) { + if (formatter->outputId()) { + formatID(formatter); + } + } else { + ObjectUsage::baseExportToJSON(formatter); + } +} + +//! @endcond + +//! @cond Doxygen_Suppress +static const std::string nullString; + +static const std::string &_getNTv2Filename(const Transformation *op, + bool allowInverse) { + + const auto &l_method = op->method(); + if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV2 || + (allowInverse && + ci_equal(l_method->nameStr(), INVERSE_OF + EPSG_NAME_METHOD_NTV2))) { + const auto &fileParameter = op->parameterValue( + EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + return fileParameter->valueFile(); + } + } + return nullString; +} +//! @endcond + +// --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress +const std::string &Transformation::getNTv2Filename() const { + + return _getNTv2Filename(this, false); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string &_getNTv1Filename(const Transformation *op, + bool allowInverse) { + + const auto &l_method = op->method(); + const auto &methodName = l_method->nameStr(); + if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV1 || + (allowInverse && + ci_equal(methodName, INVERSE_OF + EPSG_NAME_METHOD_NTV1))) { + const auto &fileParameter = op->parameterValue( + EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + return fileParameter->valueFile(); + } + } + return nullString; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string &_getCTABLE2Filename(const Transformation *op, + bool allowInverse) { + const auto &l_method = op->method(); + const auto &methodName = l_method->nameStr(); + if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_CTABLE2) || + (allowInverse && + ci_equal(methodName, INVERSE_OF + PROJ_WKT2_NAME_METHOD_CTABLE2))) { + const auto &fileParameter = op->parameterValue( + EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + return fileParameter->valueFile(); + } + } + return nullString; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string & +_getHorizontalShiftGTIFFFilename(const Transformation *op, bool allowInverse) { + const auto &l_method = op->method(); + const auto &methodName = l_method->nameStr(); + if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF) || + (allowInverse && + ci_equal(methodName, + INVERSE_OF + PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF))) { + const auto &fileParameter = op->parameterValue( + EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + return fileParameter->valueFile(); + } + } + return nullString; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string & +_getGeocentricTranslationFilename(const Transformation *op, bool allowInverse) { + + const auto &l_method = op->method(); + const auto &methodName = l_method->nameStr(); + if (l_method->getEPSGCode() == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN || + (allowInverse && + ci_equal( + methodName, + INVERSE_OF + + EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN))) { + const auto &fileParameter = + op->parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, + EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + return fileParameter->valueFile(); + } + } + return nullString; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string & +_getHeightToGeographic3DFilename(const Transformation *op, bool allowInverse) { + + const auto &methodName = op->method()->nameStr(); + + if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D) || + (allowInverse && + ci_equal(methodName, + INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D))) { + const auto &fileParameter = + op->parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, + EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + return fileParameter->valueFile(); + } + } + return nullString; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static bool +isGeographic3DToGravityRelatedHeight(const OperationMethodNNPtr &method, + bool allowInverse) { + const auto &methodName = method->nameStr(); + static const char *const methodCodes[] = { + "1025", // Geographic3D to GravityRelatedHeight (EGM2008) + "1030", // Geographic3D to GravityRelatedHeight (NZgeoid) + "1045", // Geographic3D to GravityRelatedHeight (OSGM02-Ire) + "1047", // Geographic3D to GravityRelatedHeight (Gravsoft) + "1048", // Geographic3D to GravityRelatedHeight (Ausgeoid v2) + "1050", // Geographic3D to GravityRelatedHeight (CI) + "1059", // Geographic3D to GravityRelatedHeight (PNG) + "1060", // Geographic3D to GravityRelatedHeight (CGG2013) + "1072", // Geographic3D to GravityRelatedHeight (OSGM15-Ire) + "1073", // Geographic3D to GravityRelatedHeight (IGN2009) + "1081", // Geographic3D to GravityRelatedHeight (BEV AT) + "1083", // Geog3D to Geog2D+Vertical (AUSGeoid v2) + "9661", // Geographic3D to GravityRelatedHeight (EGM) + "9662", // Geographic3D to GravityRelatedHeight (Ausgeoid98) + "9663", // Geographic3D to GravityRelatedHeight (OSGM-GB) + "9664", // Geographic3D to GravityRelatedHeight (IGN1997) + "9665", // Geographic3D to GravityRelatedHeight (US .gtx) + "9635", // Geog3D to Geog2D+GravityRelatedHeight (US .gtx) + }; + + if (ci_find(methodName, "Geographic3D to GravityRelatedHeight") == 0) { + return true; + } + if (allowInverse && + ci_find(methodName, + INVERSE_OF + "Geographic3D to GravityRelatedHeight") == 0) { + return true; + } + + for (const auto &code : methodCodes) { + for (const auto &idSrc : method->identifiers()) { + const auto &srcAuthName = *(idSrc->codeSpace()); + const auto &srcCode = idSrc->code(); + if (ci_equal(srcAuthName, "EPSG") && srcCode == code) { + return true; + } + if (allowInverse && ci_equal(srcAuthName, "INVERSE(EPSG)") && + srcCode == code) { + return true; + } + } + } + return false; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const std::string &Transformation::getHeightToGeographic3DFilename() const { + + const std::string &ret = _getHeightToGeographic3DFilename(this, false); + if (!ret.empty()) + return ret; + if (isGeographic3DToGravityRelatedHeight(method(), false)) { + const auto &fileParameter = + parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, + EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + return fileParameter->valueFile(); + } + } + return nullString; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static util::PropertyMap +createSimilarPropertiesMethod(common::IdentifiedObjectNNPtr obj) { + util::PropertyMap map; + + const std::string &forwardName = obj->nameStr(); + if (!forwardName.empty()) { + map.set(common::IdentifiedObject::NAME_KEY, forwardName); + } + + { + auto ar = util::ArrayOfBaseObject::create(); + for (const auto &idSrc : obj->identifiers()) { + const auto &srcAuthName = *(idSrc->codeSpace()); + const auto &srcCode = idSrc->code(); + auto idsProp = util::PropertyMap().set( + metadata::Identifier::CODESPACE_KEY, srcAuthName); + ar->add(metadata::Identifier::create(srcCode, idsProp)); + } + if (!ar->empty()) { + map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar); + } + } + + return map; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static util::PropertyMap +createSimilarPropertiesTransformation(TransformationNNPtr obj) { + util::PropertyMap map; + + // The domain(s) are unchanged + addDomains(map, obj.get()); + + const std::string &forwardName = obj->nameStr(); + if (!forwardName.empty()) { + map.set(common::IdentifiedObject::NAME_KEY, forwardName); + } + + const std::string &remarks = obj->remarks(); + if (!remarks.empty()) { + map.set(common::IdentifiedObject::REMARKS_KEY, remarks); + } + + addModifiedIdentifier(map, obj.get(), false, true); + + return map; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static TransformationNNPtr +createNTv1(const util::PropertyMap &properties, + const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, + const std::string &filename, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return Transformation::create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV1), + {OperationParameter::create( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, + EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE) + .set(metadata::Identifier::CODESPACE_KEY, + metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE))}, + {ParameterValue::createFilename(filename)}, accuracies); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return an equivalent transformation to the current one, but using + * PROJ alternative grid names. + */ +TransformationNNPtr Transformation::substitutePROJAlternativeGridNames( + io::DatabaseContextNNPtr databaseContext) const { + auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>( + shared_from_this().as_nullable())); + + const auto &l_method = method(); + const int methodEPSGCode = l_method->getEPSGCode(); + + std::string projFilename; + std::string projGridFormat; + bool inverseDirection = false; + + const auto &NTv1Filename = _getNTv1Filename(this, false); + const auto &NTv2Filename = _getNTv2Filename(this, false); + std::string lasFilename; + if (methodEPSGCode == EPSG_CODE_METHOD_NADCON) { + const auto &latitudeFileParameter = + parameterValue(EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE); + const auto &longitudeFileParameter = + parameterValue(EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE); + if (latitudeFileParameter && + latitudeFileParameter->type() == ParameterValue::Type::FILENAME && + longitudeFileParameter && + longitudeFileParameter->type() == ParameterValue::Type::FILENAME) { + lasFilename = latitudeFileParameter->valueFile(); + } + } + const auto &horizontalGridName = + !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty() + ? NTv2Filename + : lasFilename; + + if (!horizontalGridName.empty() && + databaseContext->lookForGridAlternative(horizontalGridName, + projFilename, projGridFormat, + inverseDirection)) { + + if (horizontalGridName == projFilename) { + if (inverseDirection) { + throw util::UnsupportedOperationException( + "Inverse direction for " + projFilename + " not supported"); + } + return self; + } + + const auto &l_sourceCRS = sourceCRS(); + const auto &l_targetCRS = targetCRS(); + const auto &l_accuracies = coordinateOperationAccuracies(); + if (projGridFormat == "GTiff") { + auto parameters = + std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}; + auto methodProperties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF); + auto values = std::vector<ParameterValueNNPtr>{ + ParameterValue::createFilename(projFilename)}; + if (inverseDirection) { + return create(createPropertiesForInverse( + self.as_nullable().get(), true, false), + l_targetCRS, l_sourceCRS, nullptr, + methodProperties, parameters, values, + l_accuracies) + ->inverseAsTransformation(); + + } else { + return create(createSimilarPropertiesTransformation(self), + l_sourceCRS, l_targetCRS, nullptr, + methodProperties, parameters, values, + l_accuracies); + } + } else if (projGridFormat == "NTv1") { + if (inverseDirection) { + return createNTv1(createPropertiesForInverse( + self.as_nullable().get(), true, false), + l_targetCRS, l_sourceCRS, projFilename, + l_accuracies) + ->inverseAsTransformation(); + } else { + return createNTv1(createSimilarPropertiesTransformation(self), + l_sourceCRS, l_targetCRS, projFilename, + l_accuracies); + } + } else if (projGridFormat == "NTv2") { + if (inverseDirection) { + return createNTv2(createPropertiesForInverse( + self.as_nullable().get(), true, false), + l_targetCRS, l_sourceCRS, projFilename, + l_accuracies) + ->inverseAsTransformation(); + } else { + return createNTv2(createSimilarPropertiesTransformation(self), + l_sourceCRS, l_targetCRS, projFilename, + l_accuracies); + } + } else if (projGridFormat == "CTable2") { + auto parameters = + std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}; + auto methodProperties = + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + PROJ_WKT2_NAME_METHOD_CTABLE2); + auto values = std::vector<ParameterValueNNPtr>{ + ParameterValue::createFilename(projFilename)}; + if (inverseDirection) { + return create(createPropertiesForInverse( + self.as_nullable().get(), true, false), + l_targetCRS, l_sourceCRS, nullptr, + methodProperties, parameters, values, + l_accuracies) + ->inverseAsTransformation(); + + } else { + return create(createSimilarPropertiesTransformation(self), + l_sourceCRS, l_targetCRS, nullptr, + methodProperties, parameters, values, + l_accuracies); + } + } + } + + if (isGeographic3DToGravityRelatedHeight(method(), false)) { + const auto &fileParameter = + parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, + EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + auto filename = fileParameter->valueFile(); + if (databaseContext->lookForGridAlternative( + filename, projFilename, projGridFormat, inverseDirection)) { + + if (inverseDirection) { + throw util::UnsupportedOperationException( + "Inverse direction for " + "Geographic3DToGravityRelatedHeight not supported"); + } + + if (filename == projFilename) { + return self; + } + + auto parameters = std::vector<OperationParameterNNPtr>{ + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)}; +#ifdef disabled_for_now + if (inverseDirection) { + return create(createPropertiesForInverse( + self.as_nullable().get(), true, false), + targetCRS(), sourceCRS(), nullptr, + createSimilarPropertiesMethod(method()), + parameters, {ParameterValue::createFilename( + projFilename)}, + coordinateOperationAccuracies()) + ->inverseAsTransformation(); + } else +#endif + { + return create( + createSimilarPropertiesTransformation(self), + sourceCRS(), targetCRS(), nullptr, + createSimilarPropertiesMethod(method()), parameters, + {ParameterValue::createFilename(projFilename)}, + coordinateOperationAccuracies()); + } + } + } + } + + const auto &geocentricTranslationFilename = + _getGeocentricTranslationFilename(this, false); + if (!geocentricTranslationFilename.empty()) { + if (databaseContext->lookForGridAlternative( + geocentricTranslationFilename, projFilename, projGridFormat, + inverseDirection)) { + + if (inverseDirection) { + throw util::UnsupportedOperationException( + "Inverse direction for " + "GeocentricTranslation not supported"); + } + + if (geocentricTranslationFilename == projFilename) { + return self; + } + + auto parameters = + std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE)}; + return create(createSimilarPropertiesTransformation(self), + sourceCRS(), targetCRS(), interpolationCRS(), + createSimilarPropertiesMethod(method()), parameters, + {ParameterValue::createFilename(projFilename)}, + coordinateOperationAccuracies()); + } + } + + if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON || + methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD || + methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT || + methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX) { + auto fileParameter = + parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, + EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + + auto filename = fileParameter->valueFile(); + if (databaseContext->lookForGridAlternative( + filename, projFilename, projGridFormat, inverseDirection)) { + + if (filename == projFilename) { + if (inverseDirection) { + throw util::UnsupportedOperationException( + "Inverse direction for " + projFilename + + " not supported"); + } + return self; + } + + auto parameters = std::vector<OperationParameterNNPtr>{ + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)}; + if (inverseDirection) { + return create(createPropertiesForInverse( + self.as_nullable().get(), true, false), + targetCRS(), sourceCRS(), nullptr, + createSimilarPropertiesMethod(method()), + parameters, {ParameterValue::createFilename( + projFilename)}, + coordinateOperationAccuracies()) + ->inverseAsTransformation(); + } else { + return create( + createSimilarPropertiesTransformation(self), + sourceCRS(), targetCRS(), nullptr, + createSimilarPropertiesMethod(method()), parameters, + {ParameterValue::createFilename(projFilename)}, + coordinateOperationAccuracies()); + } + } + } + } + + return self; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static void ThrowExceptionNotGeodeticGeographic(const char *trfrm_name) { + throw io::FormattingException(concat("Can apply ", std::string(trfrm_name), + " only to GeodeticCRS / " + "GeographicCRS")); +} + +// --------------------------------------------------------------------------- + +// If crs is a geographic CRS, or a compound CRS of a geographic CRS, +// or a compoundCRS of a bound CRS of a geographic CRS, return that +// geographic CRS +static crs::GeographicCRSPtr +extractGeographicCRSIfGeographicCRSOrEquivalent(const crs::CRSNNPtr &crs) { + auto geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(crs); + if (!geogCRS) { + auto compoundCRS = util::nn_dynamic_pointer_cast<crs::CompoundCRS>(crs); + if (compoundCRS) { + const auto &components = compoundCRS->componentReferenceSystems(); + if (!components.empty()) { + geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>( + components[0]); + if (!geogCRS) { + auto boundCRS = + util::nn_dynamic_pointer_cast<crs::BoundCRS>( + components[0]); + if (boundCRS) { + geogCRS = + util::nn_dynamic_pointer_cast<crs::GeographicCRS>( + boundCRS->baseCRS()); + } + } + } + } else { + auto boundCRS = util::nn_dynamic_pointer_cast<crs::BoundCRS>(crs); + if (boundCRS) { + geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>( + boundCRS->baseCRS()); + } + } + } + return geogCRS; +} + +// --------------------------------------------------------------------------- + +static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter, + const crs::CRSNNPtr &crs, bool addPushV3, + const char *trfrm_name) { + auto sourceCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs); + if (sourceCRSGeog) { + formatter->startInversion(); + sourceCRSGeog->_exportToPROJString(formatter); + formatter->stopInversion(); + if (util::isOfExactType<crs::DerivedGeographicCRS>( + *(sourceCRSGeog.get()))) { + // The export of a DerivedGeographicCRS in non-CRS mode adds + // unit conversion and axis swapping. We must compensate for that + formatter->startInversion(); + sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + } + + if (addPushV3) { + formatter->addStep("push"); + formatter->addParam("v_3"); + } + + formatter->addStep("cart"); + sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); + } else { + auto sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get()); + if (!sourceCRSGeod) { + ThrowExceptionNotGeodeticGeographic(trfrm_name); + } + formatter->startInversion(); + sourceCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter); + formatter->stopInversion(); + } +} +// --------------------------------------------------------------------------- + +static void setupPROJGeodeticTargetCRS(io::PROJStringFormatter *formatter, + const crs::CRSNNPtr &crs, bool addPopV3, + const char *trfrm_name) { + auto targetCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs); + if (targetCRSGeog) { + formatter->addStep("cart"); + formatter->setCurrentStepInverted(true); + targetCRSGeog->ellipsoid()->_exportToPROJString(formatter); + + if (addPopV3) { + formatter->addStep("pop"); + formatter->addParam("v_3"); + } + if (util::isOfExactType<crs::DerivedGeographicCRS>( + *(targetCRSGeog.get()))) { + // The export of a DerivedGeographicCRS in non-CRS mode adds + // unit conversion and axis swapping. We must compensate for that + targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + } + targetCRSGeog->_exportToPROJString(formatter); + } else { + auto targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get()); + if (!targetCRSGeod) { + ThrowExceptionNotGeodeticGeographic(trfrm_name); + } + targetCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter); + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + +void Transformation::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(FormattingException) +{ + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + throw io::FormattingException( + "Transformation cannot be exported as a PROJ.4 string"); + } + + formatter->setCoordinateOperationOptimizations(true); + + bool positionVectorConvention = true; + bool sevenParamsTransform = false; + bool threeParamsTransform = false; + bool fifteenParamsTransform = false; + const auto &l_method = method(); + const int methodEPSGCode = l_method->getEPSGCode(); + const auto &methodName = l_method->nameStr(); + const auto paramCount = parameterValues().size(); + const bool l_isTimeDependent = isTimeDependent(methodName); + const bool isPositionVector = + ci_find(methodName, "Position Vector") != std::string::npos || + ci_find(methodName, "PV") != std::string::npos; + const bool isCoordinateFrame = + ci_find(methodName, "Coordinate Frame") != std::string::npos || + ci_find(methodName, "CF") != std::string::npos; + if ((paramCount == 7 && isCoordinateFrame && !l_isTimeDependent) || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) { + positionVectorConvention = false; + sevenParamsTransform = true; + } else if ( + (paramCount == 15 && isCoordinateFrame && l_isTimeDependent) || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) { + positionVectorConvention = false; + fifteenParamsTransform = true; + } else if ((paramCount == 7 && isPositionVector && !l_isTimeDependent) || + methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) { + sevenParamsTransform = true; + } else if ( + (paramCount == 15 && isPositionVector && l_isTimeDependent) || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) { + fifteenParamsTransform = true; + } else if ((paramCount == 3 && + ci_find(methodName, "Geocentric translations") != + std::string::npos) || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) { + threeParamsTransform = true; + } + if (threeParamsTransform || sevenParamsTransform || + fifteenParamsTransform) { + double x = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); + double y = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); + double z = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); + + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); + const bool addPushPopV3 = + !CoordinateOperation::getPrivate()->use3DHelmert_ && + ((sourceCRSGeog && + sourceCRSGeog->coordinateSystem()->axisList().size() == 2) || + (targetCRSGeog && + targetCRSGeog->coordinateSystem()->axisList().size() == 2)); + + setupPROJGeodeticSourceCRS(formatter, sourceCRS(), addPushPopV3, + "Helmert"); + + formatter->addStep("helmert"); + formatter->addParam("x", x); + formatter->addParam("y", y); + formatter->addParam("z", z); + if (sevenParamsTransform || fifteenParamsTransform) { + double rx = + parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND); + double ry = + parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND); + double rz = + parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND); + double scaleDiff = + parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, + common::UnitOfMeasure::PARTS_PER_MILLION); + formatter->addParam("rx", rx); + formatter->addParam("ry", ry); + formatter->addParam("rz", rz); + formatter->addParam("s", scaleDiff); + if (fifteenParamsTransform) { + double rate_x = parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION, + common::UnitOfMeasure::METRE_PER_YEAR); + double rate_y = parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION, + common::UnitOfMeasure::METRE_PER_YEAR); + double rate_z = parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION, + common::UnitOfMeasure::METRE_PER_YEAR); + double rate_rx = parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR); + double rate_ry = parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR); + double rate_rz = parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR); + double rate_scaleDiff = parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE, + common::UnitOfMeasure::PPM_PER_YEAR); + double referenceEpochYear = + parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH, + common::UnitOfMeasure::YEAR); + formatter->addParam("dx", rate_x); + formatter->addParam("dy", rate_y); + formatter->addParam("dz", rate_z); + formatter->addParam("drx", rate_rx); + formatter->addParam("dry", rate_ry); + formatter->addParam("drz", rate_rz); + formatter->addParam("ds", rate_scaleDiff); + formatter->addParam("t_epoch", referenceEpochYear); + } + if (positionVectorConvention) { + formatter->addParam("convention", "position_vector"); + } else { + formatter->addParam("convention", "coordinate_frame"); + } + } + + setupPROJGeodeticTargetCRS(formatter, targetCRS(), addPushPopV3, + "Helmert"); + + return; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC || + methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D) { + + positionVectorConvention = + isPositionVector || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D; + + double x = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); + double y = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); + double z = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); + double rx = parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND); + double ry = parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND); + double rz = parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND); + double scaleDiff = + parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, + common::UnitOfMeasure::PARTS_PER_MILLION); + + double px = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT); + double py = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT); + double pz = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT); + + bool addPushPopV3 = + (methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D); + + setupPROJGeodeticSourceCRS(formatter, sourceCRS(), addPushPopV3, + "Molodensky-Badekas"); + + formatter->addStep("molobadekas"); + formatter->addParam("x", x); + formatter->addParam("y", y); + formatter->addParam("z", z); + formatter->addParam("rx", rx); + formatter->addParam("ry", ry); + formatter->addParam("rz", rz); + formatter->addParam("s", scaleDiff); + formatter->addParam("px", px); + formatter->addParam("py", py); + formatter->addParam("pz", pz); + if (positionVectorConvention) { + formatter->addParam("convention", "position_vector"); + } else { + formatter->addParam("convention", "coordinate_frame"); + } + + setupPROJGeodeticTargetCRS(formatter, targetCRS(), addPushPopV3, + "Molodensky-Badekas"); + + return; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY || + methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { + double x = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); + double y = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); + double z = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); + double da = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE); + double df = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE); + + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + "Can apply Molodensky only to GeographicCRS"); + } + + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); + if (!targetCRSGeog) { + throw io::FormattingException( + "Can apply Molodensky only to GeographicCRS"); + } + + formatter->startInversion(); + sourceCRSGeog->_exportToPROJString(formatter); + formatter->stopInversion(); + + formatter->addStep("molodensky"); + sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); + formatter->addParam("dx", x); + formatter->addParam("dy", y); + formatter->addParam("dz", z); + formatter->addParam("da", da); + formatter->addParam("df", df); + + if (ci_find(methodName, "Abridged") != std::string::npos || + methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { + formatter->addParam("abridged"); + } + + targetCRSGeog->_exportToPROJString(formatter); + + return; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) { + double offsetLat = + parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, + common::UnitOfMeasure::ARC_SECOND); + double offsetLong = + parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, + common::UnitOfMeasure::ARC_SECOND); + + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + "Can apply Geographic 2D offsets only to GeographicCRS"); + } + + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); + if (!targetCRSGeog) { + throw io::FormattingException( + "Can apply Geographic 2D offsets only to GeographicCRS"); + } + + formatter->startInversion(); + sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + if (offsetLat != 0.0 || offsetLong != 0.0) { + formatter->addStep("geogoffset"); + formatter->addParam("dlat", offsetLat); + formatter->addParam("dlon", offsetLong); + } + + targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + + return; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) { + double offsetLat = + parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, + common::UnitOfMeasure::ARC_SECOND); + double offsetLong = + parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, + common::UnitOfMeasure::ARC_SECOND); + double offsetHeight = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); + + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + "Can apply Geographic 3D offsets only to GeographicCRS"); + } + + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); + if (!targetCRSGeog) { + throw io::FormattingException( + "Can apply Geographic 3D offsets only to GeographicCRS"); + } + + formatter->startInversion(); + sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) { + formatter->addStep("geogoffset"); + formatter->addParam("dlat", offsetLat); + formatter->addParam("dlon", offsetLong); + formatter->addParam("dh", offsetHeight); + } + + targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + + return; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) { + double offsetLat = + parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, + common::UnitOfMeasure::ARC_SECOND); + double offsetLong = + parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, + common::UnitOfMeasure::ARC_SECOND); + double offsetHeight = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_UNDULATION); + + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); + if (!sourceCRSGeog) { + auto sourceCRSCompound = + dynamic_cast<const crs::CompoundCRS *>(sourceCRS().get()); + if (sourceCRSCompound) { + sourceCRSGeog = sourceCRSCompound->extractGeographicCRS().get(); + } + if (!sourceCRSGeog) { + throw io::FormattingException("Can apply Geographic 2D with " + "height offsets only to " + "GeographicCRS / CompoundCRS"); + } + } + + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); + if (!targetCRSGeog) { + auto targetCRSCompound = + dynamic_cast<const crs::CompoundCRS *>(targetCRS().get()); + if (targetCRSCompound) { + targetCRSGeog = targetCRSCompound->extractGeographicCRS().get(); + } + if (!targetCRSGeog) { + throw io::FormattingException("Can apply Geographic 2D with " + "height offsets only to " + "GeographicCRS / CompoundCRS"); + } + } + + formatter->startInversion(); + sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) { + formatter->addStep("geogoffset"); + formatter->addParam("dlat", offsetLat); + formatter->addParam("dlon", offsetLong); + formatter->addParam("dh", offsetHeight); + } + + targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + + return; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) { + + auto sourceCRSVert = + dynamic_cast<const crs::VerticalCRS *>(sourceCRS().get()); + if (!sourceCRSVert) { + throw io::FormattingException( + "Can apply Vertical offset only to VerticalCRS"); + } + + auto targetCRSVert = + dynamic_cast<const crs::VerticalCRS *>(targetCRS().get()); + if (!targetCRSVert) { + throw io::FormattingException( + "Can apply Vertical offset only to VerticalCRS"); + } + + auto offsetHeight = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); + + formatter->startInversion(); + sourceCRSVert->addLinearUnitConvert(formatter); + formatter->stopInversion(); + + formatter->addStep("geogoffset"); + formatter->addParam("dh", offsetHeight); + + targetCRSVert->addLinearUnitConvert(formatter); + + return; + } + + // Substitute grid names with PROJ friendly names. + if (formatter->databaseContext()) { + auto alternate = substitutePROJAlternativeGridNames( + NN_NO_CHECK(formatter->databaseContext())); + auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>( + shared_from_this().as_nullable())); + + if (alternate != self) { + alternate->_exportToPROJString(formatter); + return; + } + } + + const bool isMethodInverseOf = starts_with(methodName, INVERSE_OF); + + const auto &NTv1Filename = _getNTv1Filename(this, true); + const auto &NTv2Filename = _getNTv2Filename(this, true); + const auto &CTABLE2Filename = _getCTABLE2Filename(this, true); + const auto &HorizontalShiftGTIFFFilename = + _getHorizontalShiftGTIFFFilename(this, true); + const auto &hGridShiftFilename = + !HorizontalShiftGTIFFFilename.empty() + ? HorizontalShiftGTIFFFilename + : !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty() + ? NTv2Filename + : CTABLE2Filename; + if (!hGridShiftFilename.empty()) { + auto sourceCRSGeog = + extractGeographicCRSIfGeographicCRSOrEquivalent(sourceCRS()); + if (!sourceCRSGeog) { + throw io::FormattingException( + concat("Can apply ", methodName, " only to GeographicCRS")); + } + + auto targetCRSGeog = + extractGeographicCRSIfGeographicCRSOrEquivalent(targetCRS()); + if (!targetCRSGeog) { + throw io::FormattingException( + concat("Can apply ", methodName, " only to GeographicCRS")); + } + + formatter->startInversion(); + sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + if (isMethodInverseOf) { + formatter->startInversion(); + } + formatter->addStep("hgridshift"); + formatter->addParam("grids", hGridShiftFilename); + if (isMethodInverseOf) { + formatter->stopInversion(); + } + + targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + + return; + } + + const auto &geocentricTranslationFilename = + _getGeocentricTranslationFilename(this, true); + if (!geocentricTranslationFilename.empty()) { + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + concat("Can apply ", methodName, " only to GeographicCRS")); + } + + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); + if (!targetCRSGeog) { + throw io::FormattingException( + concat("Can apply ", methodName, " only to GeographicCRS")); + } + + const auto &interpCRS = interpolationCRS(); + if (!interpCRS) { + throw io::FormattingException( + "InterpolationCRS required " + "for" + " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN); + } + const bool interpIsSrc = interpCRS->_isEquivalentTo( + sourceCRS().get(), util::IComparable::Criterion::EQUIVALENT); + const bool interpIsTarget = interpCRS->_isEquivalentTo( + targetCRS().get(), util::IComparable::Criterion::EQUIVALENT); + if (!interpIsSrc && !interpIsTarget) { + throw io::FormattingException( + "For" + " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN + ", interpolation CRS should be the source or target CRS"); + } + + formatter->startInversion(); + sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + if (isMethodInverseOf) { + formatter->startInversion(); + } + + formatter->addStep("push"); + formatter->addParam("v_3"); + + formatter->addStep("cart"); + sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); + + formatter->addStep("xyzgridshift"); + formatter->addParam("grids", geocentricTranslationFilename); + formatter->addParam("grid_ref", + interpIsTarget ? "output_crs" : "input_crs"); + (interpIsTarget ? targetCRSGeog : sourceCRSGeog) + ->ellipsoid() + ->_exportToPROJString(formatter); + + formatter->startInversion(); + formatter->addStep("cart"); + targetCRSGeog->ellipsoid()->_exportToPROJString(formatter); + formatter->stopInversion(); + + formatter->addStep("pop"); + formatter->addParam("v_3"); + + if (isMethodInverseOf) { + formatter->stopInversion(); + } + + targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + + return; + } + + const auto &heightFilename = _getHeightToGeographic3DFilename(this, true); + if (!heightFilename.empty()) { + auto targetCRSGeog = + extractGeographicCRSIfGeographicCRSOrEquivalent(targetCRS()); + if (!targetCRSGeog) { + throw io::FormattingException( + concat("Can apply ", methodName, " only to GeographicCRS")); + } + + if (!formatter->omitHorizontalConversionInVertTransformation()) { + formatter->startInversion(); + formatter->pushOmitZUnitConversion(); + targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->popOmitZUnitConversion(); + formatter->stopInversion(); + } + + if (isMethodInverseOf) { + formatter->startInversion(); + } + formatter->addStep("vgridshift"); + formatter->addParam("grids", heightFilename); + formatter->addParam("multiplier", 1.0); + if (isMethodInverseOf) { + formatter->stopInversion(); + } + + if (!formatter->omitHorizontalConversionInVertTransformation()) { + formatter->pushOmitZUnitConversion(); + targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->popOmitZUnitConversion(); + } + + return; + } + + if (isGeographic3DToGravityRelatedHeight(method(), true)) { + auto fileParameter = + parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, + EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + auto filename = fileParameter->valueFile(); + + auto sourceCRSGeog = + extractGeographicCRSIfGeographicCRSOrEquivalent(sourceCRS()); + if (!sourceCRSGeog) { + throw io::FormattingException( + concat("Can apply ", methodName, " only to GeographicCRS")); + } + + if (!formatter->omitHorizontalConversionInVertTransformation()) { + formatter->startInversion(); + formatter->pushOmitZUnitConversion(); + sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->popOmitZUnitConversion(); + formatter->stopInversion(); + } + + bool doInversion = isMethodInverseOf; + // The EPSG Geog3DToHeight is the reverse convention of PROJ ! + doInversion = !doInversion; + if (doInversion) { + formatter->startInversion(); + } + formatter->addStep("vgridshift"); + formatter->addParam("grids", filename); + formatter->addParam("multiplier", 1.0); + if (doInversion) { + formatter->stopInversion(); + } + + if (!formatter->omitHorizontalConversionInVertTransformation()) { + formatter->pushOmitZUnitConversion(); + sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->popOmitZUnitConversion(); + } + + return; + } + } + + if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON) { + auto fileParameter = + parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, + EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + formatter->addStep("vgridshift"); + formatter->addParam("grids", fileParameter->valueFile()); + if (fileParameter->valueFile().find(".tif") != std::string::npos) { + formatter->addParam("multiplier", 1.0); + } else { + // The vertcon grids go from NGVD 29 to NAVD 88, with units + // in millimeter (see + // https://github.com/OSGeo/proj.4/issues/1071), for gtx files + formatter->addParam("multiplier", 0.001); + } + return; + } + } + + if (methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD || + methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT || + methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX) { + auto fileParameter = + parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, + EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + formatter->addStep("vgridshift"); + formatter->addParam("grids", fileParameter->valueFile()); + formatter->addParam("multiplier", 1.0); + return; + } + } + + if (isLongitudeRotation()) { + double offsetDeg = + parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, + common::UnitOfMeasure::DEGREE); + + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + concat("Can apply ", methodName, " only to GeographicCRS")); + } + + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); + if (!targetCRSGeog) { + throw io::FormattingException( + concat("Can apply ", methodName + " only to GeographicCRS")); + } + + if (!sourceCRSGeog->ellipsoid()->_isEquivalentTo( + targetCRSGeog->ellipsoid().get(), + util::IComparable::Criterion::EQUIVALENT)) { + // This is arguable if we should check this... + throw io::FormattingException("Can apply Longitude rotation " + "only to SRS with same " + "ellipsoid"); + } + + formatter->startInversion(); + sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + bool done = false; + if (offsetDeg != 0.0) { + // Optimization: as we are doing nominally a +step=inv, + // if the negation of the offset value is a well-known name, + // then use forward case with this name. + auto projPMName = datum::PrimeMeridian::getPROJStringWellKnownName( + common::Angle(-offsetDeg)); + if (!projPMName.empty()) { + done = true; + formatter->addStep("longlat"); + sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); + formatter->addParam("pm", projPMName); + } + } + if (!done) { + // To actually add the offset, we must use the reverse longlat + // operation. + formatter->startInversion(); + formatter->addStep("longlat"); + sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); + datum::PrimeMeridian::create(util::PropertyMap(), + common::Angle(offsetDeg)) + ->_exportToPROJString(formatter); + formatter->stopInversion(); + } + + targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + + return; + } + + if (exportToPROJStringGeneric(formatter)) { + return; + } + + throw io::FormattingException("Unimplemented"); +} + +} // namespace crs +NS_PROJ_END diff --git a/src/iso19111/operation/vectorofvaluesparams.cpp b/src/iso19111/operation/vectorofvaluesparams.cpp new file mode 100644 index 00000000..e70ecced --- /dev/null +++ b/src/iso19111/operation/vectorofvaluesparams.cpp @@ -0,0 +1,116 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "vectorofvaluesparams.hpp" + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static std::vector<ParameterValueNNPtr> buildParameterValueFromMeasure( + const std::initializer_list<common::Measure> &list) { + std::vector<ParameterValueNNPtr> res; + for (const auto &v : list) { + res.emplace_back(ParameterValue::create(v)); + } + return res; +} + +VectorOfValues::VectorOfValues(std::initializer_list<common::Measure> list) + : std::vector<ParameterValueNNPtr>(buildParameterValueFromMeasure(list)) {} + +// This way, we disable inlining of destruction, and save a lot of space +VectorOfValues::~VectorOfValues() = default; + +VectorOfValues createParams(const common::Measure &m1, + const common::Measure &m2, + const common::Measure &m3) { + return VectorOfValues{ParameterValue::create(m1), + ParameterValue::create(m2), + ParameterValue::create(m3)}; +} + +VectorOfValues createParams(const common::Measure &m1, + const common::Measure &m2, + const common::Measure &m3, + const common::Measure &m4) { + return VectorOfValues{ + ParameterValue::create(m1), ParameterValue::create(m2), + ParameterValue::create(m3), ParameterValue::create(m4)}; +} + +VectorOfValues createParams(const common::Measure &m1, + const common::Measure &m2, + const common::Measure &m3, + const common::Measure &m4, + const common::Measure &m5) { + return VectorOfValues{ + ParameterValue::create(m1), ParameterValue::create(m2), + ParameterValue::create(m3), ParameterValue::create(m4), + ParameterValue::create(m5), + }; +} + +VectorOfValues +createParams(const common::Measure &m1, const common::Measure &m2, + const common::Measure &m3, const common::Measure &m4, + const common::Measure &m5, const common::Measure &m6) { + return VectorOfValues{ + ParameterValue::create(m1), ParameterValue::create(m2), + ParameterValue::create(m3), ParameterValue::create(m4), + ParameterValue::create(m5), ParameterValue::create(m6), + }; +} + +VectorOfValues +createParams(const common::Measure &m1, const common::Measure &m2, + const common::Measure &m3, const common::Measure &m4, + const common::Measure &m5, const common::Measure &m6, + const common::Measure &m7) { + return VectorOfValues{ + ParameterValue::create(m1), ParameterValue::create(m2), + ParameterValue::create(m3), ParameterValue::create(m4), + ParameterValue::create(m5), ParameterValue::create(m6), + ParameterValue::create(m7), + }; +} + +// This way, we disable inlining of destruction, and save a lot of space +VectorOfParameters::~VectorOfParameters() = default; + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END diff --git a/src/iso19111/operation/vectorofvaluesparams.hpp b/src/iso19111/operation/vectorofvaluesparams.hpp new file mode 100644 index 00000000..6f12543b --- /dev/null +++ b/src/iso19111/operation/vectorofvaluesparams.hpp @@ -0,0 +1,101 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef VECTOROFVALUESPARAMS_HPP +#define VECTOROFVALUESPARAMS_HPP + +#include "proj/coordinateoperation.hpp" +#include "proj/util.hpp" + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +struct VectorOfValues : public std::vector<ParameterValueNNPtr> { + VectorOfValues() : std::vector<ParameterValueNNPtr>() {} + explicit VectorOfValues(std::initializer_list<ParameterValueNNPtr> list) + : std::vector<ParameterValueNNPtr>(list) {} + + explicit VectorOfValues(std::initializer_list<common::Measure> list); + VectorOfValues(const VectorOfValues &) = delete; + VectorOfValues(VectorOfValues &&) = default; + + ~VectorOfValues(); +}; + +VectorOfValues createParams(const common::Measure &m1, + const common::Measure &m2, + const common::Measure &m3); + +VectorOfValues createParams(const common::Measure &m1, + const common::Measure &m2, + const common::Measure &m3, + const common::Measure &m4); + +VectorOfValues createParams(const common::Measure &m1, + const common::Measure &m2, + const common::Measure &m3, + const common::Measure &m4, + const common::Measure &m5); + +VectorOfValues +createParams(const common::Measure &m1, const common::Measure &m2, + const common::Measure &m3, const common::Measure &m4, + const common::Measure &m5, const common::Measure &m6); + +VectorOfValues +createParams(const common::Measure &m1, const common::Measure &m2, + const common::Measure &m3, const common::Measure &m4, + const common::Measure &m5, const common::Measure &m6, + const common::Measure &m7); + +// --------------------------------------------------------------------------- + +struct VectorOfParameters : public std::vector<OperationParameterNNPtr> { + VectorOfParameters() : std::vector<OperationParameterNNPtr>() {} + explicit VectorOfParameters( + std::initializer_list<OperationParameterNNPtr> list) + : std::vector<OperationParameterNNPtr>(list) {} + VectorOfParameters(const VectorOfParameters &) = delete; + + ~VectorOfParameters(); +}; + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END + +#endif // VECTOROFVALUESPARAMS_HPP diff --git a/src/iso19111/static.cpp b/src/iso19111/static.cpp index 11306e35..d099cd6c 100644 --- a/src/iso19111/static.cpp +++ b/src/iso19111/static.cpp @@ -39,6 +39,7 @@ #include "proj/metadata.hpp" #include "proj/util.hpp" +#include "operation/oputils.hpp" #include "proj/internal/coordinatesystem_internal.hpp" #include "proj/internal/io_internal.hpp" @@ -653,6 +654,17 @@ const GeographicCRSNNPtr const std::string operation::CoordinateOperation::OPERATION_VERSION_KEY("operationVersion"); +//! @cond Doxygen_Suppress +const common::Measure operation::nullMeasure{}; + +const std::string operation::INVERSE_OF = "Inverse of "; + +const std::string operation::AXIS_ORDER_CHANGE_2D_NAME = + "axis order change (2D)"; +const std::string operation::AXIS_ORDER_CHANGE_3D_NAME = + "axis order change (geographic3D horizontal)"; +//! @endcond + // --------------------------------------------------------------------------- NS_PROJ_END diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake index 3a4880c6..95bb222a 100644 --- a/src/lib_proj.cmake +++ b/src/lib_proj.cmake @@ -199,11 +199,20 @@ set(SRC_LIBPROJ_ISO19111 iso19111/crs.cpp iso19111/datum.cpp iso19111/coordinatesystem.cpp - iso19111/coordinateoperation.cpp iso19111/io.cpp iso19111/internal.cpp iso19111/factory.cpp iso19111/c_api.cpp + iso19111/operation/concatenatedoperation.cpp + iso19111/operation/coordinateoperationfactory.cpp + iso19111/operation/conversion.cpp + iso19111/operation/esriparammappings.cpp + iso19111/operation/oputils.cpp + iso19111/operation/parammappings.cpp + iso19111/operation/projbasedoperation.cpp + iso19111/operation/singleoperation.cpp + iso19111/operation/transformation.cpp + iso19111/operation/vectorofvaluesparams.cpp ) set(SRC_LIBPROJ_CORE diff --git a/src/log.cpp b/src/log.cpp index c343e65b..c50b0ebc 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -96,3 +96,93 @@ void pj_log( PJ_CONTEXT *ctx, int level, const char *fmt, ... ) pj_vlog( ctx, level, fmt, args ); va_end( args ); } + + + +/***************************************************************************************/ +PJ_LOG_LEVEL proj_log_level (PJ_CONTEXT *ctx, PJ_LOG_LEVEL log_level) { +/**************************************************************************************** + Set logging level 0-3. Higher number means more debug info. 0 turns it off +****************************************************************************************/ + PJ_LOG_LEVEL previous; + if (nullptr==ctx) + ctx = pj_get_default_ctx(); + if (nullptr==ctx) + return PJ_LOG_TELL; + previous = static_cast<PJ_LOG_LEVEL>(abs (ctx->debug_level)); + if (PJ_LOG_TELL==log_level) + return previous; + ctx->debug_level = log_level; + return previous; +} + +/*****************************************************************************/ +static std::string add_short_name_prefix(const PJ* P, const char* fmt) +/*****************************************************************************/ +{ + if( P->short_name == nullptr ) + return fmt; + std::string ret(P->short_name); + ret += ": "; + ret += fmt; + return ret; +} + +/*****************************************************************************/ +void proj_log_error (const PJ *P, const char *fmt, ...) { +/****************************************************************************** + For reporting the most severe events. +******************************************************************************/ + va_list args; + va_start( args, fmt ); + pj_vlog (pj_get_ctx ((PJ*)P), PJ_LOG_ERROR , add_short_name_prefix(P, fmt).c_str(), args); + va_end( args ); +} + + +/*****************************************************************************/ +void proj_log_debug (PJ *P, const char *fmt, ...) { +/****************************************************************************** + For reporting debugging information. +******************************************************************************/ + va_list args; + va_start( args, fmt ); + pj_vlog (pj_get_ctx (P), PJ_LOG_DEBUG , add_short_name_prefix(P, fmt).c_str(), args); + va_end( args ); +} + +/*****************************************************************************/ +void proj_context_log_debug (PJ_CONTEXT *ctx, const char *fmt, ...) { +/****************************************************************************** + For reporting debugging information. +******************************************************************************/ + va_list args; + va_start( args, fmt ); + pj_vlog (ctx, PJ_LOG_DEBUG , fmt, args); + va_end( args ); +} + +/*****************************************************************************/ +void proj_log_trace (PJ *P, const char *fmt, ...) { +/****************************************************************************** + For reporting embarrassingly detailed debugging information. +******************************************************************************/ + va_list args; + va_start( args, fmt ); + pj_vlog (pj_get_ctx (P), PJ_LOG_TRACE , add_short_name_prefix(P, fmt).c_str(), args); + va_end( args ); +} + + +/*****************************************************************************/ +void proj_log_func (PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf) { +/****************************************************************************** + Put a new logging function into P's context. The opaque object app_data is + passed as first arg at each call to the logger +******************************************************************************/ + if (nullptr==ctx) + ctx = pj_get_default_ctx (); + ctx->logger_app_data = app_data; + if (nullptr!=logf) + ctx->logger = logf; +} diff --git a/src/mlfn.hpp b/src/mlfn.hpp index 228c65a4..426dcb0e 100644 --- a/src/mlfn.hpp +++ b/src/mlfn.hpp @@ -66,7 +66,7 @@ inline_pj_inv_mlfn(PJ_CONTEXT *ctx, double arg, double es, const double *en, } *sinphi = s; *cosphi = c; - proj_context_errno_set( ctx, PJD_ERR_NON_CONV_INV_MERI_DIST ); + proj_context_errno_set( ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN ); return phi; } diff --git a/src/networkfilemanager.cpp b/src/networkfilemanager.cpp index 9edffaad..102d3d15 100644 --- a/src/networkfilemanager.cpp +++ b/src/networkfilemanager.cpp @@ -1315,7 +1315,7 @@ std::unique_ptr<File> NetworkFile::open(PJ_CONTEXT *ctx, const char *filename) { errorBuffer.resize(strlen(errorBuffer.data())); pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", filename, errorBuffer.c_str()); - proj_context_errno_set(ctx, PJD_ERR_NETWORK_ERROR); + proj_context_errno_set(ctx, PROJ_ERR_OTHER_NETWORK_ERROR); } bool ok = false; @@ -1404,7 +1404,7 @@ size_t NetworkFile::read(void *buffer, size_t sizeBytes) { &nRead, errorBuffer.size(), &errorBuffer[0], m_ctx->networking.user_data); if (!m_handle) { - proj_context_errno_set(m_ctx, PJD_ERR_NETWORK_ERROR); + proj_context_errno_set(m_ctx, PROJ_ERR_OTHER_NETWORK_ERROR); return 0; } } else { @@ -1420,7 +1420,7 @@ size_t NetworkFile::read(void *buffer, size_t sizeBytes) { pj_log(m_ctx, PJ_LOG_ERROR, "Cannot read in %s: %s", m_url.c_str(), errorBuffer.c_str()); } - proj_context_errno_set(m_ctx, PJD_ERR_NETWORK_ERROR); + proj_context_errno_set(m_ctx, PROJ_ERR_OTHER_NETWORK_ERROR); return 0; } diff --git a/src/param.cpp b/src/param.cpp index 28d6bc3e..a716f9b9 100644 --- a/src/param.cpp +++ b/src/param.cpp @@ -220,7 +220,7 @@ PROJVALUE pj_param (PJ_CONTEXT *ctx, paralist *pl, const char *opt) { value.i = 1; break; default: - proj_context_errno_set (ctx, PJD_ERR_INVALID_BOOLEAN_PARAM); + proj_context_errno_set (ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); value.i = 0; break; } diff --git a/src/phi2.cpp b/src/phi2.cpp index 2f258e47..23793557 100644 --- a/src/phi2.cpp +++ b/src/phi2.cpp @@ -103,7 +103,7 @@ double pj_sinhpsi2tanphi(PJ_CONTEXT *ctx, const double taup, const double e) { break; } if (i == 0) - proj_context_errno_set(ctx, PJD_ERR_NON_CONV_SINHPSI2TANPHI); + proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return tau; } diff --git a/src/pipeline.cpp b/src/pipeline.cpp index e9d11604..b1989824 100644 --- a/src/pipeline.cpp +++ b/src/pipeline.cpp @@ -96,7 +96,6 @@ Thomas Knudsen, thokn@sdfe.dk, 2016-05-20 #define PJ_LIB__ -#include <errno.h> #include <math.h> #include <stddef.h> #include <string.h> @@ -430,8 +429,8 @@ PJ *OPERATION(pipeline,0) { // proj=pipeline step "x="""," u=" proj=pipeline step ste=""[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline p step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" proj=pipeline step ""x=""""""""""" // Probably an issue with the quoting handling code // But doesn't hurt to add an extra safety check - proj_log_error (P, "Pipeline: too deep recursion"); - return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: nested pipelines */ + proj_log_error (P, _("Pipeline: too deep recursion")); + return destructor (P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); /* ERROR: nested pipelines */ } P->fwd4d = pipeline_forward_4d; @@ -453,24 +452,24 @@ PJ *OPERATION(pipeline,0) { P->opaque = new (std::nothrow) Pipeline(); if (nullptr==P->opaque) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_INVALID_OP /* ENOMEM */); argc = (int)argc_params (P->params); auto pipeline = static_cast<struct Pipeline*>(P->opaque); pipeline->argv = argv = argv_params (P->params, argc); if (nullptr==argv) - return destructor (P, ENOMEM); + return destructor (P, PROJ_ERR_INVALID_OP /* ENOMEM */); pipeline->current_argv = current_argv = static_cast<char**>(calloc (argc, sizeof (char *))); if (nullptr==current_argv) - return destructor (P, ENOMEM); + return destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); /* Do some syntactical sanity checking */ for (i = 0; i < argc; i++) { if (0==strcmp (argv_sentinel, argv[i])) { if (-1==i_pipeline) { - proj_log_error (P, "Pipeline: +step before +proj=pipeline"); - return destructor (P, PJD_ERR_MALFORMED_PIPELINE); + proj_log_error (P, _("Pipeline: +step before +proj=pipeline")); + return destructor (P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); } if (0==nsteps) i_first_step = i; @@ -480,8 +479,8 @@ PJ *OPERATION(pipeline,0) { if (0==strcmp ("proj=pipeline", argv[i])) { if (-1 != i_pipeline) { - proj_log_error (P, "Pipeline: Nesting only allowed when child pipelines are wrapped in '+init's"); - return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: nested pipelines */ + proj_log_error (P, _("Pipeline: Nesting only allowed when child pipelines are wrapped in '+init's")); + return destructor (P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); /* ERROR: nested pipelines */ } i_pipeline = i; } @@ -489,10 +488,10 @@ PJ *OPERATION(pipeline,0) { nsteps--; /* Last instance of +step is just a sentinel */ if (-1==i_pipeline) - return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: no pipeline def */ + return destructor (P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); /* ERROR: no pipeline def */ if (0==nsteps) - return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: no pipeline def */ + return destructor (P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); /* ERROR: no pipeline def */ set_ellipsoid(P); @@ -532,8 +531,8 @@ PJ *OPERATION(pipeline,0) { /* The step init failed, but possibly without setting errno. If so, we say "malformed" */ int err_to_report = proj_errno(P); if (0==err_to_report) - err_to_report = PJD_ERR_MALFORMED_PIPELINE; - proj_log_error (P, "Pipeline: Bad step definition: %s (%s)", current_argv[0], proj_errno_string (err_to_report)); + err_to_report = PROJ_ERR_INVALID_OP_WRONG_SYNTAX; + proj_log_error (P, _("Pipeline: Bad step definition: %s (%s)"), current_argv[0], proj_context_errno_string (P->ctx, err_to_report)); return destructor (P, err_to_report); /* ERROR: bad pipeline def */ } next_step->parent = P; @@ -562,8 +561,8 @@ PJ *OPERATION(pipeline,0) { (!Q->inverted && (Q->fwd || Q->fwd3d || Q->fwd4d) ) ) { continue; } else { - proj_log_error (P, "Pipeline: A forward operation couldn't be constructed"); - return destructor (P, PJD_ERR_MALFORMED_PIPELINE); + proj_log_error (P, _("Pipeline: A forward operation couldn't be constructed")); + return destructor (P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); } } @@ -612,8 +611,8 @@ PJ *OPERATION(pipeline,0) { continue; if ( curr_step_output != next_step_input ) { - proj_log_error (P, "Pipeline: Mismatched units between step %d and %d", i+1, i+2); - return destructor (P, PJD_ERR_MALFORMED_PIPELINE); + proj_log_error (P, _("Pipeline: Mismatched units between step %d and %d"), i+1, i+2); + return destructor (P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); } } @@ -682,7 +681,7 @@ static PJ *setup_pushpop(PJ *P) { auto pushpop = static_cast<struct PushPop*>(calloc (1, sizeof(struct PushPop))); P->opaque = pushpop; if (nullptr==P->opaque) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); if (pj_param_exists(P->params, "v_1")) pushpop->v1 = true; @@ -610,6 +610,33 @@ double PROJ_DLL proj_xyz_dist (PJ_COORD a, PJ_COORD b); /* Geodesic distance (in meter) + fwd and rev azimuth between two points on the ellipsoid */ PJ_COORD PROJ_DLL proj_geod (const PJ *P, PJ_COORD a, PJ_COORD b); +/* PROJ error codes */ + +/** Error codes typically related to coordinate operation initalization + * Note: some of them can also be emitted during coordinate transformation, + * like PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID in case the resource loading + * is differed until it is really needed. + */ +#define PROJ_ERR_INVALID_OP 1024 /* other/unspecified error related to coordinate operation initialization */ +#define PROJ_ERR_INVALID_OP_WRONG_SYNTAX (PROJ_ERR_INVALID_OP+1) /* invalid pipeline structure, missing +proj argument, etc */ +#define PROJ_ERR_INVALID_OP_MISSING_ARG (PROJ_ERR_INVALID_OP+2) /* missing required operation parameter */ +#define PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE (PROJ_ERR_INVALID_OP+3) /* one of the operation parameter has an illegal value */ +#define PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS (PROJ_ERR_INVALID_OP+4) /* mutually exclusive arguments */ +#define PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID (PROJ_ERR_INVALID_OP+5) /* file not found (particular case of PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE) */ + +/** Error codes related to transformation on a specific coordinate */ +#define PROJ_ERR_COORD_TRANSFM 2048 /* other error related to coordinate transformation */ +#define PROJ_ERR_COORD_TRANSFM_INVALID_COORD (PROJ_ERR_COORD_TRANSFM+1) /* for e.g lat > 90deg */ +#define PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN (PROJ_ERR_COORD_TRANSFM+2) /* coordinate is outside of the projection domain. e.g approximate mercator with |longitude - lon_0| > 90deg, or iterative convergence method failed */ +#define PROJ_ERR_COORD_TRANSFM_NO_OPERATION (PROJ_ERR_COORD_TRANSFM+3) /* no operation found, e.g if no match the required accuracy, or if ballpark transformations were asked to not be used and they would be only such candidate */ +#define PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID (PROJ_ERR_COORD_TRANSFM+4) /* point to transform falls outside grid or subgrid */ +#define PROJ_ERR_COORD_TRANSFM_GRID_AT_NODATA (PROJ_ERR_COORD_TRANSFM+5) /* point to transform falls in a grid cell that evaluates to nodata */ + +/** Other type of errors */ +#define PROJ_ERR_OTHER 4096 +#define PROJ_ERR_OTHER_API_MISUSE (PROJ_ERR_OTHER+1) /* error related to a misuse of PROJ API */ +#define PROJ_ERR_OTHER_NO_INVERSE_OP (PROJ_ERR_OTHER+2) /* no inverse method available */ +#define PROJ_ERR_OTHER_NETWORK_ERROR (PROJ_ERR_OTHER+3) /* failure when accessing a network resource */ /* Set or read error level */ int PROJ_DLL proj_context_errno (PJ_CONTEXT *ctx); @@ -617,7 +644,8 @@ int PROJ_DLL proj_errno (const PJ *P); int PROJ_DLL proj_errno_set (const PJ *P, int err); int PROJ_DLL proj_errno_reset (const PJ *P); int PROJ_DLL proj_errno_restore (const PJ *P, int err); -const char PROJ_DLL * proj_errno_string (int err); +const char PROJ_DLL * proj_errno_string (int err); /* deprecated. use proj_context_errno_string() */ +const char PROJ_DLL * proj_context_errno_string(PJ_CONTEXT* ctx, int err); PJ_LOG_LEVEL PROJ_DLL proj_log_level (PJ_CONTEXT *ctx, PJ_LOG_LEVEL log_level); void PROJ_DLL proj_log_func (PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf); diff --git a/src/proj_internal.h b/src/proj_internal.h index 32aaa1ec..795db036 100644 --- a/src/proj_internal.h +++ b/src/proj_internal.h @@ -189,7 +189,10 @@ PJ_COORD pj_inv4d (PJ_COORD coo, PJ *P); PJ_COORD PROJ_DLL pj_approx_2D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo); PJ_COORD PROJ_DLL pj_approx_3D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo); -void PROJ_DLL proj_log_error (PJ *P, const char *fmt, ...); +/* Provision for gettext translatable strings */ +#define _(str) (str) + +void PROJ_DLL proj_log_error (const PJ *P, const char *fmt, ...); void proj_log_debug (PJ *P, const char *fmt, ...); void proj_log_trace (PJ *P, const char *fmt, ...); @@ -351,6 +354,7 @@ struct PJconsts { **************************************************************************************/ PJ_CONTEXT *ctx = nullptr; + const char *short_name = nullptr; /* From pj_list.h */ const char *descr = nullptr; /* From pj_list.h or individual PJ_*.c file */ paralist *params = nullptr; /* Parameter list */ char *def_full = nullptr; /* Full textual definition (usually 0 - set by proj_pj_info) */ @@ -598,72 +602,6 @@ struct FACTORS { int code; /* always 0 */ }; -/* library errors */ -#define PJD_ERR_NO_ARGS -1 -#define PJD_ERR_NO_OPTION_IN_INIT_FILE -2 -#define PJD_ERR_NO_COLON_IN_INIT_STRING -3 -#define PJD_ERR_PROJ_NOT_NAMED -4 -#define PJD_ERR_UNKNOWN_PROJECTION_ID -5 -#define PJD_ERR_INVALID_ECCENTRICITY -6 -#define PJD_ERR_UNKNOWN_UNIT_ID -7 -#define PJD_ERR_INVALID_BOOLEAN_PARAM -8 -#define PJD_ERR_UNKNOWN_ELLP_PARAM -9 -#define PJD_ERR_REV_FLATTENING_IS_ZERO -10 -#define PJD_ERR_REF_RAD_LARGER_THAN_90 -11 -#define PJD_ERR_ES_LESS_THAN_ZERO -12 -#define PJD_ERR_MAJOR_AXIS_NOT_GIVEN -13 -#define PJD_ERR_LAT_OR_LON_EXCEED_LIMIT -14 -#define PJD_ERR_INVALID_X_OR_Y -15 -#define PJD_ERR_WRONG_FORMAT_DMS_VALUE -16 -#define PJD_ERR_NON_CONV_INV_MERI_DIST -17 -#define PJD_ERR_NON_CONV_SINHPSI2TANPHI -18 -#define PJD_ERR_ACOS_ASIN_ARG_TOO_LARGE -19 -#define PJD_ERR_TOLERANCE_CONDITION -20 -#define PJD_ERR_CONIC_LAT_EQUAL -21 -#define PJD_ERR_LAT_LARGER_THAN_90 -22 -#define PJD_ERR_LAT1_IS_ZERO -23 -#define PJD_ERR_LAT_TS_LARGER_THAN_90 -24 -#define PJD_ERR_CONTROL_POINT_NO_DIST -25 -#define PJD_ERR_NO_ROTATION_PROJ -26 -#define PJD_ERR_W_OR_M_ZERO_OR_LESS -27 -#define PJD_ERR_LSAT_NOT_IN_RANGE -28 -#define PJD_ERR_PATH_NOT_IN_RANGE -29 -#define PJD_ERR_INVALID_H -30 -#define PJD_ERR_K_LESS_THAN_ZERO -31 -#define PJD_ERR_LAT_1_OR_2_ZERO_OR_90 -32 -#define PJD_ERR_LAT_0_OR_ALPHA_EQ_90 -33 -#define PJD_ERR_ELLIPSOID_USE_REQUIRED -34 -#define PJD_ERR_INVALID_UTM_ZONE -35 -/* -36 no longer used */ -#define PJD_ERR_FAILED_TO_FIND_PROJ -37 -#define PJD_ERR_FAILED_TO_LOAD_GRID -38 -#define PJD_ERR_INVALID_M_OR_N -39 -#define PJD_ERR_N_OUT_OF_RANGE -40 -#define PJD_ERR_LAT_1_2_UNSPECIFIED -41 -#define PJD_ERR_ABS_LAT1_EQ_ABS_LAT2 -42 -#define PJD_ERR_LAT_0_HALF_PI_FROM_MEAN -43 -#define PJD_ERR_UNPARSEABLE_CS_DEF -44 -#define PJD_ERR_GEOCENTRIC -45 -#define PJD_ERR_UNKNOWN_PRIME_MERIDIAN -46 -#define PJD_ERR_AXIS -47 -#define PJD_ERR_GRID_AREA -48 -#define PJD_ERR_INVALID_SWEEP_AXIS -49 -#define PJD_ERR_MALFORMED_PIPELINE -50 -#define PJD_ERR_UNIT_FACTOR_LESS_THAN_0 -51 -#define PJD_ERR_INVALID_SCALE -52 -#define PJD_ERR_NON_CONVERGENT -53 -#define PJD_ERR_MISSING_ARGS -54 -#define PJD_ERR_LAT_0_IS_ZERO -55 -#define PJD_ERR_ELLIPSOIDAL_UNSUPPORTED -56 -#define PJD_ERR_TOO_MANY_INITS -57 -#define PJD_ERR_INVALID_ARG -58 -#define PJD_ERR_INCONSISTENT_UNIT -59 -#define PJD_ERR_MUTUALLY_EXCLUSIVE_ARGS -60 -#define PJD_ERR_GENERIC_ERROR -61 -#define PJD_ERR_NETWORK_ERROR -62 -/* NOTE: Remember to update src/strerrno.cpp, src/apps/gie.cpp and transient_error in */ -/* src/transform.cpp when adding new value */ - // Legacy struct projFileAPI_t; @@ -707,6 +645,7 @@ struct projFileApiCallbackAndData /* proj thread context */ struct pj_ctx{ + std::string lastFullErrorMessage{}; // used by proj_context_errno_string int last_errno = 0; int debug_level = 0; void (*logger)(void *, int, const char *) = nullptr; @@ -781,6 +720,7 @@ C_NAMESPACE PJ *pj_##name (PJ *P) { \ P = pj_new(); \ if (nullptr==P) \ return nullptr; \ + P->short_name = #name; \ P->descr = des_##name; \ P->need_ellps = NEED_ELLPS; \ P->left = PJ_IO_UNITS_RADIANS; \ @@ -922,15 +862,6 @@ PJ_LP pj_generic_inverse_2d(PJ_XY xy, PJ *P, PJ_LP lpInitial); extern char const PROJ_DLL pj_release[]; /* global release id string */ -#ifndef PROJ_INTERNAL_H -/* replaced by enum proj_log_level in proj_internal.h */ -#define PJ_LOG_NONE 0 -#define PJ_LOG_ERROR 1 -#define PJ_LOG_DEBUG_MAJOR 2 -#define PJ_LOG_DEBUG_MINOR 3 -#endif - - /* procedure prototypes */ PJ_CONTEXT PROJ_DLL *pj_get_default_ctx(void); diff --git a/src/proj_mdist.cpp b/src/proj_mdist.cpp index ed07ffd1..9a819861 100644 --- a/src/proj_mdist.cpp +++ b/src/proj_mdist.cpp @@ -124,6 +124,6 @@ proj_inv_mdist(PJ_CONTEXT *ctx, double dist, const void *data) { return phi; } /* convergence failed */ - proj_context_errno_set(ctx, PJD_ERR_NON_CONV_INV_MERI_DIST); + proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return phi; } diff --git a/src/projections/adams.cpp b/src/projections/adams.cpp index d1217ff1..389ca054 100644 --- a/src/projections/adams.cpp +++ b/src/projections/adams.cpp @@ -99,7 +99,7 @@ static PJ_XY adams_forward(PJ_LP lp, PJ *P) { switch (Q->mode) { case GUYOU: if ((fabs(lp.lam) - TOL) > M_PI_2) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } @@ -119,7 +119,7 @@ static PJ_XY adams_forward(PJ_LP lp, PJ *P) { break; case PEIRCE_Q: { if( lp.phi < -TOL ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } const double sl = sin(lp.lam); @@ -134,7 +134,7 @@ static PJ_XY adams_forward(PJ_LP lp, PJ *P) { case ADAMS_HEMI: { const double sp = sin(lp.phi); if ((fabs(lp.lam) - TOL) > M_PI_2) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } a = cos(lp.phi) * sin(lp.lam); @@ -208,7 +208,7 @@ static PJ *setup(PJ *P, projection_type mode) { calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->es = 0; diff --git a/src/projections/aea.cpp b/src/projections/aea.cpp index af0f292d..4cbbfb56 100644 --- a/src/projections/aea.cpp +++ b/src/projections/aea.cpp @@ -107,7 +107,7 @@ static PJ_XY aea_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoid/spheroid, forward struct pj_opaque *Q = static_cast<struct pj_opaque*>(P->opaque); Q->rho = Q->c - (Q->ellips ? Q->n * pj_qsfn(sin(lp.phi), P->e, P->one_es) : Q->n2 * sin(lp.phi));; if (Q->rho < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } Q->rho = Q->dd * sqrt(Q->rho); @@ -134,12 +134,12 @@ static PJ_LP aea_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoid/spheroid, inverse lp.phi = (Q->c - lp.phi * lp.phi) / Q->n; if (fabs(Q->ec - fabs(lp.phi)) > TOL7) { if (fabs(lp.phi) > 2 ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } lp.phi = phi1_(lp.phi, P->e, P->one_es); if (lp.phi == HUGE_VAL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } } else @@ -167,10 +167,21 @@ static PJ *setup(PJ *P) { P->inv = aea_e_inverse; P->fwd = aea_e_forward; - if (fabs(Q->phi1) > M_HALFPI || fabs(Q->phi2) > M_HALFPI) - return destructor(P, PJD_ERR_LAT_LARGER_THAN_90); + if (fabs(Q->phi1) > M_HALFPI) + { + proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be <= 90°")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + if (fabs(Q->phi2) > M_HALFPI) + { + proj_log_error(P, _("Invalid value for lat_2: |lat_2| should be <= 90°")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } if (fabs(Q->phi1 + Q->phi2) < EPS10) - return destructor(P, PJD_ERR_CONIC_LAT_EQUAL); + { + proj_log_error(P, _("Invalid value for lat_1 and lat_2: |lat_1 + lat_2| should be > 0")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } double sinphi = sin(Q->phi1); Q->n = sinphi; double cosphi = cos(Q->phi1); @@ -197,7 +208,8 @@ static PJ *setup(PJ *P) { Q->n = (m1 * m1 - m2 * m2) / (ml2 - ml1); if (Q->n == 0) { // Not quite, but es is very close to 1... - return destructor(P, PJD_ERR_INVALID_ECCENTRICITY); + proj_log_error(P, _("Invalid value for eccentricity")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } Q->ec = 1. - .5 * P->one_es * log((1. - P->e) / @@ -221,7 +233,7 @@ static PJ *setup(PJ *P) { PJ *PROJECTION(aea) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; @@ -234,7 +246,7 @@ PJ *PROJECTION(aea) { PJ *PROJECTION(leac) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; diff --git a/src/projections/aeqd.cpp b/src/projections/aeqd.cpp index d5d90b62..e6443858 100644 --- a/src/projections/aeqd.cpp +++ b/src/projections/aeqd.cpp @@ -149,7 +149,7 @@ static PJ_XY aeqd_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward oblcon: if (fabs(fabs(xy.y) - 1.) < TOL) if (xy.y < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } else @@ -168,7 +168,7 @@ oblcon: /*-fallthrough*/ case S_POLE: if (fabs(lp.phi - M_HALFPI) < EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.y = (M_HALFPI + lp.phi); @@ -239,7 +239,7 @@ static PJ_LP aeqd_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse c_rh = hypot(xy.x, xy.y); if (c_rh > M_PI) { if (c_rh - EPS10 > M_PI) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } c_rh = M_PI; @@ -276,7 +276,7 @@ static PJ_LP aeqd_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse PJ *PROJECTION(aeqd) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; diff --git a/src/projections/airy.cpp b/src/projections/airy.cpp index 15ff60d8..ead2c086 100644 --- a/src/projections/airy.cpp +++ b/src/projections/airy.cpp @@ -74,14 +74,14 @@ static PJ_XY airy_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward if (Q->mode == OBLIQ) cosz = Q->sinph0 * sinphi + Q->cosph0 * cosz; if (!Q->no_cut && cosz < -EPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } s = 1. - cosz; if (fabs(s) > EPS) { t = 0.5 * (1. + cosz); if(t == 0) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } Krho = -log(t)/s - Q->Cb / t; @@ -97,7 +97,7 @@ static PJ_XY airy_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward case N_POLE: lp.phi = fabs(Q->p_halfpi - lp.phi); if (!Q->no_cut && (lp.phi - EPS) > M_HALFPI) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } lp.phi *= 0.5; @@ -122,7 +122,7 @@ PJ *PROJECTION(airy) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; diff --git a/src/projections/aitoff.cpp b/src/projections/aitoff.cpp index 857ebb80..68f0c2da 100644 --- a/src/projections/aitoff.cpp +++ b/src/projections/aitoff.cpp @@ -123,7 +123,7 @@ static PJ_LP aitoff_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver C = 1. - D * D; const double denom = pow(C, 1.5); if( denom == 0 ) { - proj_errno_set(P, PJD_ERR_NON_CONVERGENT); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } D = acos(D) / denom; @@ -170,7 +170,7 @@ static PJ_LP aitoff_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver if (iter == MAXITER && round == MAXROUND) { - proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT ); + proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN ); /* fprintf(stderr, "Warning: Accuracy of 1e-12 not reached. Last increments: dlat=%e and dlon=%e\n", dp, dl); */ } @@ -189,7 +189,7 @@ static PJ *setup(PJ *P) { PJ *PROJECTION(aitoff) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->mode = AITOFF; @@ -200,13 +200,16 @@ PJ *PROJECTION(aitoff) { PJ *PROJECTION(wintri) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->mode = WINKEL_TRIPEL; if (pj_param(P->ctx, P->params, "tlat_1").i) { if ((Q->cosphi1 = cos(pj_param(P->ctx, P->params, "rlat_1").f)) == 0.) - return pj_default_destructor (P, PJD_ERR_LAT_LARGER_THAN_90); + { + proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } } else /* 50d28' or acos(2/pi) */ Q->cosphi1 = 0.636619772367581343; diff --git a/src/projections/bacon.cpp b/src/projections/bacon.cpp index 7ff2a7ac..bc069a2c 100644 --- a/src/projections/bacon.cpp +++ b/src/projections/bacon.cpp @@ -45,7 +45,7 @@ static PJ_XY bacon_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwar PJ *PROJECTION(bacon) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->bacn = 1; @@ -59,7 +59,7 @@ PJ *PROJECTION(bacon) { PJ *PROJECTION(apian) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->bacn = Q->ortl = 0; @@ -72,7 +72,7 @@ PJ *PROJECTION(apian) { PJ *PROJECTION(ortel) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->bacn = 0; diff --git a/src/projections/bertin1953.cpp b/src/projections/bertin1953.cpp index 58509e16..512384b4 100644 --- a/src/projections/bertin1953.cpp +++ b/src/projections/bertin1953.cpp @@ -77,7 +77,7 @@ static PJ_XY bertin1953_s_forward (PJ_LP lp, PJ *P) { PJ *PROJECTION(bertin1953) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->lam0 = 0; diff --git a/src/projections/bipc.cpp b/src/projections/bipc.cpp index 743acd1c..0071b4b5 100644 --- a/src/projections/bipc.cpp +++ b/src/projections/bipc.cpp @@ -60,7 +60,7 @@ static PJ_XY bipc_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward z = S20 * sphi + C20 * cphi * cdlam; if (fabs(z) > 1.) { if (fabs(z) > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } else z = z < 0. ? -1. : 1.; @@ -74,7 +74,7 @@ static PJ_XY bipc_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward z = S45 * (sphi + cphi * cdlam); if (fabs(z) > 1.) { if (fabs(z) > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } else z = z < 0. ? -1. : 1.; @@ -84,19 +84,19 @@ static PJ_XY bipc_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward xy.y = -rhoc; } if (z < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } t = pow(tan(.5 * z), n); r = F * t; if ((al = .5 * (R104 - z)) < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } al = (t + pow(al, n)) / T; if (fabs(al) > 1.) { if (fabs(al) > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } else al = al < 0. ? -1. : 1.; @@ -153,7 +153,7 @@ static PJ_LP bipc_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse rl = r; } if (! i) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } Az = Av - Az / n; @@ -170,7 +170,7 @@ static PJ_LP bipc_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse PJ *PROJECTION(bipc) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->noskew = pj_param(P->ctx, P->params, "bns").i; diff --git a/src/projections/bonne.cpp b/src/projections/bonne.cpp index 7817e968..6923409b 100644 --- a/src/projections/bonne.cpp +++ b/src/projections/bonne.cpp @@ -65,7 +65,7 @@ static PJ_LP bonne_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers rh = hypot(xy.x, xy.y); lp.phi = Q->cphi1 + Q->phi1 - rh; if (fabs(lp.phi) > M_HALFPI) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) @@ -91,7 +91,7 @@ static PJ_LP bonne_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, invers } else if (fabs(s - M_HALFPI) <= EPS10) lp.lam = 0.; else { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } return lp; @@ -115,18 +115,21 @@ PJ *PROJECTION(bonne) { double c; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; if (fabs(Q->phi1) < EPS10) - return destructor (P, PJD_ERR_LAT1_IS_ZERO); + { + proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be > 0")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } if (P->es != 0.0) { Q->en = pj_enfn(P->es); if (nullptr==Q->en) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->am1 = sin(Q->phi1); c = cos(Q->phi1); Q->m1 = pj_mlfn(Q->phi1, Q->am1, c, Q->en); diff --git a/src/projections/calcofi.cpp b/src/projections/calcofi.cpp index d1e96de8..b9528ab9 100644 --- a/src/projections/calcofi.cpp +++ b/src/projections/calcofi.cpp @@ -44,7 +44,7 @@ static PJ_XY calcofi_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, forw line as xy xy, r, o form a right triangle */ if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } @@ -73,7 +73,7 @@ static PJ_XY calcofi_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forw double l2; double ry; if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.x = lp.lam; diff --git a/src/projections/cass.cpp b/src/projections/cass.cpp index f5531f6a..a7fb48be 100644 --- a/src/projections/cass.cpp +++ b/src/projections/cass.cpp @@ -113,12 +113,12 @@ PJ *PROJECTION(cass) { /* otherwise it's ellipsoidal */ P->opaque = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==P->opaque) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->destructor = destructor; static_cast<struct pj_opaque*>(P->opaque)->en = pj_enfn (P->es); if (nullptr==static_cast<struct pj_opaque*>(P->opaque)->en) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); static_cast<struct pj_opaque*>(P->opaque)->m0 = pj_mlfn (P->phi0, sin (P->phi0), cos (P->phi0), static_cast<struct pj_opaque*>(P->opaque)->en); P->inv = cass_e_inverse; diff --git a/src/projections/cc.cpp b/src/projections/cc.cpp index 244e185d..f15a95bd 100644 --- a/src/projections/cc.cpp +++ b/src/projections/cc.cpp @@ -12,7 +12,7 @@ PROJ_HEAD(cc, "Central Cylindrical") "\n\tCyl, Sph"; static PJ_XY cc_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0,0.0}; if (fabs (fabs(lp.phi) - M_HALFPI) <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.x = lp.lam; diff --git a/src/projections/ccon.cpp b/src/projections/ccon.cpp index 7b3b7105..dc0b0ea4 100644 --- a/src/projections/ccon.cpp +++ b/src/projections/ccon.cpp @@ -84,16 +84,18 @@ PJ *PROJECTION(ccon) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; if (fabs(Q->phi1) < EPS10) - return destructor (P, PJD_ERR_LAT1_IS_ZERO); - + { + proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be > 0")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } if (!(Q->en = pj_enfn(P->es))) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->sinphi1 = sin(Q->phi1); Q->cosphi1 = cos(Q->phi1); diff --git a/src/projections/cea.cpp b/src/projections/cea.cpp index b7874b90..e26a14f3 100644 --- a/src/projections/cea.cpp +++ b/src/projections/cea.cpp @@ -53,7 +53,7 @@ static PJ_LP cea_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse lp.phi = asin(xy.y); lp.lam = xy.x / P->k0; } else { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } return (lp); @@ -75,15 +75,19 @@ PJ *PROJECTION(cea) { double t = 0.0; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; if (pj_param(P->ctx, P->params, "tlat_ts").i) { - P->k0 = cos(t = pj_param(P->ctx, P->params, "rlat_ts").f); + t = pj_param(P->ctx, P->params, "rlat_ts").f; + P->k0 = cos(t); if (P->k0 < 0.) - return pj_default_destructor (P, PJD_ERR_LAT_TS_LARGER_THAN_90); + { + proj_log_error(P, _("Invalid value for lat_ts: |lat_ts| should be <= 90°")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } } if (P->es != 0.0) { t = sin(t); @@ -91,7 +95,7 @@ PJ *PROJECTION(cea) { P->e = sqrt(P->es); Q->apa = pj_authset(P->es); if (!(Q->apa)) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->qp = pj_qsfn(1., P->e, P->one_es); P->inv = cea_e_inverse; diff --git a/src/projections/chamb.cpp b/src/projections/chamb.cpp index b315832a..1050dad3 100644 --- a/src/projections/chamb.cpp +++ b/src/projections/chamb.cpp @@ -105,7 +105,7 @@ PJ *PROJECTION(chamb) { char line[10]; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; @@ -123,7 +123,10 @@ PJ *PROJECTION(chamb) { Q->c[i].v = vect(P->ctx,Q->c[j].phi - Q->c[i].phi, Q->c[i].cosphi, Q->c[i].sinphi, Q->c[j].cosphi, Q->c[j].sinphi, Q->c[j].lam - Q->c[i].lam); if (Q->c[i].v.r == 0.0) - return pj_default_destructor (P, PJD_ERR_CONTROL_POINT_NO_DIST); + { + proj_log_error(P, _("Invalid value for control points: they should be distinct")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } /* co-linearity problem ignored for now */ } Q->beta_0 = lc(P->ctx,Q->c[0].v.r, Q->c[2].v.r, Q->c[1].v.r); diff --git a/src/projections/col_urban.cpp b/src/projections/col_urban.cpp index de0c178f..ffeb1785 100644 --- a/src/projections/col_urban.cpp +++ b/src/projections/col_urban.cpp @@ -56,7 +56,7 @@ static PJ_LP col_urban_inverse (PJ_XY xy, PJ *P) { PJ *PROJECTION(col_urban) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; const double h0_unscaled = pj_param(P->ctx, P->params, "dh_0").f; diff --git a/src/projections/collg.cpp b/src/projections/collg.cpp index 1b9d2da7..958dfddb 100644 --- a/src/projections/collg.cpp +++ b/src/projections/collg.cpp @@ -32,7 +32,7 @@ static PJ_LP collg_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers if (fabs(lp.phi) < 1.) lp.phi = asin(lp.phi); else if (fabs(lp.phi) > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; diff --git a/src/projections/comill.cpp b/src/projections/comill.cpp index 44524990..910d8aa7 100644 --- a/src/projections/comill.cpp +++ b/src/projections/comill.cpp @@ -66,7 +66,7 @@ static PJ_LP comill_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver } } if( i == 0 ) - proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT ); + proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN ); lp.phi = yc; /* longitude */ diff --git a/src/projections/eck2.cpp b/src/projections/eck2.cpp index 0d9fd5fa..891ad30e 100644 --- a/src/projections/eck2.cpp +++ b/src/projections/eck2.cpp @@ -34,7 +34,7 @@ static PJ_LP eck2_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse lp.phi = (4. - lp.phi * lp.phi) * C13; if (fabs(lp.phi) >= 1.) { if (fabs(lp.phi) > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; diff --git a/src/projections/eck3.cpp b/src/projections/eck3.cpp index 2563053f..ff1f4c5b 100644 --- a/src/projections/eck3.cpp +++ b/src/projections/eck3.cpp @@ -54,7 +54,7 @@ static PJ *setup(PJ *P) { PJ *PROJECTION(eck3) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->C_x = 0.42223820031577120149; @@ -69,7 +69,7 @@ PJ *PROJECTION(eck3) { PJ *PROJECTION(kav7) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; /* Defined twice in original code - Using 0.866..., @@ -87,7 +87,7 @@ PJ *PROJECTION(kav7) { PJ *PROJECTION(wag6) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->C_x = 0.94745; @@ -102,7 +102,7 @@ PJ *PROJECTION(wag6) { PJ *PROJECTION(putp1) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->C_x = 1.89490; diff --git a/src/projections/eqc.cpp b/src/projections/eqc.cpp index 9ebc9286..0e696879 100644 --- a/src/projections/eqc.cpp +++ b/src/projections/eqc.cpp @@ -41,11 +41,14 @@ static PJ_LP eqc_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse PJ *PROJECTION(eqc) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if ((Q->rc = cos(pj_param(P->ctx, P->params, "rlat_ts").f)) <= 0.) - return pj_default_destructor (P, PJD_ERR_LAT_TS_LARGER_THAN_90); + { + proj_log_error(P, _("Invalid value for lat_ts: |lat_ts| should be <= 90°")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } P->inv = eqc_s_inverse; P->fwd = eqc_s_forward; P->es = 0.; diff --git a/src/projections/eqdc.cpp b/src/projections/eqdc.cpp index 28767d74..4407ab62 100644 --- a/src/projections/eqdc.cpp +++ b/src/projections/eqdc.cpp @@ -79,20 +79,32 @@ PJ *PROJECTION(eqdc) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; Q->phi2 = pj_param(P->ctx, P->params, "rlat_2").f; - if (fabs(Q->phi1) > M_HALFPI || fabs(Q->phi2) > M_HALFPI) - return destructor(P, PJD_ERR_LAT_LARGER_THAN_90); + if (fabs(Q->phi1) > M_HALFPI) + { + proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be <= 90°")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + + if (fabs(Q->phi2) > M_HALFPI) + { + proj_log_error(P, _("Invalid value for lat_2: |lat_2| should be <= 90°")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } if (fabs(Q->phi1 + Q->phi2) < EPS10) - return destructor (P, PJD_ERR_CONIC_LAT_EQUAL); + { + proj_log_error(P, _("Invalid value for lat_1 and lat_2: |lat_1 + lat_2| should be > 0")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } if (!(Q->en = pj_enfn(P->es))) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); sinphi = sin(Q->phi1); Q->n = sinphi; @@ -111,7 +123,8 @@ PJ *PROJECTION(eqdc) { (pj_mlfn(Q->phi2, sinphi, cosphi, Q->en) - ml1); if (Q->n == 0) { // Not quite, but es is very close to 1... - return destructor(P, PJD_ERR_INVALID_ECCENTRICITY); + proj_log_error(P, _("Invalid value for eccentricity")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } Q->c = ml1 + m1 / Q->n; @@ -121,7 +134,10 @@ PJ *PROJECTION(eqdc) { if (secant) Q->n = (cosphi - cos(Q->phi2)) / (Q->phi2 - Q->phi1); if (Q->n == 0) - return destructor (P, PJD_ERR_CONIC_LAT_EQUAL); + { + proj_log_error(P, _("Invalid value for lat_1 and lat_2: lat_1 + lat_2 should be > 0")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } Q->c = Q->phi1 + cos(Q->phi1) / Q->n; Q->rho0 = Q->c - P->phi0; } diff --git a/src/projections/eqearth.cpp b/src/projections/eqearth.cpp index 2ef2775b..2b9ee468 100644 --- a/src/projections/eqearth.cpp +++ b/src/projections/eqearth.cpp @@ -110,7 +110,7 @@ static PJ_LP eqearth_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal/sphe } if( i == 0 ) { - proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT ); + proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN ); return lp; } @@ -145,7 +145,7 @@ static PJ *destructor (PJ *P, int errlev) { /* Destructor */ PJ *PROJECTION(eqearth) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; P->fwd = eqearth_e_forward; @@ -156,7 +156,7 @@ PJ *PROJECTION(eqearth) { if (P->es != 0.0) { Q->apa = pj_authset(P->es); /* For auth_lat(). */ if (nullptr == Q->apa) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->qp = pj_qsfn(1.0, P->e, P->one_es); /* For auth_lat(). */ Q->rqda = sqrt(0.5*Q->qp); /* Authalic radius divided by major axis */ } diff --git a/src/projections/fouc_s.cpp b/src/projections/fouc_s.cpp index f7607635..ba6e917d 100644 --- a/src/projections/fouc_s.cpp +++ b/src/projections/fouc_s.cpp @@ -57,12 +57,15 @@ static PJ_LP fouc_s_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver PJ *PROJECTION(fouc_s) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->n = pj_param(P->ctx, P->params, "dn").f; if (Q->n < 0. || Q->n > 1.) - return pj_default_destructor (P, PJD_ERR_N_OUT_OF_RANGE); + { + proj_log_error(P, _("Invalid value for n: it should be in [0,1] range.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } Q->n1 = 1. - Q->n; P->es = 0; diff --git a/src/projections/geos.cpp b/src/projections/geos.cpp index 5de4c7ca..528ac4a5 100644 --- a/src/projections/geos.cpp +++ b/src/projections/geos.cpp @@ -99,7 +99,7 @@ static PJ_XY geos_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, forward /* Check visibility. */ if (((Q->radius_g - Vx) * Vx - Vy * Vy - Vz * Vz * Q->radius_p_inv2) < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } @@ -138,7 +138,7 @@ static PJ_LP geos_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse b = 2 * Q->radius_g * Vx; const double det = (b * b) - 4 * a * Q->C; if (det < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } @@ -178,7 +178,7 @@ static PJ_LP geos_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse b = 2 * Q->radius_g * Vx; const double det = (b * b) - 4 * a * Q->C; if (det < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } @@ -201,7 +201,7 @@ PJ *PROJECTION(geos) { char *sweep_axis; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->h = pj_param(P->ctx, P->params, "dh").f; @@ -212,7 +212,10 @@ PJ *PROJECTION(geos) { else { if ((sweep_axis[0] != 'x' && sweep_axis[0] != 'y') || sweep_axis[1] != '\0') - return pj_default_destructor (P, PJD_ERR_INVALID_SWEEP_AXIS); + { + proj_log_error(P, _("Invalid value for sweep: it should be equal to x or y.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } if (sweep_axis[0] == 'x') Q->flip_axis = 1; @@ -222,7 +225,10 @@ PJ *PROJECTION(geos) { Q->radius_g_1 = Q->h / P->a; if ( Q->radius_g_1 <= 0 || Q->radius_g_1 > 1e10 ) - return pj_default_destructor (P, PJD_ERR_INVALID_H); + { + proj_log_error(P, _("Invalid value for h.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } Q->radius_g = 1. + Q->radius_g_1; Q->C = Q->radius_g * Q->radius_g - 1.0; if (P->es != 0.0) { diff --git a/src/projections/gn_sinu.cpp b/src/projections/gn_sinu.cpp index ef312613..863613b6 100644 --- a/src/projections/gn_sinu.cpp +++ b/src/projections/gn_sinu.cpp @@ -46,7 +46,7 @@ static PJ_LP gn_sinu_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inve } else if ((s - EPS10) < M_HALFPI) { lp.lam = 0.; } else { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); } return lp; @@ -71,7 +71,7 @@ static PJ_XY gn_sinu_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forw break; } if (!i) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } @@ -123,12 +123,12 @@ static void setup(PJ *P) { PJ *PROJECTION(sinu) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; if (!(Q->en = pj_enfn(P->es))) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); if (P->es != 0.0) { P->inv = gn_sinu_e_inverse; @@ -145,7 +145,7 @@ PJ *PROJECTION(sinu) { PJ *PROJECTION(eck6) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; @@ -160,7 +160,7 @@ PJ *PROJECTION(eck6) { PJ *PROJECTION(mbtfps) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; @@ -175,17 +175,33 @@ PJ *PROJECTION(mbtfps) { PJ *PROJECTION(gn_sinu) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; - if (pj_param(P->ctx, P->params, "tn").i && pj_param(P->ctx, P->params, "tm").i) { - Q->n = pj_param(P->ctx, P->params, "dn").f; - Q->m = pj_param(P->ctx, P->params, "dm").f; - if (Q->n <= 0 || Q->m < 0) - return destructor (P, PJD_ERR_INVALID_M_OR_N); - } else - return destructor (P, PJD_ERR_INVALID_M_OR_N); + if (!pj_param(P->ctx, P->params, "tn").i ) + { + proj_log_error(P, _("Missing parameter n.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); + } + if (!pj_param(P->ctx, P->params, "tm").i ) + { + proj_log_error(P, _("Missing parameter m.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); + } + + Q->n = pj_param(P->ctx, P->params, "dn").f; + Q->m = pj_param(P->ctx, P->params, "dm").f; + if (Q->n <= 0) + { + proj_log_error(P, _("Invalid value for n: it should be > 0.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + if (Q->m < 0) + { + proj_log_error(P, _("Invalid value for m: it should be >= 0.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } setup(P); diff --git a/src/projections/gnom.cpp b/src/projections/gnom.cpp index 9abbb7ce..6257008d 100644 --- a/src/projections/gnom.cpp +++ b/src/projections/gnom.cpp @@ -54,7 +54,7 @@ static PJ_XY gnom_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward } if (xy.y <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } @@ -126,7 +126,7 @@ static PJ_LP gnom_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse PJ *PROJECTION(gnom) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) { diff --git a/src/projections/goode.cpp b/src/projections/goode.cpp index c0afd2d8..6729820b 100644 --- a/src/projections/goode.cpp +++ b/src/projections/goode.cpp @@ -64,7 +64,7 @@ static PJ *destructor (PJ *P, int errlev) { /* Destructor */ PJ *PROJECTION(goode) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; @@ -72,14 +72,14 @@ PJ *PROJECTION(goode) { Q->sinu = pj_sinu(nullptr); Q->moll = pj_moll(nullptr); if (Q->sinu == nullptr || Q->moll == nullptr) - return destructor (P, ENOMEM); + return destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); Q->sinu->es = 0.; Q->sinu->ctx = P->ctx; Q->moll->ctx = P->ctx; Q->sinu = pj_sinu(Q->sinu); Q->moll = pj_moll(Q->moll); if (Q->sinu == nullptr || Q->moll == nullptr) - return destructor (P, ENOMEM); + return destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->fwd = goode_s_forward; P->inv = goode_s_inverse; diff --git a/src/projections/gstmerc.cpp b/src/projections/gstmerc.cpp index b21f6ffd..ea41a33a 100644 --- a/src/projections/gstmerc.cpp +++ b/src/projections/gstmerc.cpp @@ -56,7 +56,7 @@ static PJ_LP gstmerc_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inve PJ *PROJECTION(gstmerc) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->lamc = P->lam0; diff --git a/src/projections/hammer.cpp b/src/projections/hammer.cpp index d9bcafc7..b66b757b 100644 --- a/src/projections/hammer.cpp +++ b/src/projections/hammer.cpp @@ -28,7 +28,7 @@ static PJ_XY hammer_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwa lp.lam *= Q->w; double denom = 1. + cosphi * cos(lp.lam); if( denom == 0.0 ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } d = sqrt(2./denom); @@ -47,7 +47,7 @@ static PJ_LP hammer_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver if (fabs(2.*z*z-1.) < EPS) { lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; - proj_errno_set(P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); } else { lp.lam = aatan2(Q->w * xy.x * z,2. * z * z - 1)/Q->w; lp.phi = aasin(P->ctx,z * xy.y); @@ -59,19 +59,25 @@ static PJ_LP hammer_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver PJ *PROJECTION(hammer) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (pj_param(P->ctx, P->params, "tW").i) { Q->w = fabs(pj_param(P->ctx, P->params, "dW").f); if (Q->w <= 0.) - return pj_default_destructor (P, PJD_ERR_W_OR_M_ZERO_OR_LESS); + { + proj_log_error(P, _("Invalid value for W: it should be > 0")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } } else Q->w = .5; if (pj_param(P->ctx, P->params, "tM").i) { Q->m = fabs(pj_param(P->ctx, P->params, "dM").f); if (Q->m <= 0.) - return pj_default_destructor (P, PJD_ERR_W_OR_M_ZERO_OR_LESS); + { + proj_log_error(P, _("Invalid value for M: it should be > 0")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } } else Q->m = 1.; diff --git a/src/projections/hatano.cpp b/src/projections/hatano.cpp index c10c4e35..4897435f 100644 --- a/src/projections/hatano.cpp +++ b/src/projections/hatano.cpp @@ -47,7 +47,7 @@ static PJ_LP hatano_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver th = xy.y * ( xy.y < 0. ? RYCS : RYCN); if (fabs(th) > 1.) { if (fabs(th) > ONETOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { th = th > 0. ? M_HALFPI : - M_HALFPI; @@ -61,7 +61,7 @@ static PJ_LP hatano_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver lp.phi = (th + sin(th)) * (xy.y < 0. ? RCS : RCN); if (fabs(lp.phi) > 1.) { if (fabs(lp.phi) > ONETOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { lp.phi = lp.phi > 0. ? M_HALFPI : - M_HALFPI; diff --git a/src/projections/healpix.cpp b/src/projections/healpix.cpp index c778f28f..0340f7dd 100644 --- a/src/projections/healpix.cpp +++ b/src/projections/healpix.cpp @@ -524,7 +524,7 @@ static PJ_LP s_healpix_inverse(PJ_XY xy, PJ *P) { /* sphere */ PJ_LP lp; lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; - proj_context_errno_set(P->ctx, PJD_ERR_INVALID_X_OR_Y); + proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } return healpix_spherhealpix_e_inverse(xy); @@ -540,7 +540,7 @@ static PJ_LP e_healpix_inverse(PJ_XY xy, PJ *P) { /* ellipsoid */ if (in_image(xy.x, xy.y, 0, 0, 0) == 0) { lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; - proj_context_errno_set(P->ctx, PJD_ERR_INVALID_X_OR_Y); + proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } lp = healpix_spherhealpix_e_inverse(xy); @@ -574,7 +574,7 @@ static PJ_LP s_rhealpix_inverse(PJ_XY xy, PJ *P) { /* sphere */ PJ_LP lp; lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; - proj_context_errno_set(P->ctx, PJD_ERR_INVALID_X_OR_Y); + proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } xy = combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 1); @@ -590,7 +590,7 @@ static PJ_LP e_rhealpix_inverse(PJ_XY xy, PJ *P) { /* ellipsoid */ if (in_image(xy.x, xy.y, 1, Q->north_square, Q->south_square) == 0) { lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; - proj_context_errno_set(P->ctx, PJD_ERR_INVALID_X_OR_Y); + proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } xy = combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 1); @@ -615,7 +615,7 @@ static PJ *destructor (PJ *P, int errlev) { /* Destructor PJ *PROJECTION(healpix) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; @@ -625,7 +625,7 @@ PJ *PROJECTION(healpix) { if (P->es != 0.0) { Q->apa = pj_authset(P->es); /* For auth_lat(). */ if (nullptr==Q->apa) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->qp = pj_qsfn(1.0, P->e, P->one_es); /* For auth_lat(). */ P->a = P->a*sqrt(0.5*Q->qp); /* Set P->a to authalic radius. */ pj_calc_ellipsoid_params (P, P->a, P->es); /* Ensure we have a consistent parameter set */ @@ -643,7 +643,7 @@ PJ *PROJECTION(healpix) { PJ *PROJECTION(rhealpix) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; @@ -652,13 +652,19 @@ PJ *PROJECTION(rhealpix) { /* Check for valid north_square and south_square inputs. */ if (Q->north_square < 0 || Q->north_square > 3) - return destructor (P, PJD_ERR_AXIS); + { + proj_log_error(P, _("Invalid value for north_square: it should be in [0,3] range.")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } if (Q->south_square < 0 || Q->south_square > 3) - return destructor (P, PJD_ERR_AXIS); + { + proj_log_error(P, _("Invalid value for south_square: it should be in [0,3] range.")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } if (P->es != 0.0) { Q->apa = pj_authset(P->es); /* For auth_lat(). */ if (nullptr==Q->apa) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->qp = pj_qsfn(1.0, P->e, P->one_es); /* For auth_lat(). */ P->a = P->a*sqrt(0.5*Q->qp); /* Set P->a to authalic radius. */ P->ra = 1.0/P->a; diff --git a/src/projections/igh.cpp b/src/projections/igh.cpp index 8aaaaba1..a6bac0c4 100644 --- a/src/projections/igh.cpp +++ b/src/projections/igh.cpp @@ -210,7 +210,7 @@ PJ *PROJECTION(igh) { PJ_LP lp = { 0, phi_boundary }; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; @@ -222,7 +222,7 @@ PJ *PROJECTION(igh) { !setup_zone(P, Q, 7, pj_sinu, d20, 0, d20) || !setup_zone(P, Q, 8, pj_sinu, d140, 0, d140)) { - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); } /* mollweide zones */ @@ -243,7 +243,7 @@ PJ *PROJECTION(igh) { !setup_zone(P, Q,11, pj_moll, d20, -Q->dy0, d20) || !setup_zone(P, Q,12, pj_moll, d140, -Q->dy0, d140)) { - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); } P->inv = igh_s_inverse; diff --git a/src/projections/igh_o.cpp b/src/projections/igh_o.cpp index 80874845..c0931117 100644 --- a/src/projections/igh_o.cpp +++ b/src/projections/igh_o.cpp @@ -224,7 +224,7 @@ PJ *PROJECTION(igh_o) { PJ_LP lp = { 0, phi_boundary }; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; @@ -236,13 +236,13 @@ PJ *PROJECTION(igh_o) { !setup_zone(P, Q, 8, pj_sinu, d20, 0, d20) || !setup_zone(P, Q, 9, pj_sinu, d150, 0, d150)) { - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); } /* mollweide zones */ if (!setup_zone(P, Q, 1, pj_moll, -d140, 0, -d140)) { - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); } /* y0 ? */ @@ -260,7 +260,7 @@ PJ *PROJECTION(igh_o) { !setup_zone(P, Q, 11, pj_moll, d20, -Q->dy0, d20) || !setup_zone(P, Q, 12, pj_moll, d150, -Q->dy0, d150)) { - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); } P->inv = igh_o_s_inverse; diff --git a/src/projections/imw_p.cpp b/src/projections/imw_p.cpp index 6e82d287..ed5cb897 100644 --- a/src/projections/imw_p.cpp +++ b/src/projections/imw_p.cpp @@ -34,15 +34,27 @@ static int phi12(PJ *P, double *del, double *sig) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(P->opaque); int err = 0; - if (!pj_param(P->ctx, P->params, "tlat_1").i || - !pj_param(P->ctx, P->params, "tlat_2").i) { - err = PJD_ERR_LAT_1_2_UNSPECIFIED; - } else { + if (!pj_param(P->ctx, P->params, "tlat_1").i ) + { + proj_log_error(P, _("Missing parameter: lat_1 should be specified")); + err = PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE; + } + else if ( !pj_param(P->ctx, P->params, "tlat_2").i) + { + proj_log_error(P, _("Missing parameter: lat_2 should be specified")); + err = PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE; + } + else + { Q->phi_1 = pj_param(P->ctx, P->params, "rlat_1").f; Q->phi_2 = pj_param(P->ctx, P->params, "rlat_2").f; *del = 0.5 * (Q->phi_2 - Q->phi_1); *sig = 0.5 * (Q->phi_2 + Q->phi_1); - err = (fabs(*del) < EPS || fabs(*sig) < EPS) ? PJD_ERR_ABS_LAT1_EQ_ABS_LAT2 : 0; + err = (fabs(*del) < EPS || fabs(*sig) < EPS) ? PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE : 0; + if( err ) + { + proj_log_error(P, _("Illegal value for lat_1 and lat_2: |lat_1 - lat_2| and |lat_1 + lat_2| should be > 0")); + } } return err; } @@ -120,7 +132,7 @@ static PJ_LP imw_p_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, invers if( denom != 0 || fabs(t.y - xy.y) > TOL ) { if( denom == 0 ) { - proj_errno_set(P, PJD_ERR_NON_CONVERGENT); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lp; } lp.phi = ((lp.phi - Q->phi_1) * (xy.y - yc) / denom) + Q->phi_1; @@ -133,7 +145,7 @@ static PJ_LP imw_p_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, invers if( i == N_MAX_ITER ) { - proj_errno_set(P, PJD_ERR_NON_CONVERGENT); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lp; } @@ -171,10 +183,10 @@ PJ *PROJECTION(imw_p) { int err; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; - if (!(Q->en = pj_enfn(P->es))) return pj_default_destructor (P, ENOMEM); + if (!(Q->en = pj_enfn(P->es))) return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); if( (err = phi12(P, &del, &sig)) != 0) { return destructor(P, err); } diff --git a/src/projections/isea.cpp b/src/projections/isea.cpp index 77a5689b..08e558a4 100644 --- a/src/projections/isea.cpp +++ b/src/projections/isea.cpp @@ -1026,7 +1026,7 @@ static PJ_XY isea_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward try { out = isea_forward(&Q->dgg, &in); } catch( const char* ) { - proj_errno_set(P, PJD_ERR_NON_CONVERGENT); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } @@ -1041,7 +1041,7 @@ PJ *PROJECTION(isea) { char *opt; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; @@ -1059,7 +1059,8 @@ PJ *PROJECTION(isea) { } else if (!strcmp(opt, "pole")) { isea_orient_pole(&Q->dgg); } else { - return pj_default_destructor(P, PJD_ERR_ELLIPSOID_USE_REQUIRED); + proj_log_error(P, _("Invalid value for orient: only isea or pole are supported")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } @@ -1089,8 +1090,8 @@ PJ *PROJECTION(isea) { Q->dgg.output = ISEA_HEX; } else { - /* TODO verify error code. Possibly eliminate magic */ - return pj_default_destructor(P, PJD_ERR_ELLIPSOID_USE_REQUIRED); + proj_log_error(P, _("Invalid value for mode: only plane, di, dd or hex are supported")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } diff --git a/src/projections/krovak.cpp b/src/projections/krovak.cpp index adc039fe..1ddf7777 100644 --- a/src/projections/krovak.cpp +++ b/src/projections/krovak.cpp @@ -181,7 +181,7 @@ static PJ_LP krovak_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, fi1 = lp.phi; } if( i == 0 ) - proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT ); + proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN ); lp.lam -= P->lam0; @@ -193,7 +193,7 @@ PJ *PROJECTION(krovak) { double u0, n0, g; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; /* we want Bessel as fixed ellipsoid */ @@ -225,7 +225,8 @@ PJ *PROJECTION(krovak) { g = pow( (1. + P->e * sin(P->phi0)) / (1. - P->e * sin(P->phi0)) , Q->alpha * P->e / 2. ); double tan_half_phi0_plus_pi_4 = tan(P->phi0 / 2. + M_PI_4); if( tan_half_phi0_plus_pi_4 == 0.0 ) { - return pj_default_destructor(P, PJD_ERR_INVALID_ARG); + proj_log_error(P, _("Invalid value for lat_0: lat_0 + PI/4 should be different from 0")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->k = tan( u0 / 2. + M_PI_4) / pow (tan_half_phi0_plus_pi_4 , Q->alpha) * g; n0 = sqrt(1. - P->es) / (1. - P->es * pow(sin(P->phi0), 2)); diff --git a/src/projections/labrd.cpp b/src/projections/labrd.cpp index 4fbcf460..9fa17817 100644 --- a/src/projections/labrd.cpp +++ b/src/projections/labrd.cpp @@ -107,11 +107,12 @@ PJ *PROJECTION(labrd) { double Az, sinp, R, N, t; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (P->phi0 == 0.) { - return pj_default_destructor(P, PJD_ERR_LAT_0_IS_ZERO); + proj_log_error(P, _("Invalid value for lat_0: lat_0 should be different from 0")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Az = pj_param(P->ctx, P->params, "razi").f; diff --git a/src/projections/laea.cpp b/src/projections/laea.cpp index 2d19cab1..8c7797e8 100644 --- a/src/projections/laea.cpp +++ b/src/projections/laea.cpp @@ -65,7 +65,7 @@ static PJ_XY laea_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, forward break; } if (fabs(b) < EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } @@ -111,7 +111,7 @@ static PJ_XY laea_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward xy.y = 1. + Q->sinb1 * sinphi + Q->cosb1 * cosphi * coslam; oblcon: if (xy.y <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.y = sqrt(2. / xy.y); @@ -124,7 +124,7 @@ oblcon: /*-fallthrough*/ case S_POLE: if (fabs(lp.phi + P->phi0) < EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.y = M_FORTPI - lp.phi * .5; @@ -193,7 +193,7 @@ static PJ_LP laea_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse rh = hypot(xy.x, xy.y); if ((lp.phi = rh * .5 ) > 1.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } lp.phi = 2. * asin(lp.phi); @@ -244,13 +244,14 @@ PJ *PROJECTION(laea) { double t; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; t = fabs(P->phi0); if (t > M_HALFPI + EPS10 ) { - return destructor(P, PJD_ERR_LAT_LARGER_THAN_90); + proj_log_error(P, _("Invalid value for lat_0: |lat_0| should be <= 90°")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (fabs(t - M_HALFPI) < EPS10) Q->mode = P->phi0 < 0. ? S_POLE : N_POLE; @@ -266,7 +267,7 @@ PJ *PROJECTION(laea) { Q->mmf = .5 / (1. - P->es); Q->apa = pj_authset(P->es); if (nullptr==Q->apa) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); switch (Q->mode) { case N_POLE: case S_POLE: diff --git a/src/projections/lagrng.cpp b/src/projections/lagrng.cpp index 1029bf8d..228f0b80 100644 --- a/src/projections/lagrng.cpp +++ b/src/projections/lagrng.cpp @@ -35,7 +35,7 @@ static PJ_XY lagrng_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwa lp.lam *= Q->rw; c = 0.5 * (v + 1./v) + cos(lp.lam); if (c < TOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.x = 2. * sin(lp.lam) / c; @@ -59,7 +59,7 @@ static PJ_LP lagrng_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver y2m = 2. - xy.y; c = y2p * y2m - x2; if (fabs(c) < TOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } lp.phi = 2. * atan(pow((y2p * y2p + x2) / (Q->a2 * (y2m * y2m + x2)), Q->hw)) - M_HALFPI; @@ -73,7 +73,7 @@ PJ *PROJECTION(lagrng) { double sin_phi1; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if( pj_param(P->ctx, P->params, "tW").i ) @@ -81,13 +81,19 @@ PJ *PROJECTION(lagrng) { else Q->w = 2; if (Q->w <= 0) - return pj_default_destructor(P, PJD_ERR_W_OR_M_ZERO_OR_LESS); + { + proj_log_error(P, _("Invalid value for W: it should be > 0")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } Q->hw = 0.5 * Q->w; Q->rw = 1. / Q->w; Q->hrw = 0.5 * Q->rw; sin_phi1 = sin(pj_param(P->ctx, P->params, "rlat_1").f); if (fabs(fabs(sin_phi1) - 1.) < TOL) - return pj_default_destructor(P, PJD_ERR_LAT_LARGER_THAN_90); + { + proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } Q->a1 = pow((1. - sin_phi1)/(1. + sin_phi1), Q->hrw); Q->a2 = Q->a1 * Q->a1; diff --git a/src/projections/lcc.cpp b/src/projections/lcc.cpp index 525281f4..b47cf67e 100644 --- a/src/projections/lcc.cpp +++ b/src/projections/lcc.cpp @@ -27,7 +27,7 @@ static PJ_XY lcc_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, forward if (fabs(fabs(lp.phi) - M_HALFPI) < EPS10) { if ((lp.phi * Q->n) <= 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } rho = 0.; @@ -62,7 +62,7 @@ static PJ_LP lcc_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse if (P->es != 0.) { lp.phi = pj_phi2(P->ctx, pow(rho / Q->c, 1./Q->n), P->e); if (lp.phi == HUGE_VAL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } @@ -83,7 +83,7 @@ PJ *PROJECTION(lcc) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc(1, sizeof (struct pj_opaque))); if (nullptr == Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; @@ -94,37 +94,45 @@ PJ *PROJECTION(lcc) { if (!pj_param(P->ctx, P->params, "tlat_0").i) P->phi0 = Q->phi1; } - if (fabs(Q->phi1) > M_HALFPI || fabs(Q->phi2) > M_HALFPI) - return pj_default_destructor(P, PJD_ERR_LAT_LARGER_THAN_90); + if (fabs(Q->phi1 + Q->phi2) < EPS10) - return pj_default_destructor(P, PJD_ERR_CONIC_LAT_EQUAL); + { + proj_log_error(P, _("Invalid value for lat_1 and lat_2: |lat_1 + lat_2| should be > 0")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } Q->n = sinphi = sin(Q->phi1); cosphi = cos(Q->phi1); + + if( fabs(cosphi) < EPS10 || fabs(Q->phi1) >= M_PI_2 ) { + proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + if( fabs(cos(Q->phi2)) < EPS10 || fabs(Q->phi2) >= M_PI_2 ) { + proj_log_error(P, _("Invalid value for lat_2: |lat_2| should be < 90°")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + secant = fabs(Q->phi1 - Q->phi2) >= EPS10; if (P->es != 0.) { double ml1, m1; m1 = pj_msfn(sinphi, cosphi, P->es); - if( fabs(Q->phi1) == M_HALFPI ) { - return pj_default_destructor(P, PJD_ERR_LAT_1_OR_2_ZERO_OR_90); - } ml1 = pj_tsfn(Q->phi1, sinphi, P->e); if (secant) { /* secant cone */ sinphi = sin(Q->phi2); Q->n = log(m1 / pj_msfn(sinphi, cos(Q->phi2), P->es)); if (Q->n == 0) { // Not quite, but es is very close to 1... - return pj_default_destructor(P, PJD_ERR_INVALID_ECCENTRICITY); - } - if( fabs(Q->phi2) == M_HALFPI ) { - return pj_default_destructor(P, PJD_ERR_LAT_1_OR_2_ZERO_OR_90); + proj_log_error(P, _("Invalid value for eccentricity")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } const double ml2 = pj_tsfn(Q->phi2, sinphi, P->e); const double denom = log(ml1 / ml2); if( denom == 0 ) { // Not quite, but es is very close to 1... - return pj_default_destructor(P, PJD_ERR_INVALID_ECCENTRICITY); + proj_log_error(P, _("Invalid value for eccentricity")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->n /= denom; } @@ -133,9 +141,6 @@ PJ *PROJECTION(lcc) { Q->rho0 *= (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) ? 0. : pow(pj_tsfn(P->phi0, sin(P->phi0), P->e), Q->n); } else { - if( fabs(cosphi) < EPS10 || fabs(cos(Q->phi2)) < EPS10 ) { - return pj_default_destructor(P, PJD_ERR_LAT_1_OR_2_ZERO_OR_90); - } if (secant) Q->n = log(cosphi / cos(Q->phi2)) / log(tan(M_FORTPI + .5 * Q->phi2) / @@ -143,7 +148,8 @@ PJ *PROJECTION(lcc) { if( Q->n == 0 ) { // Likely reason is that phi1 / phi2 are too close to zero. // Can be reproduced with +proj=lcc +a=1 +lat_2=.0000001 - return pj_default_destructor(P, PJD_ERR_CONIC_LAT_EQUAL); + proj_log_error(P, _("Invalid value for lat_1 and lat_2: |lat_1 + lat_2| should be > 0")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->c = cosphi * pow(tan(M_FORTPI + .5 * Q->phi1), Q->n) / Q->n; Q->rho0 = (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) ? 0. : diff --git a/src/projections/lcca.cpp b/src/projections/lcca.cpp index 53646fc6..9de4d5dc 100644 --- a/src/projections/lcca.cpp +++ b/src/projections/lcca.cpp @@ -112,7 +112,7 @@ static PJ_LP lcca_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse if (fabs(dif) < DEL_TOL) break; } if (!i) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } lp.phi = pj_inv_mlfn(P->ctx, S + Q->M0, P->es, Q->en); @@ -137,15 +137,16 @@ PJ *PROJECTION(lcca) { double s2p0, N0, R0, tan0; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; (Q->en = pj_enfn(P->es)); if (!Q->en) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); if (P->phi0 == 0.) { - return destructor(P, PJD_ERR_LAT_0_IS_ZERO); + proj_log_error(P, _("Invalid value for lat_0: it should be different from 0.")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->l = sin(P->phi0); Q->M0 = pj_mlfn(P->phi0, Q->l, cos(P->phi0), Q->en); diff --git a/src/projections/loxim.cpp b/src/projections/loxim.cpp index 64124bdd..00f43da4 100644 --- a/src/projections/loxim.cpp +++ b/src/projections/loxim.cpp @@ -58,14 +58,16 @@ static PJ_LP loxim_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers PJ *PROJECTION(loxim) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; Q->cosphi1 = cos(Q->phi1); if (Q->cosphi1 < EPS) - return pj_default_destructor(P, PJD_ERR_LAT_LARGER_THAN_90); - + { + proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } Q->tanphi1 = tan(M_FORTPI + 0.5 * Q->phi1); diff --git a/src/projections/lsat.cpp b/src/projections/lsat.cpp index a811a3a6..a7f386f7 100644 --- a/src/projections/lsat.cpp +++ b/src/projections/lsat.cpp @@ -136,7 +136,7 @@ static PJ_LP lsat_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse sppsq = spp * spp; const double denom = 1. - sppsq * (1. + Q->u); if( denom == 0.0 ) { - proj_errno_set(P, PJD_ERR_INVALID_X_OR_Y); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lp; } lamt = atan(((1. - sppsq * P->rone_es) * tan(lamdp) * @@ -160,16 +160,23 @@ PJ *PROJECTION(lsat) { double lam, alf, esc, ess; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; land = pj_param(P->ctx, P->params, "ilsat").i; if (land <= 0 || land > 5) - return pj_default_destructor(P, PJD_ERR_LSAT_NOT_IN_RANGE); + { + proj_log_error(P, _("Invalid value for lsat: lsat should be in [1, 5] range")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } path = pj_param(P->ctx, P->params, "ipath").i; - if (path <= 0 || path > (land <= 3 ? 251 : 233)) - return pj_default_destructor(P, PJD_ERR_PATH_NOT_IN_RANGE); + const int maxPathVal = (land <= 3 ? 251 : 233); + if (path <= 0 || path > maxPathVal) + { + proj_log_error(P, _("Invalid value for path: path should be in [1, %d] range"), maxPathVal); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } if (land <= 3) { P->lam0 = DEG_TO_RAD * 128.87 - M_TWOPI / 251. * path; diff --git a/src/projections/mbtfpp.cpp b/src/projections/mbtfpp.cpp index cc01cb40..bfa869f9 100644 --- a/src/projections/mbtfpp.cpp +++ b/src/projections/mbtfpp.cpp @@ -32,7 +32,7 @@ static PJ_LP mbtfpp_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver lp.phi = xy.y / FYC; if (fabs(lp.phi) >= 1.) { if (fabs(lp.phi) > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { lp.phi = (lp.phi < 0.) ? -M_HALFPI : M_HALFPI; @@ -45,7 +45,7 @@ static PJ_LP mbtfpp_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver lp.phi = sin(lp.phi) / CSy; if (fabs(lp.phi) >= 1.) { if (fabs(lp.phi) > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { lp.phi = (lp.phi < 0.) ? -M_HALFPI : M_HALFPI; diff --git a/src/projections/mbtfpq.cpp b/src/projections/mbtfpq.cpp index 5c7f8ca6..7d44b5ba 100644 --- a/src/projections/mbtfpq.cpp +++ b/src/projections/mbtfpq.cpp @@ -42,7 +42,7 @@ static PJ_LP mbtfpq_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver lp.phi = RYC * xy.y; if (fabs(lp.phi) > 1.) { if (fabs(lp.phi) > ONETOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else if (lp.phi < 0.) { @@ -61,7 +61,7 @@ static PJ_LP mbtfpq_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver lp.phi = RC * (t + sin(lp.phi)); if (fabs(lp.phi) > 1.) if (fabs(lp.phi) > ONETOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; diff --git a/src/projections/merc.cpp b/src/projections/merc.cpp index 3a0ed7b4..7fd3dff9 100644 --- a/src/projections/merc.cpp +++ b/src/projections/merc.cpp @@ -53,7 +53,10 @@ PJ *PROJECTION(merc) { if( (is_phits = pj_param(P->ctx, P->params, "tlat_ts").i) ) { phits = fabs(pj_param(P->ctx, P->params, "rlat_ts").f); if (phits >= M_HALFPI) - return pj_default_destructor(P, PJD_ERR_LAT_TS_LARGER_THAN_90); + { + proj_log_error(P, _("Invalid value for lat_ts: |lat_ts| should be <= 90°")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } } if (P->es != 0.0) { /* ellipsoid */ diff --git a/src/projections/misrsom.cpp b/src/projections/misrsom.cpp index d7e199f2..07c9961e 100644 --- a/src/projections/misrsom.cpp +++ b/src/projections/misrsom.cpp @@ -155,7 +155,7 @@ static PJ_LP misrsom_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inve sppsq = spp * spp; const double denom = 1. - sppsq * (1. + Q->u); if( denom == 0.0 ) { - proj_errno_set(P, PJD_ERR_NON_CONVERGENT); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lp; } lamt = atan(((1. - sppsq * P->rone_es) * tan(lamdp) * @@ -180,12 +180,15 @@ PJ *PROJECTION(misrsom) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; path = pj_param(P->ctx, P->params, "ipath").i; if (path <= 0 || path > 233) - return pj_default_destructor(P, PJD_ERR_PATH_NOT_IN_RANGE); + { + proj_log_error(P, _("Invalid value for path: path should be in [1, 233] range")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } P->lam0 = DEG_TO_RAD * 129.3056 - M_TWOPI / 233. * path; alf = 98.30382 * DEG_TO_RAD; diff --git a/src/projections/mod_ster.cpp b/src/projections/mod_ster.cpp index 0c30b7b6..ae64c494 100644 --- a/src/projections/mod_ster.cpp +++ b/src/projections/mod_ster.cpp @@ -37,7 +37,7 @@ static PJ_XY mod_ster_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, for cchi = cos(chi); const double denom = 1. + Q->schio * schi + Q->cchio * cchi * coslon; if( denom == 0 ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } s = 2. / denom; @@ -136,7 +136,7 @@ PJ *PROJECTION(mil_os) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->n = 2; @@ -159,7 +159,7 @@ PJ *PROJECTION(lee_os) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->n = 2; @@ -184,7 +184,7 @@ PJ *PROJECTION(gs48) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->n = 4; @@ -219,7 +219,7 @@ PJ *PROJECTION(alsk) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->n = 5; @@ -267,7 +267,7 @@ PJ *PROJECTION(gs50) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->n = 9; diff --git a/src/projections/moll.cpp b/src/projections/moll.cpp index 4864c8e1..87b38336 100644 --- a/src/projections/moll.cpp +++ b/src/projections/moll.cpp @@ -79,7 +79,7 @@ static PJ * setup(PJ *P, double p) { PJ *PROJECTION(moll) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; return setup(P, M_HALFPI); @@ -89,7 +89,7 @@ PJ *PROJECTION(moll) { PJ *PROJECTION(wag4) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; return setup(P, M_PI/3.); @@ -98,7 +98,7 @@ PJ *PROJECTION(wag4) { PJ *PROJECTION(wag5) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->es = 0; diff --git a/src/projections/natearth.cpp b/src/projections/natearth.cpp index e1f71089..f83c8467 100644 --- a/src/projections/natearth.cpp +++ b/src/projections/natearth.cpp @@ -82,7 +82,7 @@ static PJ_LP natearth_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inv } } if( i == 0 ) - proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT ); + proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN ); lp.phi = yc; /* longitude */ diff --git a/src/projections/natearth2.cpp b/src/projections/natearth2.cpp index e4516a0a..8bef168d 100644 --- a/src/projections/natearth2.cpp +++ b/src/projections/natearth2.cpp @@ -76,7 +76,7 @@ static PJ_LP natearth2_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, in } } if( i == 0 ) - proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT ); + proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN ); lp.phi = yc; /* longitude */ diff --git a/src/projections/nsper.cpp b/src/projections/nsper.cpp index 951111ac..9e67388e 100644 --- a/src/projections/nsper.cpp +++ b/src/projections/nsper.cpp @@ -66,7 +66,7 @@ static PJ_XY nsper_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwar break; } if (xy.y < Q->rp) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.y = Q->pn1 / (Q->p - xy.y); @@ -120,7 +120,7 @@ static PJ_LP nsper_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers double cosz, sinz; sinz = 1. - rh * rh * Q->pfact; if (sinz < 0.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } sinz = (Q->p - sqrt(sinz)) / (Q->pn1 / rh + rh / Q->pn1); @@ -166,7 +166,10 @@ static PJ *setup(PJ *P) { } Q->pn1 = Q->height / P->a; /* normalize by radius */ if ( Q->pn1 <= 0 || Q->pn1 > 1e10 ) - return pj_default_destructor (P, PJD_ERR_INVALID_H); + { + proj_log_error(P, _("Invalid value for h")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } Q->p = 1. + Q->pn1; Q->rp = 1. / Q->p; Q->h = 1. / Q->pn1; @@ -182,7 +185,7 @@ static PJ *setup(PJ *P) { PJ *PROJECTION(nsper) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->tilt = 0; @@ -196,7 +199,7 @@ PJ *PROJECTION(tpers) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; omega = pj_param(P->ctx, P->params, "rtilt").f; diff --git a/src/projections/ob_tran.cpp b/src/projections/ob_tran.cpp index 86798e0a..78c57521 100644 --- a/src/projections/ob_tran.cpp +++ b/src/projections/ob_tran.cpp @@ -180,26 +180,33 @@ PJ *PROJECTION(ob_tran) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; /* get name of projection to be translated */ if (pj_param(P->ctx, P->params, "so_proj").s == nullptr) - return destructor(P, PJD_ERR_NO_ROTATION_PROJ); + { + proj_log_error(P, _("Missing parameter: o_proj")); + return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); + } /* Create the target projection object to rotate */ args = ob_tran_target_params (P->params); /* avoid endless recursion */ if (args.argv == nullptr ) { - return destructor(P, PJD_ERR_FAILED_TO_FIND_PROJ); + proj_log_error(P, _("Failed to find projection to be rotated")); + return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } R = proj_create_argv (P->ctx, args.argc, args.argv); free (args.argv); if (nullptr==R) - return destructor (P, PJD_ERR_UNKNOWN_PROJECTION_ID); + { + proj_log_error(P, _("Projection to be rotated is unknown")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } Q->link = R; if (pj_param(P->ctx, P->params, "to_alpha").i) { @@ -210,7 +217,10 @@ PJ *PROJECTION(ob_tran) { alpha = pj_param(P->ctx, P->params, "ro_alpha").f; if (fabs(fabs(phic) - M_HALFPI) <= TOL) - return destructor(P, PJD_ERR_LAT_0_OR_ALPHA_EQ_90); + { + proj_log_error(P, _("Invalid value for lat_c: |lat_c| should be < 90°")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } Q->lamp = lamc + aatan2(-cos(alpha), -sin(alpha) * sin(phic)); phip = aasin(P->ctx,cos(phic) * sin(alpha)); @@ -225,9 +235,27 @@ PJ *PROJECTION(ob_tran) { lam2 = pj_param(P->ctx, P->params, "ro_lon_2").f; phi2 = pj_param(P->ctx, P->params, "ro_lat_2").f; con = fabs(phi1); - if (fabs(phi1 - phi2) <= TOL || con <= TOL || - fabs(con - M_HALFPI) <= TOL || fabs(fabs(phi2) - M_HALFPI) <= TOL) - return destructor(P, PJD_ERR_LAT_1_OR_2_ZERO_OR_90); + + if (fabs(phi1) > M_HALFPI - TOL) + { + proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + if (fabs(phi2) > M_HALFPI - TOL) + { + proj_log_error(P, _("Invalid value for lat_2: |lat_2| should be < 90°")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + if (fabs(phi1 - phi2) < TOL) + { + proj_log_error(P, _("Invalid value for lat_1 and lat_2: lat_1 should be different from lat_2")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + if (con < TOL) + { + proj_log_error(P, _("Invalid value for lat_1: lat_1 should be different from zero")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } Q->lamp = atan2(cos(phi1) * sin(phi2) * cos(lam1) - sin(phi1) * cos(phi2) * cos(lam2), diff --git a/src/projections/ocea.cpp b/src/projections/ocea.cpp index c78e1ebc..33f8d1f3 100644 --- a/src/projections/ocea.cpp +++ b/src/projections/ocea.cpp @@ -54,7 +54,7 @@ PJ *PROJECTION(ocea) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->rok = 1. / P->k0; diff --git a/src/projections/oea.cpp b/src/projections/oea.cpp index 46c00d16..2f13ae98 100644 --- a/src/projections/oea.cpp +++ b/src/projections/oea.cpp @@ -60,27 +60,34 @@ static PJ_LP oea_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse PJ *PROJECTION(oea) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; - if (((Q->n = pj_param(P->ctx, P->params, "dn").f) <= 0.) || - ((Q->m = pj_param(P->ctx, P->params, "dm").f) <= 0.)) { - return pj_default_destructor(P, PJD_ERR_INVALID_M_OR_N); - } else { - Q->theta = pj_param(P->ctx, P->params, "rtheta").f; - Q->sp0 = sin(P->phi0); - Q->cp0 = cos(P->phi0); - Q->rn = 1./ Q->n; - Q->rm = 1./ Q->m; - Q->two_r_n = 2. * Q->rn; - Q->two_r_m = 2. * Q->rm; - Q->hm = 0.5 * Q->m; - Q->hn = 0.5 * Q->n; - P->fwd = oea_s_forward; - P->inv = oea_s_inverse; - P->es = 0.; + if (((Q->n = pj_param(P->ctx, P->params, "dn").f) <= 0.) ) + { + proj_log_error(P, _("Invalid value for n: it should be > 0")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } + if (((Q->m = pj_param(P->ctx, P->params, "dm").f) <= 0.) ) + { + proj_log_error(P, _("Invalid value for m: it should be > 0")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + + Q->theta = pj_param(P->ctx, P->params, "rtheta").f; + Q->sp0 = sin(P->phi0); + Q->cp0 = cos(P->phi0); + Q->rn = 1./ Q->n; + Q->rm = 1./ Q->m; + Q->two_r_n = 2. * Q->rn; + Q->two_r_m = 2. * Q->rm; + Q->hm = 0.5 * Q->m; + Q->hn = 0.5 * Q->n; + P->fwd = oea_s_forward; + P->inv = oea_s_inverse; + P->es = 0.; + return P; } diff --git a/src/projections/omerc.cpp b/src/projections/omerc.cpp index 90067cc3..48943972 100644 --- a/src/projections/omerc.cpp +++ b/src/projections/omerc.cpp @@ -58,7 +58,7 @@ static PJ_XY omerc_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, forwar const double V = sin(Q->B * lp.lam); const double U = (S * Q->singam - V * Q->cosgam) / T; if (fabs(fabs(U) - 1.0) < EPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } v = 0.5 * Q->ArB * log((1. - U)/(1. + U)); @@ -98,7 +98,7 @@ static PJ_LP omerc_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, invers } Qp = exp(- Q->BrA * v); if( Qp == 0 ) { - proj_errno_set(P, PJD_ERR_INVALID_X_OR_Y); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lp; } Sp = .5 * (Qp - 1. / Qp); @@ -111,7 +111,7 @@ static PJ_LP omerc_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, invers } else { lp.phi = Q->E / sqrt((1. + Up) / (1. - Up)); if ((lp.phi = pj_phi2(P->ctx, pow(lp.phi, 1. / Q->B), P->e)) == HUGE_VAL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } lp.lam = - Q->rB * atan2((Sp * Q->cosgam - @@ -128,7 +128,7 @@ PJ *PROJECTION(omerc) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->no_rot = pj_param(P->ctx, P->params, "bno_rot").i; @@ -154,14 +154,36 @@ PJ *PROJECTION(omerc) { phi1 = pj_param(P->ctx, P->params, "rlat_1").f; lam2 = pj_param(P->ctx, P->params, "rlon_2").f; phi2 = pj_param(P->ctx, P->params, "rlat_2").f; - if (fabs(phi1) > M_HALFPI || fabs(phi2) > M_HALFPI) - return pj_default_destructor(P, PJD_ERR_LAT_LARGER_THAN_90); - if (fabs(phi1 - phi2) <= TOL || - (con = fabs(phi1)) <= TOL || - fabs(con - M_HALFPI) <= TOL || - fabs(fabs(P->phi0) - M_HALFPI) <= TOL || - fabs(fabs(phi2) - M_HALFPI) <= TOL) - return pj_default_destructor(P, PJD_ERR_LAT_0_OR_ALPHA_EQ_90); + con = fabs(phi1); + + if (fabs(phi1) > M_HALFPI - TOL) + { + proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + if (fabs(phi2) > M_HALFPI - TOL) + { + proj_log_error(P, _("Invalid value for lat_2: |lat_2| should be < 90°")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + + if (fabs(phi1 - phi2) <= TOL ) + { + proj_log_error(P, _("Invalid value for lat_1/lat_2: lat_1 should be different from lat_2")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + + if (con <= TOL ) + { + proj_log_error(P, _("Invalid value for lat_1: lat_1 should be different from 0")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + + if (fabs(fabs(P->phi0) - M_HALFPI) <= TOL) + { + proj_log_error(P, _("Invalid value for lat_01: |lat_0| should be < 90°")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } } com = sqrt(P->one_es); if (fabs(P->phi0) > EPS) { @@ -193,9 +215,13 @@ PJ *PROJECTION(omerc) { gamma = alpha_c; } else alpha_c = aasin(P->ctx, D*sin(gamma0 = gamma)); - if( fabs(fabs(P->phi0) - M_HALFPI) <= TOL ) { - return pj_default_destructor(P, PJD_ERR_LAT_0_OR_ALPHA_EQ_90); + + if (fabs(fabs(P->phi0) - M_HALFPI) <= TOL) + { + proj_log_error(P, _("Invalid value for lat_01: |lat_0| should be < 90°")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } + P->lam0 = lamc - aasin(P->ctx, .5 * (F - 1. / F) * tan(gamma0)) / Q->B; } else { @@ -205,7 +231,8 @@ PJ *PROJECTION(omerc) { p = (L - H) / (L + H); if( p == 0 ) { // Not quite, but es is very close to 1... - return pj_default_destructor(P, PJD_ERR_INVALID_ECCENTRICITY); + proj_log_error(P, _("Invalid value for eccentricity")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } J = Q->E * Q->E; J = (J - L * H) / (J + L * H); @@ -217,7 +244,8 @@ PJ *PROJECTION(omerc) { J * tan(.5 * Q->B * (lam1 - lam2)) / p) / Q->B); const double denom = F - 1. / F; if( denom == 0 ) { - return pj_default_destructor(P, PJD_ERR_INVALID_ECCENTRICITY); + proj_log_error(P, _("Invalid value for eccentricity")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } gamma0 = atan(2. * sin(Q->B * adjlon(lam1 - P->lam0)) / denom); gamma = alpha_c = aasin(P->ctx, D * sin(gamma0)); diff --git a/src/projections/ortho.cpp b/src/projections/ortho.cpp index 4417dac7..9e0d9bba 100644 --- a/src/projections/ortho.cpp +++ b/src/projections/ortho.cpp @@ -29,7 +29,7 @@ struct pj_opaque { #define EPS10 1.e-10 static PJ_XY forward_error(PJ *P, PJ_LP lp, PJ_XY xy) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); proj_log_trace(P, "Coordinate (%.3f, %.3f) is on the unprojected hemisphere", proj_todeg(lp.lam), proj_todeg(lp.phi)); return xy; @@ -88,7 +88,7 @@ static PJ_LP ortho_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers sinc = rh; if (sinc > 1.) { if ((sinc - 1.) > EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); proj_log_trace(P, "Point (%.3f, %.3f) is outside the projection boundary"); return lp; } @@ -176,7 +176,7 @@ static PJ_LP ortho_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inver const double rh2 = SQ(xy.x) + SQ(xy.y); if (rh2 >= 1. - 1e-15) { if ((rh2 - 1.) > EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); proj_log_trace(P, "Point (%.3f, %.3f) is outside the projection boundary"); lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; return lp; @@ -200,7 +200,7 @@ static PJ_LP ortho_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inver // Equation of the ellipse if( SQ(xy.x) + SQ(xy.y * (P->a / P->b)) > 1 + 1e-11 ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); proj_log_trace(P, "Point (%.3f, %.3f) is outside the projection boundary"); lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; return lp; @@ -228,7 +228,7 @@ static PJ_LP ortho_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inver xy_recentered.x = xy.x; xy_recentered.y = (xy.y - Q->y_shift) / Q->y_scale; if( SQ(xy.x) + SQ(xy_recentered.y) > 1 + 1e-11 ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); proj_log_trace(P, "Point (%.3f, %.3f) is outside the projection boundary"); lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; return lp; @@ -273,7 +273,7 @@ static PJ_LP ortho_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inver return lp; } } - proj_context_errno_set(P->ctx, PJD_ERR_NON_CONVERGENT); + proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } @@ -281,7 +281,7 @@ static PJ_LP ortho_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inver PJ *PROJECTION(ortho) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->sinph0 = sin(P->phi0); diff --git a/src/projections/patterson.cpp b/src/projections/patterson.cpp index 32544580..d24ee98d 100644 --- a/src/projections/patterson.cpp +++ b/src/projections/patterson.cpp @@ -100,7 +100,7 @@ static PJ_LP patterson_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, in } } if( i == 0 ) - proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT ); + proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN ); lp.phi = yc; /* longitude */ diff --git a/src/projections/poly.cpp b/src/projections/poly.cpp index 4ea95cc7..7b0b7717 100644 --- a/src/projections/poly.cpp +++ b/src/projections/poly.cpp @@ -80,7 +80,7 @@ static PJ_LP poly_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse const double cp = cos(lp.phi); const double s2ph = sp * cp; if (fabs(cp) < ITOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } double mlp = sqrt(1. - P->es * sp * sp); @@ -97,7 +97,7 @@ static PJ_LP poly_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse break; } if (!i) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } const double c = sin(lp.phi); @@ -128,7 +128,7 @@ static PJ_LP poly_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inverse break; --i; if( i == 0 ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } } @@ -156,14 +156,14 @@ static PJ *destructor(PJ *P, int errlev) { PJ *PROJECTION(poly) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; if (P->es != 0.0) { if (!(Q->en = pj_enfn(P->es))) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); Q->ml0 = pj_mlfn(P->phi0, sin(P->phi0), cos(P->phi0), Q->en); P->inv = poly_e_inverse; P->fwd = poly_e_forward; diff --git a/src/projections/putp3.cpp b/src/projections/putp3.cpp index 09763851..3d72fdce 100644 --- a/src/projections/putp3.cpp +++ b/src/projections/putp3.cpp @@ -40,7 +40,7 @@ static PJ_LP putp3_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers PJ *PROJECTION(putp3) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->A = 4. * RPISQ; @@ -55,7 +55,7 @@ PJ *PROJECTION(putp3) { PJ *PROJECTION(putp3p) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->A = 2. * RPISQ; diff --git a/src/projections/putp4p.cpp b/src/projections/putp4p.cpp index 8df18972..d17dbfda 100644 --- a/src/projections/putp4p.cpp +++ b/src/projections/putp4p.cpp @@ -47,7 +47,7 @@ static PJ_LP putp4p_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, inver PJ *PROJECTION(putp4p) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->C_x = 0.874038744; @@ -64,7 +64,7 @@ PJ *PROJECTION(putp4p) { PJ *PROJECTION(weren) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->C_x = 1.; diff --git a/src/projections/putp5.cpp b/src/projections/putp5.cpp index 5e70382d..d2e55625 100644 --- a/src/projections/putp5.cpp +++ b/src/projections/putp5.cpp @@ -45,7 +45,7 @@ static PJ_LP putp5_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers PJ *PROJECTION(putp5) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->A = 2.; @@ -62,7 +62,7 @@ PJ *PROJECTION(putp5) { PJ *PROJECTION(putp5p) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->A = 1.5; diff --git a/src/projections/putp6.cpp b/src/projections/putp6.cpp index da8c0a7c..a5d51020 100644 --- a/src/projections/putp6.cpp +++ b/src/projections/putp6.cpp @@ -61,7 +61,7 @@ static PJ_LP putp6_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers PJ *PROJECTION(putp6) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->C_x = 1.01346; @@ -81,7 +81,7 @@ PJ *PROJECTION(putp6) { PJ *PROJECTION(putp6p) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->C_x = 0.44329; diff --git a/src/projections/qsc.cpp b/src/projections/qsc.cpp index dd9ce965..ea35eb8c 100644 --- a/src/projections/qsc.cpp +++ b/src/projections/qsc.cpp @@ -379,7 +379,7 @@ static PJ_LP qsc_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse PJ *PROJECTION(qsc) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->inv = qsc_e_inverse; diff --git a/src/projections/robin.cpp b/src/projections/robin.cpp index 6a1405b6..4dfc306c 100644 --- a/src/projections/robin.cpp +++ b/src/projections/robin.cpp @@ -86,7 +86,7 @@ static PJ_XY robin_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwar dphi = fabs(lp.phi); i = isnan(lp.phi) ? -1 : lround(floor(dphi * C1 + 1e-15)); if( i < 0 ){ - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } if (i >= NODES) i = NODES; @@ -109,7 +109,7 @@ static PJ_LP robin_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers lp.phi = fabs(xy.y / FYC); if (lp.phi >= 1.) { /* simple pathologic cases */ if (lp.phi > ONEEPS) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { @@ -120,7 +120,7 @@ static PJ_LP robin_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers /* in Y space, reduce to table interval */ long i = isnan(lp.phi) ? -1 : lround(floor(lp.phi * NODES)); if( i < 0 || i >= NODES ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } for (;;) { @@ -138,12 +138,12 @@ static PJ_LP robin_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers break; } if( iters == 0 ) - proj_context_errno_set( P->ctx, PJD_ERR_NON_CONVERGENT ); + proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN ); lp.phi = (5 * i + t) * DEG_TO_RAD; if (xy.y < 0.) lp.phi = -lp.phi; lp.lam /= V(X[i], t); if( fabs(lp.lam) > M_PI ) { - proj_errno_set(P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp = proj_coord_error().lp; } } diff --git a/src/projections/rouss.cpp b/src/projections/rouss.cpp index 2eb13b3d..7b513fdb 100644 --- a/src/projections/rouss.cpp +++ b/src/projections/rouss.cpp @@ -95,7 +95,7 @@ static PJ *destructor (PJ *P, int errlev) { if (static_cast<struct pj_opaque*>(P->opaque)->en) free (static_cast<struct pj_opaque*>(P->opaque)->en); - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); } @@ -104,11 +104,11 @@ PJ *PROJECTION(rouss) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (!((Q->en = proj_mdist_ini(P->es)))) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); es2 = sin(P->phi0); Q->s0 = proj_mdist(P->phi0, es2, cos(P->phi0), Q->en); diff --git a/src/projections/rpoly.cpp b/src/projections/rpoly.cpp index e3f09c59..f2e45f5e 100644 --- a/src/projections/rpoly.cpp +++ b/src/projections/rpoly.cpp @@ -46,7 +46,7 @@ static PJ_XY rpoly_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwar PJ *PROJECTION(rpoly) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->phi1 = fabs(pj_param(P->ctx, P->params, "rlat_ts").f); diff --git a/src/projections/sch.cpp b/src/projections/sch.cpp index 359e8efc..6e7641ff 100644 --- a/src/projections/sch.cpp +++ b/src/projections/sch.cpp @@ -131,7 +131,7 @@ static PJ *setup(PJ *P) { /* general initialization */ // Pass a dummy ellipsoid definition that will be overridden just afterwards Q->cart = proj_create(P->ctx, "+proj=cart +a=1"); if (Q->cart == nullptr) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); /* inherit ellipsoid definition from P to Q->cart */ pj_inherit_ellipsoid_def (P, Q->cart); @@ -154,7 +154,7 @@ static PJ *setup(PJ *P) { /* general initialization */ /* Set up local sphere at the given peg point */ Q->cart_sph = proj_create(P->ctx, "+proj=cart +a=1"); if (Q->cart_sph == nullptr) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); pj_calc_ellipsoid_params(Q->cart_sph, Q->rcurv, 0); /* Set up the transformation matrices */ @@ -186,7 +186,7 @@ static PJ *setup(PJ *P) { /* general initialization */ PJ *PROJECTION(sch) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; @@ -196,21 +196,24 @@ PJ *PROJECTION(sch) { if (pj_param(P->ctx, P->params, "tplat_0").i) Q->plat = pj_param(P->ctx, P->params, "rplat_0").f; else { - return pj_default_destructor(P, PJD_ERR_FAILED_TO_FIND_PROJ); + proj_log_error(P, _("Missing parameter plat_0.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } /* Check if peg longitude was defined */ if (pj_param(P->ctx, P->params, "tplon_0").i) Q->plon = pj_param(P->ctx, P->params, "rplon_0").f; else { - return pj_default_destructor(P, PJD_ERR_FAILED_TO_FIND_PROJ); + proj_log_error(P, _("Missing parameter plon_0.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } /* Check if peg heading is defined */ if (pj_param(P->ctx, P->params, "tphdg_0").i) Q->phdg = pj_param(P->ctx, P->params, "rphdg_0").f; else { - return pj_default_destructor(P, PJD_ERR_FAILED_TO_FIND_PROJ); + proj_log_error(P, _("Missing parameter phdg_0.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } diff --git a/src/projections/sconics.cpp b/src/projections/sconics.cpp index c12b05a2..8e977028 100644 --- a/src/projections/sconics.cpp +++ b/src/projections/sconics.cpp @@ -48,15 +48,28 @@ static int phi12(PJ *P, double *del) { double p1, p2; int err = 0; - if (!pj_param(P->ctx, P->params, "tlat_1").i || - !pj_param(P->ctx, P->params, "tlat_2").i) { - err = -41; - } else { + if (!pj_param(P->ctx, P->params, "tlat_1").i ) + { + proj_log_error(P, _("Missing parameter: lat_1 should be specified")); + err = PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE; + } + else if ( !pj_param(P->ctx, P->params, "tlat_2").i) + { + proj_log_error(P, _("Missing parameter: lat_2 should be specified")); + err = PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE; + } + else + { p1 = pj_param(P->ctx, P->params, "rlat_1").f; p2 = pj_param(P->ctx, P->params, "rlat_2").f; *del = 0.5 * (p2 - p1); - static_cast<struct pj_opaque*>(P->opaque)->sig = 0.5 * (p2 + p1); - err = (fabs(*del) < EPS || fabs(static_cast<struct pj_opaque*>(P->opaque)->sig) < EPS) ? PJD_ERR_ABS_LAT1_EQ_ABS_LAT2 : 0; + const double sig = 0.5 * (p2 + p1); + static_cast<struct pj_opaque*>(P->opaque)->sig = sig; + err = (fabs(*del) < EPS || fabs(sig) < EPS) ? PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE : 0; + if( err ) + { + proj_log_error(P, _("Illegal value for lat_1 and lat_2: |lat_1 - lat_2| and |lat_1 + lat_2| should be > 0")); + } } return err; } @@ -119,7 +132,7 @@ static PJ *setup(PJ *P, enum Type type) { int err; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->type = type; @@ -167,8 +180,10 @@ static PJ *setup(PJ *P, enum Type type) { Q->c1 = 1./tan (Q->sig); del = P->phi0 - Q->sig; if (fabs (del) - EPS10 >= M_HALFPI) - return pj_default_destructor(P, PJD_ERR_LAT_0_HALF_PI_FROM_MEAN); - + { + proj_log_error(P, _("Invalid value for lat_0/lat_1/lat_2: |lat_0 - 0.5 * (lat_1 + lat_2)| should be < 90°")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } Q->rho_0 = Q->c2 * (Q->c1 - tan (del)); break; diff --git a/src/projections/somerc.cpp b/src/projections/somerc.cpp index a184500c..6d6885e8 100644 --- a/src/projections/somerc.cpp +++ b/src/projections/somerc.cpp @@ -62,7 +62,7 @@ static PJ_LP somerc_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, inver lp.phi = phip; lp.lam = lamp / Q->c; } else { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } return (lp); @@ -73,7 +73,7 @@ PJ *PROJECTION(somerc) { double cp, phip0, sp; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; diff --git a/src/projections/stere.cpp b/src/projections/stere.cpp index ad1caae2..31b0fead 100644 --- a/src/projections/stere.cpp +++ b/src/projections/stere.cpp @@ -60,7 +60,7 @@ static PJ_XY stere_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, forwar const double denom = Q->cosX1 * (1. + Q->sinX1 * sinX + Q->cosX1 * cosX * coslam); if( denom == 0 ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } A = Q->akm1 / denom; @@ -117,7 +117,7 @@ static PJ_XY stere_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwar xy.y = 1. + sinph0 * sinphi + cosph0 * cosphi * coslam; oblcon: if (xy.y <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.y = Q->akm1 / xy.y; @@ -131,7 +131,7 @@ oblcon: /*-fallthrough*/ case S_POLE: if (fabs (lp.phi - M_HALFPI) < TOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.y = Q->akm1 * tan (M_FORTPI + .5 * lp.phi); @@ -190,7 +190,7 @@ static PJ_LP stere_e_inverse (PJ_XY xy, PJ *P) { /* Ellipsoidal, invers phi_l = lp.phi; } - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } @@ -304,7 +304,7 @@ static PJ *setup(PJ *P) { /* general initialization */ PJ *PROJECTION(stere) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->phits = pj_param (P->ctx, P->params, "tlat_ts").i ? @@ -317,13 +317,14 @@ PJ *PROJECTION(stere) { PJ *PROJECTION(ups) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; /* International Ellipsoid */ P->phi0 = pj_param(P->ctx, P->params, "bsouth").i ? - M_HALFPI: M_HALFPI; if (P->es == 0.0) { - return pj_default_destructor (P, PJD_ERR_ELLIPSOID_USE_REQUIRED); + proj_log_error(P, _("Invalid value for es: only ellipsoidal formulation supported")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } P->k0 = .994; P->x0 = 2000000.; diff --git a/src/projections/sterea.cpp b/src/projections/sterea.cpp index 4dd22d2f..19f2b778 100644 --- a/src/projections/sterea.cpp +++ b/src/projections/sterea.cpp @@ -55,7 +55,7 @@ static PJ_XY sterea_e_forward (PJ_LP lp, PJ *P) { /* Ellipsoidal, forwa cosl = cos(lp.lam); const double denom = 1. + Q->sinc0 * sinc + Q->cosc0 * cosc * cosl; if( denom == 0.0 ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } k = P->k0 * Q->R2 / denom; @@ -103,12 +103,12 @@ PJ *PROJECTION(sterea) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->en = pj_gauss_ini(P->e, P->phi0, &(Q->phic0), &R); if (nullptr==Q->en) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); Q->sinc0 = sin (Q->phic0); Q->cosc0 = cos (Q->phic0); diff --git a/src/projections/sts.cpp b/src/projections/sts.cpp index 75190e85..1042e38b 100644 --- a/src/projections/sts.cpp +++ b/src/projections/sts.cpp @@ -72,7 +72,7 @@ static PJ *setup(PJ *P, double p, double q, int mode) { PJ *PROJECTION(fouc) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; return setup(P, 2., 2., 1); } @@ -82,7 +82,7 @@ PJ *PROJECTION(fouc) { PJ *PROJECTION(kav5) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; return setup(P, 1.50488, 1.35439, 0); @@ -93,7 +93,7 @@ PJ *PROJECTION(kav5) { PJ *PROJECTION(qua_aut) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; return setup(P, 2., 2., 0); } @@ -103,7 +103,7 @@ PJ *PROJECTION(qua_aut) { PJ *PROJECTION(mbt_s) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; return setup(P, 1.48875, 1.36509, 0); } diff --git a/src/projections/tcc.cpp b/src/projections/tcc.cpp index 9413b567..c53bed05 100644 --- a/src/projections/tcc.cpp +++ b/src/projections/tcc.cpp @@ -16,7 +16,7 @@ static PJ_XY tcc_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forward const double b = cos (lp.phi) * sin (lp.lam); const double bt = 1. - b * b; if (bt < EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.x = b / sqrt(bt); diff --git a/src/projections/tmerc.cpp b/src/projections/tmerc.cpp index 8f897061..68411829 100644 --- a/src/projections/tmerc.cpp +++ b/src/projections/tmerc.cpp @@ -89,7 +89,7 @@ static PJ_XY approx_e_fwd (PJ_LP lp, PJ *P) if( lp.lam < -M_HALFPI || lp.lam > M_HALFPI ) { xy.x = HUGE_VAL; xy.y = HUGE_VAL; - proj_context_errno_set( P->ctx, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT ); + proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN ); return xy; } @@ -123,7 +123,7 @@ static PJ_XY tmerc_spherical_fwd (PJ_LP lp, PJ *P) { cosphi = cos(lp.phi); b = cosphi * sin (lp.lam); if (fabs (fabs (b) - 1.) <= EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } @@ -138,7 +138,7 @@ static PJ_XY tmerc_spherical_fwd (PJ_LP lp, PJ *P) { } else if (b >= 1.) { if ((b - 1.) > EPS10) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } else xy.y = 0.; @@ -190,7 +190,7 @@ static PJ_LP tmerc_spherical_inv (PJ_XY xy, PJ *P) { h = exp(xy.x / Q->esp); if( h == 0 ) { - proj_errno_set(P, PJD_ERR_INVALID_X_OR_Y); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lp; } g = .5 * (h - 1. / h); @@ -224,7 +224,7 @@ static PJ *setup_approx(PJ *P) { if (P->es != 0.0) { if (!(Q->en = pj_enfn(P->es))) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->ml0 = pj_mlfn(P->phi0, sin(P->phi0), cos(P->phi0), Q->en); Q->esp = P->es / (1. - P->es); @@ -402,8 +402,10 @@ static PJ_XY exact_e_fwd (PJ_LP lp, PJ *P) { if (fabs (Ce) <= 2.623395162778) { xy.y = Q->Qn * Cn + Q->Zb; /* Northing */ xy.x = Q->Qn * Ce; /* Easting */ - } else + } else { + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); xy.x = xy.y = HUGE_VAL; + } return xy; } @@ -476,8 +478,10 @@ static PJ_LP exact_e_inv (PJ_XY xy, PJ *P) { lp.phi = gatg (Q->cgb, PROJ_ETMERC_ORDER, Cn, cos_2_Cn, sin_2_Cn); lp.lam = Ce; } - else + else { + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp.phi = lp.lam = HUGE_VAL; + } return lp; } @@ -587,7 +591,7 @@ static PJ *setup(PJ *P, TMercAlgo eAlg) { struct tmerc_data *Q = static_cast<struct tmerc_data*>(calloc (1, sizeof (struct tmerc_data))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if( P->es == 0 ) @@ -701,14 +705,18 @@ PJ *PROJECTION(tmerc) { TMercAlgo algo; if( !getAlgoFromParams(P, algo) ) - return pj_default_destructor(P, PJD_ERR_INVALID_ARG); + { + proj_log_error(P, _("Invalid value for algo")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } return setup(P, algo); } PJ *PROJECTION(etmerc) { if (P->es == 0.0) { - return pj_default_destructor(P, PJD_ERR_ELLIPSOID_USE_REQUIRED); + proj_log_error(P, _("Invalid value for eccentricity: it should not be zero")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } return setup (P, TMercAlgo::PODER_ENGSAGER); @@ -720,10 +728,12 @@ PJ *PROJECTION(etmerc) { PJ *PROJECTION(utm) { long zone; if (P->es == 0.0) { - return pj_default_destructor(P, PJD_ERR_ELLIPSOID_USE_REQUIRED); + proj_log_error(P, _("Invalid value for eccentricity: it should not be zero")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (P->lam0 < -1000.0 || P->lam0 > 1000.0) { - return pj_default_destructor(P, PJD_ERR_INVALID_UTM_ZONE); + proj_log_error(P, _("Invalid value for lon_0")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } P->y0 = pj_param (P->ctx, P->params, "bsouth").i ? 10000000. : 0.; @@ -734,7 +744,8 @@ PJ *PROJECTION(utm) { if (zone > 0 && zone <= 60) --zone; else { - return pj_default_destructor(P, PJD_ERR_INVALID_UTM_ZONE); + proj_log_error(P, _("Invalid value for zone")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } else /* nearest central meridian input */ @@ -751,6 +762,9 @@ PJ *PROJECTION(utm) { TMercAlgo algo; if( !getAlgoFromParams(P, algo) ) - return pj_default_destructor(P, PJD_ERR_INVALID_ARG); + { + proj_log_error(P, _("Invalid value for algo")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } return setup(P, algo); } diff --git a/src/projections/tobmerc.cpp b/src/projections/tobmerc.cpp index f05a9b6b..13633a91 100644 --- a/src/projections/tobmerc.cpp +++ b/src/projections/tobmerc.cpp @@ -20,7 +20,7 @@ static PJ_XY tobmerc_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forw // it's not even that large, merely 38.025...). Even if the logic was // such that phi was strictly equal to pi/2, allowing xy.y = inf would be // a reasonable result. - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } diff --git a/src/projections/tpeqd.cpp b/src/projections/tpeqd.cpp index 90efb395..5d3c7144 100644 --- a/src/projections/tpeqd.cpp +++ b/src/projections/tpeqd.cpp @@ -66,7 +66,7 @@ PJ *PROJECTION(tpeqd) { double lam_1, lam_2, phi_1, phi_2, A12; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; @@ -77,7 +77,10 @@ PJ *PROJECTION(tpeqd) { lam_2 = pj_param(P->ctx, P->params, "rlon_2").f; if (phi_1 == phi_2 && lam_1 == lam_2) - return pj_default_destructor(P, PJD_ERR_CONTROL_POINT_NO_DIST); + { + proj_log_error(P, _("Invalid value for lat_1/lon_1/lat_2/lon_2: the 2 points should be distinct.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } P->lam0 = adjlon (0.5 * (lam_1 + lam_2)); Q->dlam2 = adjlon (lam_2 - lam_1); @@ -92,7 +95,8 @@ PJ *PROJECTION(tpeqd) { Q->z02 = aacos(P->ctx, Q->sp1 * Q->sp2 + Q->cp1 * Q->cp2 * cos (Q->dlam2)); if( Q->z02 == 0.0 ) { // Actually happens when both lat_1 = lat_2 and |lat_1| = 90 - return pj_default_destructor(P, PJD_ERR_LAT_1_OR_2_ZERO_OR_90); + proj_log_error(P, _("Invalid value for lat_1 and lat_2: their absolute value should be < 90°.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->hz0 = .5 * Q->z02; A12 = atan2(Q->cp2 * sin (Q->dlam2), diff --git a/src/projections/urm5.cpp b/src/projections/urm5.cpp index c3021841..965c9458 100644 --- a/src/projections/urm5.cpp +++ b/src/projections/urm5.cpp @@ -32,22 +32,29 @@ PJ *PROJECTION(urm5) { double alpha, t; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; - if (pj_param(P->ctx, P->params, "tn").i) { - Q->n = pj_param(P->ctx, P->params, "dn").f; - if (Q->n <= 0. || Q->n > 1.) - return pj_default_destructor(P, PJD_ERR_N_OUT_OF_RANGE); - } else { - return pj_default_destructor(P, PJD_ERR_N_OUT_OF_RANGE); + if (!pj_param(P->ctx, P->params, "tn").i ) + { + proj_log_error(P, _("Missing parameter n.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } + + Q->n = pj_param(P->ctx, P->params, "dn").f; + if (Q->n <= 0. || Q->n > 1.) + { + proj_log_error(P, _("Invalid value for n: it should be in ]0,1] range.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + Q->q3 = pj_param(P->ctx, P->params, "dq").f / 3.; alpha = pj_param(P->ctx, P->params, "ralpha").f; t = Q->n * sin (alpha); const double denom = sqrt (1. - t * t); if( denom == 0 ) { - return pj_default_destructor(P, PJD_ERR_LAT_0_OR_ALPHA_EQ_90); + proj_log_error(P, _("Invalid value for n / alpha: n * sin(|alpha|) should be < 1.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->m = cos (alpha) / denom; Q->rmn = 1. / (Q->m * Q->n); diff --git a/src/projections/urmfps.cpp b/src/projections/urmfps.cpp index 5d689f9f..6df9ba57 100644 --- a/src/projections/urmfps.cpp +++ b/src/projections/urmfps.cpp @@ -49,16 +49,21 @@ static PJ *setup(PJ *P) { PJ *PROJECTION(urmfps) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; - if (pj_param(P->ctx, P->params, "tn").i) { - static_cast<struct pj_opaque*>(P->opaque)->n = pj_param(P->ctx, P->params, "dn").f; - if (static_cast<struct pj_opaque*>(P->opaque)->n <= 0. || static_cast<struct pj_opaque*>(P->opaque)->n > 1.) - return pj_default_destructor(P, PJD_ERR_N_OUT_OF_RANGE); - } else { - return pj_default_destructor(P, PJD_ERR_N_OUT_OF_RANGE); + if (!pj_param(P->ctx, P->params, "tn").i ) + { + proj_log_error(P, _("Missing parameter n.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); + } + + Q->n = pj_param(P->ctx, P->params, "dn").f; + if (Q->n <= 0. || Q->n > 1.) + { + proj_log_error(P, _("Invalid value for n: it should be in ]0,1] range.")); + return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } return setup(P); @@ -68,7 +73,7 @@ PJ *PROJECTION(urmfps) { PJ *PROJECTION(wag1) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; static_cast<struct pj_opaque*>(P->opaque)->n = 0.8660254037844386467637231707; diff --git a/src/projections/vandg.cpp b/src/projections/vandg.cpp index 107fc3b9..603aa35c 100644 --- a/src/projections/vandg.cpp +++ b/src/projections/vandg.cpp @@ -19,7 +19,7 @@ static PJ_XY vandg_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwar // Comments tie this formulation to Snyder (1987), p. 241. p2 = fabs(lp.phi / M_HALFPI); // sin(theta) from (29-6) if ((p2 - TOL) > 1.) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } if (p2 > 1.) @@ -56,7 +56,7 @@ static PJ_XY vandg_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwar // y from (29-2) has been expressed in terms of x here xy.y = 1. - xy.y * (xy.y + 2. * al); if (xy.y < -TOL) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } if (xy.y < 0.) @@ -95,7 +95,7 @@ static PJ_LP vandg_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers d = C2_27 * c2 * c2 * c2 + (c0 * c0 - THIRD * c2 * c1) / c3; // d (29-14) const double al_mul_m = al * m; // a1*m1 if( fabs(al_mul_m) < 1e-16 ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lp; } d = 3. * d /al_mul_m; // cos(3*theta1) (29-17) @@ -109,7 +109,7 @@ static PJ_LP vandg_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers lp.lam = fabs(xy.x) <= TOL ? 0. : .5 * (r - PISQ + (t <= 0. ? 0. : sqrt(t))) / xy.x; } else { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } diff --git a/src/projections/vandg2.cpp b/src/projections/vandg2.cpp index cd7e7b6c..b25c677a 100644 --- a/src/projections/vandg2.cpp +++ b/src/projections/vandg2.cpp @@ -55,7 +55,7 @@ static PJ_XY vandg2_s_forward (PJ_LP lp, PJ *P) { /* Spheroidal, forwa PJ *PROJECTION(vandg2) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->vdg3 = 0; @@ -67,7 +67,7 @@ PJ *PROJECTION(vandg2) { PJ *PROJECTION(vandg3) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->vdg3 = 1; diff --git a/src/projections/wag3.cpp b/src/projections/wag3.cpp index ed3250ef..4d61d803 100644 --- a/src/projections/wag3.cpp +++ b/src/projections/wag3.cpp @@ -37,7 +37,7 @@ PJ *PROJECTION(wag3) { double ts; struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; diff --git a/src/projections/wink1.cpp b/src/projections/wink1.cpp index f4ffafe3..0fbd6a77 100644 --- a/src/projections/wink1.cpp +++ b/src/projections/wink1.cpp @@ -35,7 +35,7 @@ static PJ_LP wink1_s_inverse (PJ_XY xy, PJ *P) { /* Spheroidal, invers PJ *PROJECTION(wink1) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; static_cast<struct pj_opaque*>(P->opaque)->cosphi1 = cos (pj_param(P->ctx, P->params, "rlat_ts").f); diff --git a/src/projections/wink2.cpp b/src/projections/wink2.cpp index b5b1e812..bd46930f 100644 --- a/src/projections/wink2.cpp +++ b/src/projections/wink2.cpp @@ -55,7 +55,7 @@ static PJ_LP wink2_s_inverse(PJ_XY xy, PJ *P) PJ *PROJECTION(wink2) { struct pj_opaque *Q = static_cast<struct pj_opaque*>(calloc (1, sizeof (struct pj_opaque))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; static_cast<struct pj_opaque*>(P->opaque)->cosphi1 = cos(pj_param(P->ctx, P->params, "rlat_1").f); diff --git a/src/strerrno.cpp b/src/strerrno.cpp index 3d0131c6..f94de55d 100644 --- a/src/strerrno.cpp +++ b/src/strerrno.cpp @@ -8,103 +8,68 @@ #include "proj_config.h" #include "proj_internal.h" -static const char * const -pj_err_list[] = { - "no arguments in initialization list", /* -1 */ - "no options found in 'init' file", /* -2 */ - "no colon in init= string", /* -3 */ - "projection not named", /* -4 */ - "unknown projection id", /* -5 */ - "effective eccentricity < 0 or >= 1.", /* -6 */ - "unknown unit conversion id", /* -7 */ - "invalid boolean param argument", /* -8 */ - "unknown elliptical parameter name", /* -9 */ - "reciprocal flattening (1/f) = 0", /* -10 */ - "|radius reference latitude| > 90", /* -11 */ - "squared eccentricity < 0", /* -12 */ - "major axis or radius = 0 or not given", /* -13 */ - "latitude or longitude exceeded limits", /* -14 */ - "invalid x or y", /* -15 */ - "improperly formed DMS value", /* -16 */ - "non-convergent inverse meridional dist", /* -17 */ - "non-convergent sinh(psi) to tan(phi)", /* -18 */ - "acos/asin: |arg| >1.+1e-14", /* -19 */ - "tolerance condition error", /* -20 */ - "conic lat_1 = -lat_2", /* -21 */ - "lat_0, lat_1 or lat_2 >= 90", /* -22 */ - "lat_1 = 0", /* -23 */ - "lat_ts >= 90", /* -24 */ - "no distance between control points", /* -25 */ - "projection not selected to be rotated", /* -26 */ - "W <= 0 or M <= 0", /* -27 */ - "lsat not in 1-5 range", /* -28 */ - "path not in range", /* -29 */ - "h <= 0 or h > 1e10 * a", /* -30 */ - "k <= 0", /* -31 */ - "lat_1=lat_2 or lat_1=0 or lat_2=90", /* -32 */ - "lat_0 = 0 or 90 or alpha = 90", /* -33 */ - "elliptical usage required", /* -34 */ - "invalid UTM zone number", /* -35 */ - "", /* no longer used */ /* -36 */ - "failed to find projection to be rotated", /* -37 */ - "failed to load datum shift file", /* -38 */ - "both n & m must be spec'd and > 0", /* -39 */ - "n <= 0, n > 1 or not specified", /* -40 */ - "lat_1 or lat_2 not specified", /* -41 */ - "|lat_1| == |lat_2|", /* -42 */ - "lat_0 is pi/2 from mean lat", /* -43 */ - "unparseable coordinate system definition", /* -44 */ - "geocentric transformation missing z or ellps", /* -45 */ - "unknown prime meridian conversion id", /* -46 */ - "illegal axis orientation combination", /* -47 */ - "point not within available datum shift grids", /* -48 */ - "invalid sweep axis, choose x or y", /* -49 */ - "malformed pipeline", /* -50 */ - "unit conversion factor must be > 0", /* -51 */ - "invalid scale", /* -52 */ - "non-convergent computation", /* -53 */ - "missing required arguments", /* -54 */ - "lat_0 = 0", /* -55 */ - "ellipsoidal usage unsupported", /* -56 */ - "only one +init allowed for non-pipeline operations", /* -57 */ - "argument not numerical or out of range", /* -58 */ - "inconsistent unit type between input and output", /* -59 */ - "arguments are mutually exclusive", /* -60 */ - "generic error of unknown origin", /* -61 */ - "network error", /* -62 */ - /* When adding error messages, remember to update ID defines in - src/proj_internal.h and src/apps/gie.cpp */ -}; +const char* proj_errno_string(int err) { + return proj_context_errno_string(pj_get_default_ctx(), err); +} +static const struct +{ + int num; + const char *str; +} error_strings[] = { -const char* proj_errno_string(int err) { - const int max_error = 9999; - static char note[50]; - size_t adjusted_err; + { PROJ_ERR_INVALID_OP_WRONG_SYNTAX, _("Invalid PROJ string syntax") }, + { PROJ_ERR_INVALID_OP_MISSING_ARG, _("Missing argument") }, + { PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE, _("Invalid value for an argument") }, + { PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS, _("Mutually exclusive arguments") }, + { PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID, _("File not found or invalid") }, + { PROJ_ERR_COORD_TRANSFM_INVALID_COORD, _("Invalid coordinate") }, + { PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN, _("Point outside of projection domain") }, + { PROJ_ERR_COORD_TRANSFM_NO_OPERATION, _("No operation matching criteria found for coordinate") }, + { PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID, _("Coordinate to transform falls outside grid") }, + { PROJ_ERR_COORD_TRANSFM_GRID_AT_NODATA, _("Coordinate to transform falls into a grid cell that evaluates to nodata") }, + { PROJ_ERR_OTHER_API_MISUSE, _("API misuse") }, + { PROJ_ERR_OTHER_NO_INVERSE_OP, _("No inverse operation") }, + { PROJ_ERR_OTHER_NETWORK_ERROR, _("Network error when accessing a remote resource") }, +}; + +const char PROJ_DLL * proj_context_errno_string(PJ_CONTEXT* ctx, int err) +{ + if( ctx == nullptr ) + ctx = pj_get_default_ctx(); if (0==err) return nullptr; - /* System error codes are positive */ - if (err > 0) { -#ifdef HAVE_STRERROR - return strerror(err); -#else - /* Defend string boundary against exorbitantly large err values */ - /* which may occur on platforms with 64-bit ints */ - sprintf(note, "no system list, errno: %d\n", - (err < max_error) ? err: max_error); - return note; -#endif + const char* str = nullptr; + for( const auto& num_str_pair: error_strings ) + { + if( err == num_str_pair.num ) + { + str = num_str_pair.str; + break; + } } - /* PROJ error codes are negative: -1 to -9999 */ - adjusted_err = err < -max_error ? max_error : -err - 1; - if (adjusted_err < (sizeof(pj_err_list) / sizeof(char *))) - return (char *)pj_err_list[adjusted_err]; + if( str == nullptr && err > 0 && (err & PROJ_ERR_INVALID_OP) != 0 ) + { + str = _("Unspecified error related to coordinate operation initialization"); + } + if( str == nullptr && err > 0 && (err & PROJ_ERR_COORD_TRANSFM) != 0 ) + { + str = _("Unspecified error related to coordinate transformation"); + } - sprintf(note, "invalid projection system error (%d)", - (err > -max_error) ? err: -max_error); - return note; + if (str) { + ctx->lastFullErrorMessage = str; + } + else + { + ctx->lastFullErrorMessage.resize(50); + snprintf(&ctx->lastFullErrorMessage[0], ctx->lastFullErrorMessage.size(), + _("Unknown error (code %d)"), err); + ctx->lastFullErrorMessage.resize(strlen(ctx->lastFullErrorMessage.data())); + } + return ctx->lastFullErrorMessage.c_str(); } diff --git a/src/transformations/affine.cpp b/src/transformations/affine.cpp index 43fd8642..e6a94f1e 100644 --- a/src/transformations/affine.cpp +++ b/src/transformations/affine.cpp @@ -154,7 +154,7 @@ static void computeReverseParameters(PJ* P) const double det = a * A + b * B + c * C; if( det == 0.0 || Q->forward.tscale == 0.0 ) { if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { - proj_log_debug(P, "Affine: matrix non invertible"); + proj_log_debug(P, "matrix non invertible"); } P->inv4d = nullptr; P->inv3d = nullptr; @@ -176,7 +176,7 @@ static void computeReverseParameters(PJ* P) PJ *TRANSFORMATION(affine,0 /* no need for ellipsoid */) { struct pj_opaque_affine *Q = initQ(); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = (void *) Q; P->fwd4d = forward_4d; @@ -227,7 +227,7 @@ PJ *TRANSFORMATION(affine,0 /* no need for ellipsoid */) { PJ *TRANSFORMATION(geogoffset,0 /* no need for ellipsoid */) { struct pj_opaque_affine *Q = initQ(); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = (void *) Q; P->fwd4d = forward_4d; diff --git a/src/transformations/defmodel.cpp b/src/transformations/defmodel.cpp index 3d0f2a58..0d9f6690 100644 --- a/src/transformations/defmodel.cpp +++ b/src/transformations/defmodel.cpp @@ -68,8 +68,7 @@ struct Grid : public GridPrototype { if (!checkedHorizontal) { const auto samplesPerPixel = realGrid->samplesPerPixel(); if (samplesPerPixel < 2) { - pj_log(ctx, PJ_LOG_ERROR, - "defmodel: grid %s has not enough samples", + pj_log(ctx, PJ_LOG_ERROR, "grid %s has not enough samples", realGrid->name().c_str()); return false; } @@ -90,15 +89,14 @@ struct Grid : public GridPrototype { } } if (foundDesc && (!foundDescX || !foundDescY)) { - pj_log(ctx, PJ_LOG_ERROR, - "defmodel: grid %s : Found band description, " - "but not the ones expected", + pj_log(ctx, PJ_LOG_ERROR, "grid %s : Found band description, " + "but not the ones expected", realGrid->name().c_str()); return false; } const auto unit = realGrid->unit(sampleX); if (!unit.empty() && unit != expectedUnit) { - pj_log(ctx, PJ_LOG_ERROR, "defmodel: grid %s : Only unit=%s " + pj_log(ctx, PJ_LOG_ERROR, "grid %s : Only unit=%s " "currently handled for this mode", realGrid->name().c_str(), expectedUnit.c_str()); return false; @@ -130,8 +128,7 @@ struct Grid : public GridPrototype { if (samplesPerPixel == 1) { sampleZ = 0; } else if (samplesPerPixel < 3) { - pj_log(ctx, PJ_LOG_ERROR, - "defmodel: grid %s has not enough samples", + pj_log(ctx, PJ_LOG_ERROR, "grid %s has not enough samples", realGrid->name().c_str()); return false; } @@ -148,17 +145,15 @@ struct Grid : public GridPrototype { } } if (foundDesc && !foundDescZ) { - pj_log(ctx, PJ_LOG_ERROR, - "defmodel: grid %s : Found band description, " - "but not the ones expected", + pj_log(ctx, PJ_LOG_ERROR, "grid %s : Found band description, " + "but not the ones expected", realGrid->name().c_str()); return false; } const auto unit = realGrid->unit(sampleZ); if (!unit.empty() && unit != STR_METRE) { - pj_log(ctx, PJ_LOG_ERROR, - "defmodel: grid %s : Only unit=metre currently " - "handled for this mode", + pj_log(ctx, PJ_LOG_ERROR, "grid %s : Only unit=metre currently " + "handled for this mode", realGrid->name().c_str()); return false; } @@ -256,8 +251,7 @@ struct EvaluatorIface : public EvaluatorIfacePrototype<Grid, GridSet> { std::unique_ptr<GridSet> open(const std::string &filename) { auto realGridSet = NS_PROJ::GenericShiftGridSet::open(ctx, filename); if (!realGridSet) { - pj_log(ctx, PJ_LOG_ERROR, "defmodel: cannot open %s", - filename.c_str()); + pj_log(ctx, PJ_LOG_ERROR, "cannot open %s", filename.c_str()); return nullptr; } return std::unique_ptr<GridSet>( @@ -390,7 +384,7 @@ PJ *TRANSFORMATION(defmodel, 1) { // Pass a dummy ellipsoid definition that will be overridden just afterwards auto cart = proj_create(P->ctx, "+proj=cart +a=1"); if (cart == nullptr) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); /* inherit ellipsoid definition from P to Q->cart */ pj_inherit_ellipsoid_def(P, cart); @@ -402,14 +396,14 @@ PJ *TRANSFORMATION(defmodel, 1) { const char *model = pj_param(P->ctx, P->params, "smodel").s; if (!model) { - proj_log_error(P, "defmodel: +model= should be specified."); - return destructor(P, PJD_ERR_NO_ARGS); + proj_log_error(P, _("+model= should be specified.")); + return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } auto file = NS_PROJ::FileManager::open_resource_file(P->ctx, model); if (nullptr == file) { - proj_log_error(P, "defmodel: Cannot open %s", model); - return destructor(P, PJD_ERR_INVALID_ARG); + proj_log_error(P, _("Cannot open %s"), model); + return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } file->seek(0, SEEK_END); unsigned long long size = file->tell(); @@ -417,23 +411,23 @@ PJ *TRANSFORMATION(defmodel, 1) { // that could be a denial of service risk. 10 MB should be sufficiently // large for any valid use ! if (size > 10 * 1024 * 1024) { - proj_log_error(P, "defmodel: File %s too large", model); - return destructor(P, PJD_ERR_INVALID_ARG); + proj_log_error(P, _("File %s too large"), model); + return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } file->seek(0); std::string jsonStr; jsonStr.resize(static_cast<size_t>(size)); if (file->read(&jsonStr[0], jsonStr.size()) != jsonStr.size()) { - proj_log_error(P, "defmodel: Cannot read %s", model); - return destructor(P, PJD_ERR_INVALID_ARG); + proj_log_error(P, _("Cannot read %s"), model); + return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } try { Q->evaluator.reset(new Evaluator<Grid, GridSet, EvaluatorIface>( MasterFile::parse(jsonStr), Q->evaluatorIface, P->a, P->b)); } catch (const std::exception &e) { - proj_log_error(P, "defmodel: invalid model: %s", e.what()); - return destructor(P, PJD_ERR_INVALID_ARG); + proj_log_error(P, _("invalid model: %s"), e.what()); + return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } P->fwd4d = forward_4d; diff --git a/src/transformations/deformation.cpp b/src/transformations/deformation.cpp index 8ce02bee..1a04d0f5 100644 --- a/src/transformations/deformation.cpp +++ b/src/transformations/deformation.cpp @@ -100,7 +100,7 @@ static bool get_grid_values(PJ* P, } const auto samplesPerPixel = grid->samplesPerPixel(); if( samplesPerPixel < 3 ) { - proj_log_error(P, "deformation: grid has not enough samples"); + proj_log_error(P, "grid has not enough samples"); return false; } int sampleE = 0; @@ -119,7 +119,7 @@ static bool get_grid_values(PJ* P, } const auto unit = grid->unit(sampleE); if( !unit.empty() && unit != "millimetres per year" ) { - proj_log_error(P, "deformation: Only unit=millimetres per year currently handled"); + proj_log_error(P, "Only unit=millimetres per year currently handled"); return false; } @@ -179,8 +179,8 @@ static PJ_XYZ get_grid_shift(PJ* P, const PJ_XYZ& cartesian) { shift.lp = pj_hgrid_value(P, Q->hgrids, geodetic.lp); shift.enu.u = pj_vgrid_value(P, Q->vgrids, geodetic.lp, 1.0); - if (proj_errno(P) == PJD_ERR_GRID_AREA) - proj_log_debug(P, "deformation: coordinate (%.3f, %.3f) outside deformation model", + if (proj_errno(P) == PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID) + proj_log_debug(P, "coordinate (%.3f, %.3f) outside deformation model", proj_todeg(geodetic.lpz.lam), proj_todeg(geodetic.lpz.phi)); /* grid values are stored as mm/yr, we need m/yr */ @@ -261,7 +261,7 @@ static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) { if (Q->dt == HUGE_VAL) { out = proj_coord_error(); /* in the 3D case +t_obs must be specified */ - proj_log_debug(P, "deformation: +dt must be specified"); + proj_log_debug(P, "+dt must be specified"); return out.xyz; } @@ -308,7 +308,7 @@ static PJ_LPZ reverse_3d(PJ_XYZ in, PJ *P) { if (Q->dt == HUGE_VAL) { out = proj_coord_error(); /* in the 3D case +t_obs must be specified */ - proj_log_debug(P, "deformation: +dt must be specified"); + proj_log_debug(P, "+dt must be specified"); return out.lpz; } @@ -358,7 +358,7 @@ PJ *TRANSFORMATION(deformation,1) { // Pass a dummy ellipsoid definition that will be overridden just afterwards Q->cart = proj_create(P->ctx, "+proj=cart +a=1"); if (Q->cart == nullptr) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); /* inherit ellipsoid definition from P to Q->cart */ pj_inherit_ellipsoid_def (P, Q->cart); @@ -369,8 +369,8 @@ PJ *TRANSFORMATION(deformation,1) { /* Build gridlists. Both horizontal and vertical grids are mandatory. */ if ( !has_grids && (!has_xy_grids || !has_z_grids)) { - proj_log_error(P, "deformation: Either +grids or (+xy_grids and +z_grids) should be specified."); - return destructor(P, PJD_ERR_NO_ARGS ); + proj_log_error(P, _("Either +grids or (+xy_grids and +z_grids) should be specified.")); + return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG ); } if( has_grids ) @@ -378,22 +378,22 @@ PJ *TRANSFORMATION(deformation,1) { Q->grids = pj_generic_grid_init(P, "grids"); /* Was gridlist compiled properly? */ if ( proj_errno(P) ) { - proj_log_error(P, "deformation: could not find required grid(s)."); - return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_log_error(P, _("could not find required grid(s).)")); + return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } } else { Q->hgrids = pj_hgrid_init(P, "xy_grids"); if (proj_errno(P)) { - proj_log_error(P, "deformation: could not find requested xy_grid(s)."); - return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_log_error(P, _("could not find requested xy_grid(s).")); + return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } Q->vgrids = pj_vgrid_init(P, "z_grids"); if (proj_errno(P)) { - proj_log_error(P, "deformation: could not find requested z_grid(s)."); - return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_log_error(P, _("could not find requested z_grid(s).")); + return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } } @@ -403,8 +403,8 @@ PJ *TRANSFORMATION(deformation,1) { } if (pj_param_exists(P->params, "t_obs")) { - proj_log_error(P, "deformation: +t_obs parameter is deprecated. Use +dt instead."); - return destructor(P, PJD_ERR_MISSING_ARGS); + proj_log_error(P, _("+t_obs parameter is deprecated. Use +dt instead.")); + return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } Q->t_epoch = HUGE_VAL; @@ -413,13 +413,13 @@ PJ *TRANSFORMATION(deformation,1) { } if (Q->dt == HUGE_VAL && Q->t_epoch == HUGE_VAL) { - proj_log_error(P, "deformation: either +dt or +t_epoch needs to be set."); - return destructor(P, PJD_ERR_MISSING_ARGS); + proj_log_error(P, _("either +dt or +t_epoch needs to be set.")); + return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if (Q->dt != HUGE_VALL && Q->t_epoch != HUGE_VALL) { - proj_log_error(P, "deformation: +dt or +t_epoch are mutually exclusive."); - return destructor(P, PJD_ERR_MUTUALLY_EXCLUSIVE_ARGS); + proj_log_error(P, _("+dt or +t_epoch are mutually exclusive.")); + return destructor(P, PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS); } P->fwd4d = forward_4d; diff --git a/src/transformations/helmert.cpp b/src/transformations/helmert.cpp index 99aa74a4..f9bb45e0 100644 --- a/src/transformations/helmert.cpp +++ b/src/transformations/helmert.cpp @@ -478,7 +478,7 @@ static PJ_COORD helmert_reverse_4d (PJ_COORD point, PJ *P) { static PJ* init_helmert_six_parameters(PJ* P) { struct pj_opaque_helmert *Q = static_cast<struct pj_opaque_helmert*>(calloc (1, sizeof (struct pj_opaque_helmert))); if (nullptr==Q) - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = (void *) Q; /* In most cases, we work on 3D cartesian coordinates */ @@ -522,8 +522,8 @@ static PJ* read_convention(PJ* P) { if (!Q->no_rotation) { const char* convention = pj_param (P->ctx, P->params, "sconvention").s; if( !convention ) { - proj_log_error (P, "helmert: missing 'convention' argument"); - return pj_default_destructor (P, PJD_ERR_MISSING_ARGS); + proj_log_error (P, _("helmert: missing 'convention' argument")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if( strcmp(convention, "position_vector") == 0 ) { Q->is_position_vector = 1; @@ -532,17 +532,17 @@ static PJ* read_convention(PJ* P) { Q->is_position_vector = 0; } else { - proj_log_error (P, "helmert: invalid value for 'convention' argument"); - return pj_default_destructor (P, PJD_ERR_INVALID_ARG); + proj_log_error (P, _("helmert: invalid value for 'convention' argument")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } /* historically towgs84 in PROJ has always been using position_vector * convention. Accepting coordinate_frame would be confusing. */ if (pj_param_exists (P->params, "towgs84")) { if( !Q->is_position_vector ) { - proj_log_error (P, "helmert: towgs84 should only be used with " - "convention=position_vector"); - return pj_default_destructor (P, PJD_ERR_INVALID_ARG); + proj_log_error (P, _("helmert: towgs84 should only be used with " + "convention=position_vector")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } } @@ -578,9 +578,9 @@ PJ *TRANSFORMATION(helmert, 0) { /* Detect obsolete transpose flag and error out if found */ if (pj_param (P->ctx, P->params, "ttranspose").i) { - proj_log_error (P, "helmert: 'transpose' argument is no longer valid. " - "Use convention=position_vector/coordinate_frame"); - return pj_default_destructor (P, PJD_ERR_INVALID_ARG); + proj_log_error (P, _("helmert: 'transpose' argument is no longer valid. " + "Use convention=position_vector/coordinate_frame")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } /* Support the classic PROJ towgs84 parameter, but allow later overrides.*/ @@ -612,9 +612,15 @@ PJ *TRANSFORMATION(helmert, 0) { if (pj_param (P->ctx, P->params, "ts").i) { Q->scale_0 = pj_param (P->ctx, P->params, "ds").f; if( Q->scale_0 <= -1.0e6 ) - return pj_default_destructor (P, PJD_ERR_INVALID_SCALE); + { + proj_log_error (P, _("helmert: invalid value for s.")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } if (pj_param (P->ctx, P->params, "ttheta").i && Q->scale_0 == 0.0) - return pj_default_destructor (P, PJD_ERR_INVALID_SCALE); + { + proj_log_error (P, _("helmert: invalid value for s.")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } } /* Translation rates */ diff --git a/src/transformations/hgridshift.cpp b/src/transformations/hgridshift.cpp index b28eaf48..326bbb13 100644 --- a/src/transformations/hgridshift.cpp +++ b/src/transformations/hgridshift.cpp @@ -155,8 +155,8 @@ PJ *TRANSFORMATION(hgridshift,0) { P->right = PJ_IO_UNITS_RADIANS; if (0==pj_param(P->ctx, P->params, "tgrids").i) { - proj_log_error(P, "hgridshift: +grids parameter missing."); - return destructor (P, PJD_ERR_NO_ARGS); + proj_log_error(P, _("+grids parameter missing.")); + return destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG); } /* TODO: Refactor into shared function that can be used */ @@ -194,8 +194,8 @@ PJ *TRANSFORMATION(hgridshift,0) { Q->grids = pj_hgrid_init(P, "grids"); /* Was gridlist compiled properly? */ if ( proj_errno(P) ) { - proj_log_error(P, "hgridshift: could not find required grid(s)."); - return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_log_error(P, _("could not find required grid(s).")); + return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } gMutex.lock(); diff --git a/src/transformations/horner.cpp b/src/transformations/horner.cpp index 2c049186..7c8ad192 100644 --- a/src/transformations/horner.cpp +++ b/src/transformations/horner.cpp @@ -115,7 +115,7 @@ struct horner { } // anonymous namespace typedef struct horner HORNER; -static PJ_UV horner_func (const HORNER *transformation, PJ_DIRECTION direction, PJ_UV position); +static PJ_UV horner_func (PJ* P, const HORNER *transformation, PJ_DIRECTION direction, PJ_UV position); static HORNER *horner_alloc (size_t order, int complex_polynomia); static void horner_free (HORNER *h); @@ -181,7 +181,7 @@ static HORNER *horner_alloc (size_t order, int complex_polynomia) { /**********************************************************************/ -static PJ_UV horner_func (const HORNER *transformation, PJ_DIRECTION direction, PJ_UV position) { +static PJ_UV horner_func (PJ* P, const HORNER *transformation, PJ_DIRECTION direction, PJ_UV position) { /*********************************************************************** A reimplementation of the classic Engsager/Poder 2D Horner polynomial @@ -237,7 +237,6 @@ summing the tiny high order elements first. case PJ_INV: /* inverse */ break; default: /* invalid */ - errno = EINVAL; return uv_error; } @@ -259,7 +258,7 @@ summing the tiny high order elements first. } if ((fabs(n) > range) || (fabs(e) > range)) { - errno = EDOM; + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return uv_error; } @@ -297,12 +296,12 @@ summing the tiny high order elements first. static PJ_COORD horner_forward_4d (PJ_COORD point, PJ *P) { - point.uv = horner_func ((HORNER *) P->opaque, PJ_FWD, point.uv); + point.uv = horner_func (P, (HORNER *) P->opaque, PJ_FWD, point.uv); return point; } static PJ_COORD horner_reverse_4d (PJ_COORD point, PJ *P) { - point.uv = horner_func ((HORNER *) P->opaque, PJ_INV, point.uv); + point.uv = horner_func (P, (HORNER *) P->opaque, PJ_INV, point.uv); return point; } @@ -310,7 +309,7 @@ static PJ_COORD horner_reverse_4d (PJ_COORD point, PJ *P) { /**********************************************************************/ -static PJ_UV complex_horner (const HORNER *transformation, PJ_DIRECTION direction, PJ_UV position) { +static PJ_UV complex_horner (PJ *P, const HORNER *transformation, PJ_DIRECTION direction, PJ_UV position) { /*********************************************************************** A reimplementation of a classic Engsager/Poder Horner complex @@ -337,7 +336,6 @@ polynomial evaluation engine. case PJ_INV: /* inverse */ break; default: /* invalid */ - errno = EINVAL; return uv_error; } @@ -366,7 +364,7 @@ polynomial evaluation engine. } if ((fabs(n) > range) || (fabs(e) > range)) { - errno = EDOM; + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return uv_error; } @@ -387,12 +385,12 @@ polynomial evaluation engine. static PJ_COORD complex_horner_forward_4d (PJ_COORD point, PJ *P) { - point.uv = complex_horner ((HORNER *) P->opaque, PJ_FWD, point.uv); + point.uv = complex_horner (P, (HORNER *) P->opaque, PJ_FWD, point.uv); return point; } static PJ_COORD complex_horner_reverse_4d (PJ_COORD point, PJ *P) { - point.uv = complex_horner ((HORNER *) P->opaque, PJ_INV, point.uv); + point.uv = complex_horner (P, (HORNER *) P->opaque, PJ_INV, point.uv); return point; } @@ -414,7 +412,7 @@ static int parse_coefs (PJ *P, double *coefs, const char *param, int ncoefs) { buf = static_cast<char*>(calloc (strlen (param) + 2, sizeof(char))); if (nullptr==buf) { - proj_log_error (P, "Horner: No memory left"); + proj_log_error (P, "No memory left"); return 0; } @@ -430,7 +428,7 @@ static int parse_coefs (PJ *P, double *coefs, const char *param, int ncoefs) { for (i = 0; i < ncoefs; i++) { if (i > 0) { if ( next == nullptr || ','!=*next) { - proj_log_error (P, "Horner: Malformed polynomium set %s. need %d coefs", param, ncoefs); + proj_log_error (P, "Malformed polynomium set %s. need %d coefs", param, ncoefs); return 0; } init = ++next; @@ -460,12 +458,12 @@ PJ *PROJECTION(horner) { degree = pj_param(P->ctx, P->params, "ideg").i; if (degree < 0 || degree > 10000) { /* What are reasonable minimum and maximums for degree? */ - proj_log_debug (P, "Horner: Degree is unreasonable: %d", degree); - return horner_freeup (P, PJD_ERR_INVALID_ARG); + proj_log_error (P, _("Degree is unreasonable: %d"), degree); + return horner_freeup (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } else { - proj_log_debug (P, "Horner: Must specify polynomial degree, (+deg=n)"); - return horner_freeup (P, PJD_ERR_MISSING_ARGS); + proj_log_error (P, _("Must specify polynomial degree, (+deg=n)")); + return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if (pj_param (P->ctx, P->params, "tfwd_c").i || pj_param (P->ctx, P->params, "tinv_c").i) /* complex polynomium? */ @@ -473,7 +471,7 @@ PJ *PROJECTION(horner) { Q = horner_alloc (degree, complex_polynomia); if (Q == nullptr) - return horner_freeup (P, ENOMEM); + return horner_freeup (P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (complex_polynomia) { @@ -483,9 +481,15 @@ PJ *PROJECTION(horner) { n = 2*degree + 2; if (0==parse_coefs (P, Q->fwd_c, "fwd_c", n)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); + { + proj_log_error (P, _("missing fwd_c")); + return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG); + } if (0==parse_coefs (P, Q->inv_c, "inv_c", n)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); + { + proj_log_error (P, _("missing inv_c")); + return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG); + } P->fwd4d = complex_horner_forward_4d; P->inv4d = complex_horner_reverse_4d; } @@ -493,19 +497,37 @@ PJ *PROJECTION(horner) { else { n = horner_number_of_coefficients (degree); if (0==parse_coefs (P, Q->fwd_u, "fwd_u", n)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); + { + proj_log_error (P, _("missing fwd_u")); + return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG); + } if (0==parse_coefs (P, Q->fwd_v, "fwd_v", n)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); + { + proj_log_error (P, _("missing fwd_v")); + return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG); + } if (0==parse_coefs (P, Q->inv_u, "inv_u", n)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); + { + proj_log_error (P, _("missing inv_u")); + return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG); + } if (0==parse_coefs (P, Q->inv_v, "inv_v", n)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); + { + proj_log_error (P, _("missing inv_v")); + return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG); + } } if (0==parse_coefs (P, (double *)(Q->fwd_origin), "fwd_origin", 2)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); + { + proj_log_error (P, _("missing fwd_origin")); + return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG); + } if (0==parse_coefs (P, (double *)(Q->inv_origin), "inv_origin", 2)) - return horner_freeup (P, PJD_ERR_MISSING_ARGS); + { + proj_log_error (P, _("missing inv_origin")); + return horner_freeup (P, PROJ_ERR_INVALID_OP_MISSING_ARG); + } if (0==parse_coefs (P, &Q->range, "range", 1)) Q->range = 500000; diff --git a/src/transformations/molodensky.cpp b/src/transformations/molodensky.cpp index bf5960d2..70cf987b 100644 --- a/src/transformations/molodensky.cpp +++ b/src/transformations/molodensky.cpp @@ -245,7 +245,7 @@ static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) { lpz = calc_standard_params(lpz, P); } if( lpz.lam == HUGE_VAL ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xyz; } @@ -277,7 +277,7 @@ static PJ_LPZ reverse_3d(PJ_XYZ xyz, PJ *P) { lpz = calc_standard_params(point.lpz, P); if( lpz.lam == HUGE_VAL ) { - proj_errno_set(P, PJD_ERR_TOLERANCE_CONDITION); + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lpz; } @@ -297,10 +297,9 @@ static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) { PJ *TRANSFORMATION(molodensky,1) { - int count_required_params = 0; struct pj_opaque_molodensky *Q = static_cast<struct pj_opaque_molodensky*>(calloc(1, sizeof(struct pj_opaque_molodensky))); if (nullptr==Q) - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = (void *) Q; P->fwd4d = forward_4d; @@ -314,39 +313,42 @@ PJ *TRANSFORMATION(molodensky,1) { P->right = PJ_IO_UNITS_RADIANS; /* read args */ - if (pj_param(P->ctx, P->params, "tdx").i) { - count_required_params ++; - Q->dx = pj_param(P->ctx, P->params, "ddx").f; + if (!pj_param(P->ctx, P->params, "tdx").i) + { + proj_log_error (P, _("missing dx")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG); } + Q->dx = pj_param(P->ctx, P->params, "ddx").f; - if (pj_param(P->ctx, P->params, "tdy").i) { - count_required_params ++; - Q->dy = pj_param(P->ctx, P->params, "ddy").f; + if (!pj_param(P->ctx, P->params, "tdy").i) + { + proj_log_error (P, _("missing dy")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG); } + Q->dy = pj_param(P->ctx, P->params, "ddy").f; - if (pj_param(P->ctx, P->params, "tdz").i) { - count_required_params ++; - Q->dz = pj_param(P->ctx, P->params, "ddz").f; + if (!pj_param(P->ctx, P->params, "tdz").i) + { + proj_log_error (P, _("missing dz")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG); } + Q->dz = pj_param(P->ctx, P->params, "ddz").f; - if (pj_param(P->ctx, P->params, "tda").i) { - count_required_params ++; - Q->da = pj_param(P->ctx, P->params, "dda").f; + if (!pj_param(P->ctx, P->params, "tda").i) + { + proj_log_error (P, _("missing da")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG); } + Q->da = pj_param(P->ctx, P->params, "dda").f; - if (pj_param(P->ctx, P->params, "tdf").i) { - count_required_params ++; - Q->df = pj_param(P->ctx, P->params, "ddf").f; + if (!pj_param(P->ctx, P->params, "tdf").i) + { + proj_log_error (P, _("missing df")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG); } + Q->df = pj_param(P->ctx, P->params, "ddf").f; Q->abridged = pj_param(P->ctx, P->params, "tabridged").i; - /* We want all parameters (except +abridged) to be set */ - if (count_required_params == 0) - return pj_default_destructor(P, PJD_ERR_NO_ARGS); - - if (count_required_params != 5) - return pj_default_destructor(P, PJD_ERR_MISSING_ARGS); - return P; } diff --git a/src/transformations/tinshift.cpp b/src/transformations/tinshift.cpp index 96e0ea4f..51e063eb 100644 --- a/src/transformations/tinshift.cpp +++ b/src/transformations/tinshift.cpp @@ -86,14 +86,14 @@ PJ *TRANSFORMATION(tinshift, 1) { const char *filename = pj_param(P->ctx, P->params, "sfile").s; if (!filename) { - proj_log_error(P, "tinshift: +file= should be specified."); - return destructor(P, PJD_ERR_NO_ARGS); + proj_log_error(P, _("+file= should be specified.")); + return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } auto file = NS_PROJ::FileManager::open_resource_file(P->ctx, filename); if (nullptr == file) { - proj_log_error(P, "tinshift: Cannot open %s", filename); - return destructor(P, PJD_ERR_INVALID_ARG); + proj_log_error(P, _("Cannot open %s"), filename); + return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } file->seek(0, SEEK_END); unsigned long long size = file->tell(); @@ -101,15 +101,15 @@ PJ *TRANSFORMATION(tinshift, 1) { // that could be a denial of service risk. 10 MB should be sufficiently // large for any valid use ! if (size > 10 * 1024 * 1024) { - proj_log_error(P, "tinshift: File %s too large", filename); - return destructor(P, PJD_ERR_INVALID_ARG); + proj_log_error(P, _("File %s too large"), filename); + return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } file->seek(0); std::string jsonStr; jsonStr.resize(static_cast<size_t>(size)); if (file->read(&jsonStr[0], jsonStr.size()) != jsonStr.size()) { - proj_log_error(P, "tinshift: Cannot read %s", filename); - return destructor(P, PJD_ERR_INVALID_ARG); + proj_log_error(P, _("Cannot read %s"), filename); + return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } auto Q = new tinshiftData(); @@ -119,8 +119,8 @@ PJ *TRANSFORMATION(tinshift, 1) { try { Q->evaluator.reset(new Evaluator(TINShiftFile::parse(jsonStr))); } catch (const std::exception &e) { - proj_log_error(P, "tinshift: invalid model: %s", e.what()); - return destructor(P, PJD_ERR_INVALID_ARG); + proj_log_error(P, _("invalid model: %s"), e.what()); + return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } P->destructor = destructor; diff --git a/src/transformations/vgridshift.cpp b/src/transformations/vgridshift.cpp index 3d9f046a..7b234517 100644 --- a/src/transformations/vgridshift.cpp +++ b/src/transformations/vgridshift.cpp @@ -179,8 +179,8 @@ PJ *TRANSFORMATION(vgridshift,0) { P->reassign_context = reassign_context; if (!pj_param(P->ctx, P->params, "tgrids").i) { - proj_log_error(P, "vgridshift: +grids parameter missing."); - return destructor(P, PJD_ERR_NO_ARGS); + proj_log_error(P, _("+grids parameter missing.")); + return destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG); } /* TODO: Refactor into shared function that can be used */ @@ -227,8 +227,8 @@ PJ *TRANSFORMATION(vgridshift,0) { /* Was gridlist compiled properly? */ if ( proj_errno(P) ) { - proj_log_error(P, "vgridshift: could not find required grid(s)."); - return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_log_error(P, _("could not find required grid(s).")); + return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } gMutex.lock(); diff --git a/src/transformations/xyzgridshift.cpp b/src/transformations/xyzgridshift.cpp index e37e874d..c75944ba 100644 --- a/src/transformations/xyzgridshift.cpp +++ b/src/transformations/xyzgridshift.cpp @@ -257,7 +257,7 @@ PJ *TRANSFORMATION(xyzgridshift,0) { // Pass a dummy ellipsoid definition that will be overridden just afterwards Q->cart = proj_create(P->ctx, "+proj=cart +a=1"); if (Q->cart == nullptr) - return destructor(P, ENOMEM); + return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); /* inherit ellipsoid definition from P to Q->cart */ pj_inherit_ellipsoid_def (P, Q->cart); @@ -272,14 +272,14 @@ PJ *TRANSFORMATION(xyzgridshift,0) { // in RGF93 Q->grid_ref_is_input = false; } else { - proj_log_error(P, "xyzgridshift: unusupported value for grid_ref"); - return destructor (P, PJD_ERR_NO_ARGS); + proj_log_error(P, _("unusupported value for grid_ref")); + return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } if (0==pj_param(P->ctx, P->params, "tgrids").i) { - proj_log_error(P, "xyzgridshift: +grids parameter missing."); - return destructor (P, PJD_ERR_NO_ARGS); + proj_log_error(P, _("+grids parameter missing.")); + return destructor (P, PROJ_ERR_INVALID_OP_MISSING_ARG); } /* multiplier for delta x,y,z */ @@ -294,8 +294,8 @@ PJ *TRANSFORMATION(xyzgridshift,0) { Q->grids = pj_generic_grid_init(P, "grids"); /* Was gridlist compiled properly? */ if ( proj_errno(P) ) { - proj_log_error(P, "xyzgridshift: could not find required grid(s)."); - return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID); + proj_log_error(P, _("could not find required grid(s).")); + return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } } |
