diff options
Diffstat (limited to 'src')
35 files changed, 1145 insertions, 1187 deletions
diff --git a/src/4D_api.cpp b/src/4D_api.cpp index 6b53363a..71393dab 100644 --- a/src/4D_api.cpp +++ b/src/4D_api.cpp @@ -195,62 +195,93 @@ similarly, but prefers the 2D resp. 3D interfaces if available. direction = opposite_direction(direction); if( !P->alternativeCoordinateOperations.empty() ) { - // Do a first pass and select the operations that match the area of use - // and has the best accuracy. - int iBest = -1; - double bestAccuracy = std::numeric_limits<double>::max(); - int i = 0; - for( const auto &alt: P->alternativeCoordinateOperations ) { - bool spatialCriterionOK = false; - if( direction == PJ_FWD ) { - if( coord.xyzt.x >= alt.minxSrc && - coord.xyzt.y >= alt.minySrc && - coord.xyzt.x <= alt.maxxSrc && - coord.xyzt.y <= alt.maxySrc) { - spatialCriterionOK = true; + constexpr int N_MAX_RETRY = 2; + int iExcluded[N_MAX_RETRY] = {-1, -1}; + + const int nOperations = static_cast<int>( + P->alternativeCoordinateOperations.size()); + + // We may need several attempts. For example the point at + // lon=-111.5 lat=45.26 falls into the bounding box of the Canadian + // ntv2_0.gsb grid, except that it is not in any of the subgrids, being + // in the US. We thus need another retry that will select the conus + // grid. + for( int iRetry = 0; iRetry <= N_MAX_RETRY; iRetry++ ) + { + // Do a first pass and select the operations that match the area of use + // and has the best accuracy. + int iBest = -1; + double bestAccuracy = std::numeric_limits<double>::max(); + for( int i = 0; i < nOperations; i++ ) { + if( i == iExcluded[0] || i == iExcluded[1] ) { + continue; } - } else { - if( coord.xyzt.x >= alt.minxDst && - coord.xyzt.y >= alt.minyDst && - coord.xyzt.x <= alt.maxxDst && - coord.xyzt.y <= alt.maxyDst ) { - spatialCriterionOK = true; + const auto &alt = P->alternativeCoordinateOperations[i]; + bool spatialCriterionOK = false; + if( direction == PJ_FWD ) { + if( coord.xyzt.x >= alt.minxSrc && + coord.xyzt.y >= alt.minySrc && + coord.xyzt.x <= alt.maxxSrc && + coord.xyzt.y <= alt.maxySrc) { + spatialCriterionOK = true; + } + } else { + if( coord.xyzt.x >= alt.minxDst && + coord.xyzt.y >= alt.minyDst && + coord.xyzt.x <= alt.maxxDst && + coord.xyzt.y <= alt.maxyDst ) { + spatialCriterionOK = true; + } } - } - if( spatialCriterionOK ) { - // The offshore test is for the "Test bug 245 (use +datum=carthage)" - // of testvarious. The long=10 lat=34 point belongs both to the - // onshore and offshore Tunisia area of uses, but is slightly - // onshore. So in a general way, prefer a onshore area to a - // offshore one. - if( iBest < 0 || - (alt.accuracy >= 0 && alt.accuracy < bestAccuracy && - !alt.isOffshore) ) { - iBest = i; - bestAccuracy = alt.accuracy; + if( spatialCriterionOK ) { + // The offshore test is for the "Test bug 245 (use +datum=carthage)" + // of testvarious. The long=10 lat=34 point belongs both to the + // onshore and offshore Tunisia area of uses, but is slightly + // onshore. So in a general way, prefer a onshore area to a + // offshore one. + if( iBest < 0 || + (alt.accuracy >= 0 && alt.accuracy < bestAccuracy && + !alt.isOffshore) ) { + iBest = i; + bestAccuracy = alt.accuracy; + } } } - i ++; - } + if( iBest < 0 ) { + break; + } - if( iBest >= 0 ) { const auto& alt = P->alternativeCoordinateOperations[iBest]; if( P->iCurCoordOp != iBest ) { - std::string msg("Using coordinate operation "); - msg += alt.name; - pj_log(P->ctx, PJ_LOG_TRACE, msg.c_str()); + if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { + std::string msg("Using coordinate operation "); + msg += alt.name; + pj_log(P->ctx, PJ_LOG_DEBUG, msg.c_str()); + } P->iCurCoordOp = iBest; } - return direction == PJ_FWD ? + 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 ) { + return proj_coord_error (); + } + if( res.xyzt.x != HUGE_VAL ) { + return res; + } + pj_log(P->ctx, PJ_LOG_DEBUG, + "Did not result in valid result. " + "Attempting a retry with another operation."); + if( iRetry == N_MAX_RETRY ) { + break; + } + iExcluded[iRetry] = iBest; } // In case we did not find an operation whose area of use is compatible // with the input coordinate, then goes through again the list, and // use the first operation that does not require grids. - i = 0; NS_PROJ::io::DatabaseContextPtr dbContext; try { @@ -259,15 +290,18 @@ similarly, but prefers the 2D resp. 3D interfaces if available. } } catch( const std::exception& ) {} - for( const auto &alt: P->alternativeCoordinateOperations ) { + for( int i = 0; i < nOperations; i++ ) { + const auto &alt = P->alternativeCoordinateOperations[i]; auto coordOperation = dynamic_cast< NS_PROJ::operation::CoordinateOperation*>(alt.pj->iso_obj.get()); if( coordOperation ) { if( coordOperation->gridsNeeded(dbContext, true).empty() ) { if( P->iCurCoordOp != i ) { - std::string msg("Using coordinate operation "); - msg += alt.name; - pj_log(P->ctx, PJ_LOG_TRACE, msg.c_str()); + if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { + std::string msg("Using coordinate operation "); + msg += alt.name; + pj_log(P->ctx, PJ_LOG_DEBUG, msg.c_str()); + } P->iCurCoordOp = i; } if( direction == PJ_FWD ) { @@ -278,7 +312,6 @@ similarly, but prefers the 2D resp. 3D interfaces if available. } } } - i++; } proj_errno_set (P, EINVAL); diff --git a/src/Makefile.am b/src/Makefile.am index 9b513fb3..8ab30a33 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,11 +7,11 @@ TESTS = geodtest check_PROGRAMS = geodtest AM_CPPFLAGS = -DPROJ_LIB=\"$(pkgdatadir)\" \ - -DMUTEX_@MUTEX_SETTING@ @JNI_INCLUDE@ -I$(top_srcdir)/include @SQLITE3_CFLAGS@ @TIFF_CFLAGS@ @TIFF_ENABLED_FLAGS@ @CURL_CFLAGS@ @CURL_ENABLED_FLAGS@ + -DMUTEX_@MUTEX_SETTING@ -I$(top_srcdir)/include @SQLITE3_CFLAGS@ @TIFF_CFLAGS@ @TIFF_ENABLED_FLAGS@ @CURL_CFLAGS@ @CURL_ENABLED_FLAGS@ AM_CXXFLAGS = @CXX_WFLAGS@ @FLTO_FLAG@ include_HEADERS = proj.h proj_experimental.h proj_constants.h proj_api.h geodesic.h \ - org_proj4_PJ.h proj_symbol_rename.h + proj_symbol_rename.h EXTRA_DIST = bin_cct.cmake bin_gie.cmake bin_cs2cs.cmake \ bin_geod.cmake bin_proj.cmake bin_projinfo.cmake \ @@ -42,7 +42,7 @@ geodtest_LDADD = libproj.la lib_LTLIBRARIES = libproj.la -libproj_la_LDFLAGS = -no-undefined -version-info 17:0:2 +libproj_la_LDFLAGS = -no-undefined -version-info 18:0:3 libproj_la_LIBADD = @SQLITE3_LIBS@ @TIFF_LIBS@ @CURL_LIBS@ libproj_la_SOURCES = \ @@ -198,7 +198,7 @@ libproj_la_SOURCES = \ \ datums.cpp datum_set.cpp transform.cpp \ geocent.cpp geocent.h utils.cpp \ - jniproj.cpp mutex.cpp initcache.cpp geodesic.c \ + mutex.cpp initcache.cpp geodesic.c \ strtod.cpp \ \ 4D_api.cpp pipeline.cpp \ diff --git a/src/apps/cct.cpp b/src/apps/cct.cpp index d29b58fb..f7413872 100644 --- a/src/apps/cct.cpp +++ b/src/apps/cct.cpp @@ -161,19 +161,13 @@ static void logger(void *data, int level, const char *msg) { /* if we use PJ_LOG_NONE we always want to print stuff to stream */ if (level == PJ_LOG_NONE) { - fprintf(stream, "%s", msg); + fprintf(stream, "%s\n", msg); return; } - /* should always print to stderr if level == PJ_LOG_ERROR */ - if (level == PJ_LOG_ERROR) { - fprintf(stderr, "%s", msg); - return; - } - - /* otherwise only print if log level set by user is high enough */ - if (level <= log_tell) - fprintf(stream, "%s", msg); + /* otherwise only print if log level set by user is high enough or error */ + if (level <= log_tell || level == PJ_LOG_ERROR) + fprintf(stderr, "%s\n", msg); } FILE *fout; @@ -240,23 +234,25 @@ int main(int argc, char **argv) { PJ_DIRECTION direction = opt_given (o, "I")? PJ_INV: PJ_FWD; verbose = MIN(opt_given (o, "v"), 3); /* log level can't be larger than 3 */ - proj_log_level (PJ_DEFAULT_CTX, static_cast<PJ_LOG_LEVEL>(verbose)); + if( verbose > 0 ) { + proj_log_level (PJ_DEFAULT_CTX, static_cast<PJ_LOG_LEVEL>(verbose)); + } proj_log_func (PJ_DEFAULT_CTX, (void *) fout, logger); if (opt_given (o, "version")) { - print (PJ_LOG_NONE, "%s: %s\n", o->progname, pj_get_release ()); + print (PJ_LOG_NONE, "%s: %s", o->progname, pj_get_release ()); return 0; } if (opt_given (o, "o")) fout = fopen (opt_arg (o, "output"), "wt"); if (nullptr==fout) { - print (PJ_LOG_ERROR, "%s: Cannot open '%s' for output\n", o->progname, opt_arg (o, "output")); + print (PJ_LOG_ERROR, "%s: Cannot open '%s' for output", o->progname, opt_arg (o, "output")); free (o); return 1; } - print (PJ_LOG_TRACE, "%s: Running in very verbose mode\n", o->progname); + print (PJ_LOG_TRACE, "%s: Running in very verbose mode", o->progname); if (opt_given (o, "z")) { fixed_z = proj_atof (opt_arg (o, "z")); @@ -287,7 +283,7 @@ int main(int argc, char **argv) { /* cppcheck-suppress invalidscanf */ ncols = sscanf (opt_arg (o, "c"), "%d,%d,%d,%d", columns_xyzt, columns_xyzt+1, columns_xyzt+2, columns_xyzt+3); if (ncols != nfields) { - print (PJ_LOG_ERROR, "%s: Too few input columns given: '%s'\n", o->progname, opt_arg (o, "c")); + print (PJ_LOG_ERROR, "%s: Too few input columns given: '%s'", o->progname, opt_arg (o, "c")); free (o); if (stdout != fout) fclose (fout); @@ -298,7 +294,7 @@ int main(int argc, char **argv) { /* Setup transformation */ P = proj_create_argv (nullptr, o->pargc, o->pargv); if ((nullptr==P) || (0==o->pargc)) { - print (PJ_LOG_ERROR, "%s: Bad transformation arguments - (%s)\n '%s -h' for help\n", + print (PJ_LOG_ERROR, "%s: Bad transformation arguments - (%s)\n '%s -h' for help", o->progname, pj_strerrno (proj_errno(P)), o->progname); free (o); if (stdout != fout) @@ -307,12 +303,12 @@ int main(int argc, char **argv) { } info = proj_pj_info (P); - print (PJ_LOG_TRACE, "Final: %s argc=%d pargc=%d\n", info.definition, argc, o->pargc); + print (PJ_LOG_TRACE, "Final: %s argc=%d pargc=%d", info.definition, argc, o->pargc); if (direction== PJ_INV) { /* fail if an inverse operation is not available */ if (!info.has_inverse) { - print (PJ_LOG_ERROR, "Inverse operation not available\n"); + print (PJ_LOG_ERROR, "Inverse operation not available"); if (stdout != fout) fclose (fout); return 1; @@ -325,7 +321,7 @@ int main(int argc, char **argv) { /* Allocate input buffer */ buf = static_cast<char*>(calloc (1, 10000)); if (nullptr==buf) { - print (PJ_LOG_ERROR, "%s: Out of memory\n", o->progname); + print (PJ_LOG_ERROR, "%s: Out of memory", o->progname); pj_free (P); free (o); if (stdout != fout) @@ -341,7 +337,7 @@ int main(int argc, char **argv) { char *c = column (buf, 1); opt_eof_handler (o); if (nullptr==ret) { - print (PJ_LOG_ERROR, "Read error in record %d\n", (int) o->record_index); + print (PJ_LOG_ERROR, "Read error in record %d", (int) o->record_index); continue; } point = parse_input_line (buf, columns_xyzt, fixed_z, fixed_time); @@ -359,7 +355,7 @@ int main(int argc, char **argv) { if (HUGE_VAL==point.xyzt.x) { /* otherwise, it must be a syntax error */ print (PJ_LOG_NONE, "# Record %d UNREADABLE: %s", (int) o->record_index, buf); - print (PJ_LOG_ERROR, "%s: Could not parse file '%s' line %d\n", o->progname, opt_filename (o), opt_record (o)); + print (PJ_LOG_ERROR, "%s: Could not parse file '%s' line %d", o->progname, opt_filename (o), opt_record (o)); continue; } @@ -395,7 +391,7 @@ int main(int argc, char **argv) { if (proj_angular_output (P, direction)) { point.lpzt.lam = proj_todeg (point.lpzt.lam); point.lpzt.phi = proj_todeg (point.lpzt.phi); - print (PJ_LOG_NONE, "%14.*f %14.*f %12.*f %12.4f%s%s\n", + print (PJ_LOG_NONE, "%14.*f %14.*f %12.*f %12.4f%s%s", decimals_angles, point.xyzt.x, decimals_angles, point.xyzt.y, decimals_distances, point.xyzt.z, @@ -403,7 +399,7 @@ int main(int argc, char **argv) { ); } else - print (PJ_LOG_NONE, "%13.*f %13.*f %12.*f %12.4f%s%s\n", + print (PJ_LOG_NONE, "%13.*f %13.*f %12.*f %12.4f%s%s", decimals_distances, point.xyzt.x, decimals_distances, point.xyzt.y, decimals_distances, point.xyzt.z, diff --git a/src/apps/cs2cs.cpp b/src/apps/cs2cs.cpp index 273233a7..6c85d4aa 100644 --- a/src/apps/cs2cs.cpp +++ b/src/apps/cs2cs.cpp @@ -430,19 +430,6 @@ int main(int argc, char **argv) { for (lu = proj_list_units(); lu->id; ++lu) (void)printf("%12s %-20s %s\n", lu->id, lu->to_meter, lu->name); - } else if (arg[1] == 'd') { /* list datums */ - const struct PJ_DATUMS *ld; - - printf("__datum_id__ __ellipse___ " - "__definition/" - "comments______________________________\n"); - for (ld = pj_get_datums_ref(); ld->id; ++ld) { - printf("%12s %-12s %-30s\n", ld->id, ld->ellipse_id, - ld->defn); - if (ld->comments != nullptr && - strlen(ld->comments) > 0) - printf("%25s %s\n", " ", ld->comments); - } } else if (arg[1] == 'm') { /* list prime meridians */ const struct PJ_PRIME_MERIDIANS *lpm; diff --git a/src/apps/gie.cpp b/src/apps/gie.cpp index 6a67b55d..d9907776 100644 --- a/src/apps/gie.cpp +++ b/src/apps/gie.cpp @@ -1153,6 +1153,7 @@ static const struct errno_vs_err_const lookup[] = { {"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}, diff --git a/src/apps/proj.cpp b/src/apps/proj.cpp index 7fe08023..852cea04 100644 --- a/src/apps/proj.cpp +++ b/src/apps/proj.cpp @@ -385,17 +385,6 @@ int main(int argc, char **argv) { for (lu = proj_list_units(); lu->id ; ++lu) (void)printf("%12s %-20s %s\n", lu->id, lu->to_meter, lu->name); - } else if (arg[1] == 'd') { /* list datums */ - const struct PJ_DATUMS *ld; - - printf("__datum_id__ __ellipse___ __definition/comments______________________________\n" ); - for (ld = pj_get_datums_ref(); ld->id ; ++ld) - { - printf("%12s %-12s %-30s\n", - ld->id, ld->ellipse_id, ld->defn); - if( ld->comments != nullptr && strlen(ld->comments) > 0 ) - printf( "%25s %s\n", " ", ld->comments ); - } } else emess(1,"invalid list option: l%c",arg[1]); exit(0); @@ -490,6 +479,14 @@ int main(int argc, char **argv) { exit(0); } + // Ugly hack. See https://github.com/OSGeo/PROJ/issues/1782 + if( Proj->right == PJ_IO_UNITS_WHATEVER && Proj->descr && + strncmp(Proj->descr, "General Oblique Transformation", + strlen("General Oblique Transformation")) == 0 ) + { + Proj->right = PJ_IO_UNITS_PROJECTED; + } + if (inverse) { if (!Proj->inv) emess(3,"inverse projection not available"); diff --git a/src/apps/projinfo.cpp b/src/apps/projinfo.cpp index f13e526b..27ea278a 100644 --- a/src/apps/projinfo.cpp +++ b/src/apps/projinfo.cpp @@ -78,7 +78,7 @@ struct OutputOptions { static void usage() { std::cerr - << "usage: projinfo [-o formats] [-k crs|operation|ellipsoid] " + << "usage: projinfo [-o formats] [-k crs|operation|datum|ellipsoid] " "[--summary] [-q]" << std::endl << " ([--area name_or_code] | " @@ -185,6 +185,9 @@ static BaseObjectNNPtr buildObject( } else if (kind == "ellipsoid" && tokens.size() == 2) { auto urn = "urn:ogc:def:ellipsoid:" + tokens[0] + "::" + tokens[1]; obj = createFromUserInput(urn, dbContext).as_nullable(); + } else if (kind == "datum" && tokens.size() == 2) { + auto urn = "urn:ogc:def:datum:" + tokens[0] + "::" + tokens[1]; + obj = createFromUserInput(urn, dbContext).as_nullable(); } else { // Convenience to be able to use C escaped strings... if (l_user_string.size() > 2 && l_user_string[0] == '"' && @@ -206,6 +209,59 @@ static BaseObjectNNPtr buildObject( } } } + } else if (dbContext && !kind.empty() && kind != "crs" && + l_user_string.find(':') == std::string::npos) { + std::vector<AuthorityFactory::ObjectType> allowedTypes; + if (kind == "operation") + allowedTypes.push_back( + AuthorityFactory::ObjectType::COORDINATE_OPERATION); + else if (kind == "ellipsoid") + allowedTypes.push_back( + AuthorityFactory::ObjectType::ELLIPSOID); + else if (kind == "datum") + allowedTypes.push_back(AuthorityFactory::ObjectType::DATUM); + constexpr size_t limitResultCount = 10; + auto factory = AuthorityFactory::create(NN_NO_CHECK(dbContext), + std::string()); + for (int pass = 0; pass <= 1; ++pass) { + const bool approximateMatch = (pass == 1); + auto res = factory->createObjectsFromName( + l_user_string, allowedTypes, approximateMatch, + limitResultCount); + if (res.size() == 1) { + obj = res.front().as_nullable(); + } else { + for (const auto &l_obj : res) { + if (Identifier::isEquivalentName( + l_obj->nameStr().c_str(), + l_user_string.c_str())) { + obj = l_obj.as_nullable(); + break; + } + } + if (obj) { + break; + } + } + if (res.size() > 1) { + std::string msg("several objects matching this name: "); + bool first = true; + for (const auto &l_obj : res) { + if (msg.size() > 200) { + msg += ", ..."; + break; + } + if (!first) { + msg += ", "; + } + first = false; + msg += l_obj->nameStr(); + } + std::cerr << context << ": " << msg << std::endl; + std::exit(1); + } + } + } else { obj = createFromUserInput(l_user_string, dbContext).as_nullable(); @@ -424,24 +480,13 @@ static void outputObject( std::cout << "WKT1:GDAL string:" << std::endl; } - auto crs = nn_dynamic_pointer_cast<CRS>(obj); - std::shared_ptr<IWKTExportable> objToExport; - if (crs) { - objToExport = nn_dynamic_pointer_cast<IWKTExportable>( - crs->createBoundCRSToWGS84IfPossible( - dbContext, allowUseIntermediateCRS)); - } - if (!objToExport) { - objToExport = wktExportable; - } - auto formatter = WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL); if (outputOpt.singleLine) { formatter->setMultiLine(false); } formatter->setStrict(outputOpt.strict); - auto wkt = objToExport->exportToWKT(formatter.get()); + auto wkt = wktExportable->exportToWKT(formatter.get()); if (outputOpt.c_ify) { wkt = c_ify_string(wkt); } @@ -706,9 +751,7 @@ static void outputOperations( } first = false; std::cout << "-------------------------------------" << std::endl; - std::cout << "Operation n" - "\xC2\xB0" - << (i + 1) << ":" << std::endl + std::cout << "Operation No. " << (i + 1) << ":" << std::endl << std::endl; outputOperationSummary(op, dbContext, gridAvailabilityUse); std::cout << std::endl; @@ -885,6 +928,8 @@ int main(int argc, char **argv) { objectKind = "operation"; } else if (ci_equal(kind, "ellipsoid")) { objectKind = "ellipsoid"; + } else if (ci_equal(kind, "datum")) { + objectKind = "datum"; } else { std::cerr << "Unrecognized value for option -k: " << kind << std::endl; diff --git a/src/conversions/unitconvert.cpp b/src/conversions/unitconvert.cpp index 377f384e..f8439aee 100644 --- a/src/conversions/unitconvert.cpp +++ b/src/conversions/unitconvert.cpp @@ -164,7 +164,8 @@ static double decimalyear_to_mjd(double decimalyear) { double fractional_year; double mjd; - if( decimalyear < -10000 || decimalyear > 10000 ) + // Written this way to deal with NaN input + if( !(decimalyear >= -10000 && decimalyear <= 10000) ) return 0; year = lround(floor(decimalyear)); @@ -470,7 +471,7 @@ PJ *CONVERSION(unitconvert,0) { const char* normalized_name = nullptr; f = get_unit_conversion_factor(name, &xy_in_is_linear, &normalized_name); if (f != 0.0) { - proj_log_debug(P, "xy_in unit: %s", normalized_name); + proj_log_trace(P, "xy_in unit: %s", normalized_name); } else { f = pj_param (P->ctx, P->params, "dxy_in").f; if (f == 0.0 || 1.0 / f == 0.0) @@ -485,7 +486,7 @@ PJ *CONVERSION(unitconvert,0) { const char* normalized_name = nullptr; f = get_unit_conversion_factor(name, &xy_out_is_linear, &normalized_name); if (f != 0.0) { - proj_log_debug(P, "xy_out unit: %s", normalized_name); + proj_log_trace(P, "xy_out unit: %s", normalized_name); } else { f = pj_param (P->ctx, P->params, "dxy_out").f; if (f == 0.0 || 1.0 / f == 0.0) @@ -506,7 +507,7 @@ PJ *CONVERSION(unitconvert,0) { const char* normalized_name = nullptr; f = get_unit_conversion_factor(name, &z_in_is_linear, &normalized_name); if (f != 0.0) { - proj_log_debug(P, "z_in unit: %s", normalized_name); + proj_log_trace(P, "z_in unit: %s", normalized_name); } else { f = pj_param (P->ctx, P->params, "dz_in").f; if (f == 0.0 || 1.0 / f == 0.0) @@ -519,7 +520,7 @@ PJ *CONVERSION(unitconvert,0) { const char* normalized_name = nullptr; f = get_unit_conversion_factor(name, &z_out_is_linear, &normalized_name); if (f != 0.0) { - proj_log_debug(P, "z_out unit: %s", normalized_name); + proj_log_trace(P, "z_out unit: %s", normalized_name); } else { f = pj_param (P->ctx, P->params, "dz_out").f; if (f == 0.0 || 1.0 / f == 0.0) @@ -540,7 +541,7 @@ PJ *CONVERSION(unitconvert,0) { if (!s) return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); /* unknown unit conversion id */ Q->t_in_id = i; - proj_log_debug(P, "t_in unit: %s", time_units[i].name); + proj_log_trace(P, "t_in unit: %s", time_units[i].name); } s = nullptr; @@ -550,7 +551,7 @@ PJ *CONVERSION(unitconvert,0) { if (!s) return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); /* unknown unit conversion id */ Q->t_out_id = i; - proj_log_debug(P, "t_out unit: %s", time_units[i].name); + proj_log_trace(P, "t_out unit: %s", time_units[i].name); } return P; diff --git a/src/ell_set.cpp b/src/ell_set.cpp index bb46b3a4..899ec1bf 100644 --- a/src/ell_set.cpp +++ b/src/ell_set.cpp @@ -116,9 +116,9 @@ int pj_ellipsoid (PJ *P) { if (0 != ellps_spherification (P)) return 4; - proj_log_debug (P, "pj_ellipsoid - final: a=%.3f f=1/%7.3f, errno=%d", + proj_log_trace (P, "pj_ellipsoid - final: a=%.3f f=1/%7.3f, errno=%d", P->a, P->f!=0? 1/P->f: 0, proj_errno (P)); - proj_log_debug (P, "pj_ellipsoid - final: %s %s %s %s", + proj_log_trace (P, "pj_ellipsoid - final: %s %s %s %s", P->def_size? P->def_size: empty, P->def_shape? P->def_shape: empty, P->def_spherification? P->def_spherification: empty, diff --git a/src/filemanager.hpp b/src/filemanager.hpp index b84753ce..554bd325 100644 --- a/src/filemanager.hpp +++ b/src/filemanager.hpp @@ -90,7 +90,8 @@ class File { // --------------------------------------------------------------------------- -std::unique_ptr<File> pj_network_file_open(PJ_CONTEXT* ctx, const char* filename); +std::unique_ptr<File> pj_network_file_open(PJ_CONTEXT *ctx, + const char *filename); NS_PROJ_END diff --git a/src/geodesic.h b/src/geodesic.h index 5d230531..e2265c89 100644 --- a/src/geodesic.h +++ b/src/geodesic.h @@ -158,7 +158,7 @@ GEODESIC_VERSION_PATCH) #if !defined(GEOD_DLL) -#if defined(_MSC_VER) +#if defined(_MSC_VER) && defined(PROJ_MSVC_DLL_EXPORT) #define GEOD_DLL __declspec(dllexport) #elif defined(__GNUC__) #define GEOD_DLL __attribute__ ((visibility("default"))) diff --git a/src/grids.cpp b/src/grids.cpp index d5f961f7..24fcfe83 100644 --- a/src/grids.cpp +++ b/src/grids.cpp @@ -1345,7 +1345,6 @@ VerticalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) { auto fp = FileManager::open_resource_file(ctx, filename.c_str()); if (!fp) { - ctx->last_errno = 0; /* don't treat as a persistent error */ return nullptr; } const auto actualName(fp->name()); @@ -2275,7 +2274,6 @@ HorizontalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) { auto fp = FileManager::open_resource_file(ctx, filename.c_str()); if (!fp) { - ctx->last_errno = 0; /* don't treat as a persistent error */ return nullptr; } const auto actualName(fp->name()); @@ -2655,7 +2653,6 @@ GenericShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) { auto fp = FileManager::open_resource_file(ctx, filename.c_str()); if (!fp) { - ctx->last_errno = 0; /* don't treat as a persistent error */ return nullptr; } const auto actualName(fp->name()); @@ -2762,9 +2759,12 @@ ListOfGenericGrids pj_generic_grid_init(PJ *P, const char *gridkey) { auto gridSet = GenericShiftGridSet::open(P->ctx, gridname); if (!gridSet) { if (!canFail) { - pj_ctx_set_errno(P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + if (proj_context_errno(P->ctx) != PJD_ERR_NETWORK_ERROR) { + pj_ctx_set_errno(P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + } return {}; } + pj_ctx_set_errno(P->ctx, 0); // don't treat as a persistent error } else { grids.emplace_back(std::move(gridSet)); } @@ -2803,9 +2803,12 @@ static ListOfHGrids getListOfGridSets(PJ_CONTEXT *ctx, const char *grids) { auto gridSet = HorizontalShiftGridSet::open(ctx, gridname); if (!gridSet) { if (!canFail) { - pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + if (proj_context_errno(ctx) != PJD_ERR_NETWORK_ERROR) { + pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + } return {}; } + pj_ctx_set_errno(ctx, 0); // don't treat as a persistent error } else { list.emplace_back(std::move(gridSet)); } @@ -3228,9 +3231,12 @@ ListOfVGrids pj_vgrid_init(PJ *P, const char *gridkey) { auto gridSet = VerticalShiftGridSet::open(P->ctx, gridname); if (!gridSet) { if (!canFail) { - pj_ctx_set_errno(P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + if (proj_context_errno(P->ctx) != PJD_ERR_NETWORK_ERROR) { + pj_ctx_set_errno(P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + } return {}; } + pj_ctx_set_errno(P->ctx, 0); // don't treat as a persistent error } else { grids.emplace_back(std::move(gridSet)); } diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp index 5df4c513..4c98af3d 100644 --- a/src/iso19111/c_api.cpp +++ b/src/iso19111/c_api.cpp @@ -1146,15 +1146,9 @@ PJ_OBJ_LIST *proj_get_non_deprecated(PJ_CONTEXT *ctx, const PJ *obj) { // --------------------------------------------------------------------------- -/** \brief Return whether two objects are equivalent. - * - * @param obj Object (must not be NULL) - * @param other Other object (must not be NULL) - * @param criterion Comparison criterion - * @return TRUE if they are equivalent - */ -int proj_is_equivalent_to(const PJ *obj, const PJ *other, - PJ_COMPARISON_CRITERION criterion) { +static int proj_is_equivalent_to_internal(PJ_CONTEXT *ctx, const PJ *obj, + const PJ *other, + PJ_COMPARISON_CRITERION criterion) { assert(obj); assert(other); if (!obj->iso_obj) { @@ -1176,7 +1170,50 @@ int proj_is_equivalent_to(const PJ *obj, const PJ *other, return IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS; })(criterion); - return obj->iso_obj->isEquivalentTo(other->iso_obj.get(), cppCriterion); + int res = obj->iso_obj->isEquivalentTo( + other->iso_obj.get(), cppCriterion, + ctx ? getDBcontextNoException(ctx, "proj_is_equivalent_to_with_ctx") + : nullptr); + if (ctx && ctx->cpp_context) { + ctx->cpp_context->autoCloseDbIfNeeded(); + } + return res; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether two objects are equivalent. + * + * Use proj_is_equivalent_to_with_ctx() to be able to use database information. + * + * @param obj Object (must not be NULL) + * @param other Other object (must not be NULL) + * @param criterion Comparison criterion + * @return TRUE if they are equivalent + */ +int proj_is_equivalent_to(const PJ *obj, const PJ *other, + PJ_COMPARISON_CRITERION criterion) { + return proj_is_equivalent_to_internal(nullptr, obj, other, criterion); +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether two objects are equivalent + * + * Possibly using database to check for name aliases. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Object (must not be NULL) + * @param other Other object (must not be NULL) + * @param criterion Comparison criterion + * @return TRUE if they are equivalent + * @since 6.3 + */ +int proj_is_equivalent_to_with_ctx(PJ_CONTEXT *ctx, const PJ *obj, + const PJ *other, + PJ_COMPARISON_CRITERION criterion) { + SANITIZE_CTX(ctx); + return proj_is_equivalent_to_internal(ctx, obj, other, criterion); } // --------------------------------------------------------------------------- @@ -2159,6 +2196,10 @@ PJ *proj_get_target_crs(PJ_CONTEXT *ctx, const PJ *obj) { * The candidate CRSs are either hard-coded, or looked in the database when * it is available. * + * Note that the implementation uses a set of heuristics to have a good + * compromise of successful identifications over execution time. It might miss + * legitimate matches in some circumstances. + * * The method returns a list of matching reference CRS, and the percentage * (0-100) of confidence in the match. The list is sorted by decreasing * confidence. @@ -2727,16 +2768,19 @@ static GeodeticReferenceFrameNNPtr createGeodeticReferenceFrame( if (metadata::Identifier::isEquivalentName( datumName.c_str(), refDatum->nameStr().c_str())) { datumName = refDatum->nameStr(); - } - } else { - std::string outTableName; - std::string authNameFromAlias; - std::string codeFromAlias; - auto officialName = authFactory->getOfficialNameFromAlias( - datumName, "geodetic_datum", std::string(), true, - outTableName, authNameFromAlias, codeFromAlias); - if (!officialName.empty()) { - datumName = officialName; + } else if (refDatum->identifiers().size() == 1) { + const auto &id = refDatum->identifiers()[0]; + const auto aliases = + authFactory->databaseContext()->getAliases( + *id->codeSpace(), id->code(), refDatum->nameStr(), + "geodetic_datum", std::string()); + for (const auto &alias : aliases) { + if (metadata::Identifier::isEquivalentName( + datumName.c_str(), alias.c_str())) { + datumName = refDatum->nameStr(); + break; + } + } } } } @@ -7783,6 +7827,34 @@ PJ *proj_normalize_for_visualization(PJ_CONTEXT *ctx, const PJ *obj) { // --------------------------------------------------------------------------- +/** \brief Returns a PJ* coordinate operation object which represents the + * inverse operation of the specified coordinate operation. + * + * @param ctx PROJ context, or NULL for default context + * @param obj Object of type CoordinateOperation (must not be NULL) + * @return a new PJ* object to free with proj_destroy() in case of success, or + * nullptr in case of error + * @since 6.3 + */ +PJ *proj_coordoperation_create_inverse(PJ_CONTEXT *ctx, const PJ *obj) { + + SANITIZE_CTX(ctx); + auto co = dynamic_cast<const CoordinateOperation *>(obj->iso_obj.get()); + if (!co) { + proj_log_error(ctx, __FUNCTION__, + "Object is not a CoordinateOperation"); + return nullptr; + } + try { + return pj_obj_create(ctx, co->inverse()); + } catch (const std::exception &e) { + proj_log_debug(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + /** \brief Returns the number of steps of a concatenated operation. * * The input object must be a concatenated operation. diff --git a/src/iso19111/common.cpp b/src/iso19111/common.cpp index 97900bda..f2e4de4c 100644 --- a/src/iso19111/common.cpp +++ b/src/iso19111/common.cpp @@ -901,19 +901,19 @@ void IdentifiedObject::formatRemarks(JSONFormatter *formatter) const { // --------------------------------------------------------------------------- bool IdentifiedObject::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherIdObj = dynamic_cast<const IdentifiedObject *>(other); if (!otherIdObj) return false; - return _isEquivalentTo(otherIdObj, criterion); + return _isEquivalentTo(otherIdObj, criterion, dbContext); } // --------------------------------------------------------------------------- -bool IdentifiedObject::_isEquivalentTo(const IdentifiedObject *otherIdObj, - util::IComparable::Criterion criterion) - PROJ_PURE_DEFN { +bool IdentifiedObject::_isEquivalentTo( + const IdentifiedObject *otherIdObj, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) PROJ_PURE_DEFN { if (criterion == util::IComparable::Criterion::STRICT) { if (!ci_equal(nameStr(), otherIdObj->nameStr())) { return false; @@ -922,12 +922,19 @@ bool IdentifiedObject::_isEquivalentTo(const IdentifiedObject *otherIdObj, } else { if (!metadata::Identifier::isEquivalentName( nameStr().c_str(), otherIdObj->nameStr().c_str())) { - return false; + return hasEquivalentNameToUsingAlias(otherIdObj, dbContext); } } return true; } +// --------------------------------------------------------------------------- + +bool IdentifiedObject::hasEquivalentNameToUsingAlias( + const IdentifiedObject *, const io::DatabaseContextPtr &) const { + return false; +} + //! @endcond // --------------------------------------------------------------------------- @@ -1092,8 +1099,8 @@ void ObjectDomain::_exportToJSON(JSONFormatter *formatter) const { //! @cond Doxygen_Suppress bool ObjectDomain::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherDomain = dynamic_cast<const ObjectDomain *>(other); if (!otherDomain) return false; @@ -1106,7 +1113,7 @@ bool ObjectDomain::_isEquivalentTo( return false; return domainOfValidity().get() == nullptr || domainOfValidity()->_isEquivalentTo( - otherDomain->domainOfValidity().get(), criterion); + otherDomain->domainOfValidity().get(), criterion, dbContext); } //! @endcond @@ -1249,14 +1256,14 @@ void ObjectUsage::baseExportToJSON(JSONFormatter *formatter) const { //! @cond Doxygen_Suppress bool ObjectUsage::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherObjUsage = dynamic_cast<const ObjectUsage *>(other); if (!otherObjUsage) return false; // TODO: incomplete - return IdentifiedObject::_isEquivalentTo(other, criterion); + return IdentifiedObject::_isEquivalentTo(other, criterion, dbContext); } //! @endcond diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp index 7c0515c7..a1650046 100644 --- a/src/iso19111/coordinateoperation.cpp +++ b/src/iso19111/coordinateoperation.cpp @@ -1070,11 +1070,11 @@ void OperationMethod::_exportToJSON( //! @cond Doxygen_Suppress bool OperationMethod::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + 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)) { + !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { return false; } // TODO test formula and formulaCitation @@ -1086,7 +1086,8 @@ bool OperationMethod::_isEquivalentTo( } if (criterion == util::IComparable::Criterion::STRICT) { for (size_t i = 0; i < paramsSize; i++) { - if (!params[i]->_isEquivalentTo(otherParams[i].get(), criterion)) { + if (!params[i]->_isEquivalentTo(otherParams[i].get(), criterion, + dbContext)) { return false; } } @@ -1096,8 +1097,8 @@ bool OperationMethod::_isEquivalentTo( bool found = false; for (size_t j = 0; j < paramsSize; j++) { if (candidateIndices[j] && - params[i]->_isEquivalentTo(otherParams[j].get(), - criterion)) { + params[i]->_isEquivalentTo(otherParams[j].get(), criterion, + dbContext)) { candidateIndices[j] = false; found = true; break; @@ -1343,14 +1344,14 @@ bool OperationParameterValue::convertFromAbridged( //! @cond Doxygen_Suppress bool OperationParameterValue::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + 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)) { + if (!d->parameter->_isEquivalentTo(otherOPV->d->parameter.get(), criterion, + dbContext)) { return false; } if (criterion == util::IComparable::Criterion::STRICT) { @@ -1358,7 +1359,7 @@ bool OperationParameterValue::_isEquivalentTo( otherOPV->d->parameterValue.get(), criterion); } if (d->parameterValue->_isEquivalentTo(otherOPV->d->parameterValue.get(), - criterion)) { + criterion, dbContext)) { return true; } if (d->parameter->getEPSGCode() == @@ -1448,16 +1449,16 @@ OperationParameter::create(const util::PropertyMap &properties) { //! @cond Doxygen_Suppress bool OperationParameter::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + 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); + return IdentifiedObject::_isEquivalentTo(other, criterion, dbContext); } - if (IdentifiedObject::_isEquivalentTo(other, criterion)) { + if (IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { return true; } auto l_epsgCode = getEPSGCode(); @@ -1761,19 +1762,20 @@ static SingleOperationNNPtr createPROJBased( //! @cond Doxygen_Suppress bool SingleOperation::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { - return _isEquivalentTo(other, criterion, false); + 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))) { + !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { return false; } @@ -1783,7 +1785,8 @@ bool SingleOperation::_isEquivalentTo(const util::IComparable *other, bool equivalentMethods = (criterion == util::IComparable::Criterion::EQUIVALENT && methodEPSGCode != 0 && methodEPSGCode == otherMethodEPSGCode) || - d->method_->_isEquivalentTo(otherSO->d->method_.get(), criterion); + 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 && @@ -1856,7 +1859,7 @@ bool SingleOperation::_isEquivalentTo(const util::IComparable *other, 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); + return otherSO->_isEquivalentTo(this, criterion, dbContext); } else if ((methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A && otherMethodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B) || @@ -1872,7 +1875,8 @@ bool SingleOperation::_isEquivalentTo(const util::IComparable *other, auto eqConv = conv->convertToOtherMethod(otherMethodEPSGCode); if (eqConv) { - return eqConv->_isEquivalentTo(other, criterion); + return eqConv->_isEquivalentTo(other, criterion, + dbContext); } } } @@ -1890,7 +1894,8 @@ bool SingleOperation::_isEquivalentTo(const util::IComparable *other, return false; } for (size_t i = 0; i < valuesSize; i++) { - if (!values[i]->_isEquivalentTo(otherValues[i].get(), criterion)) { + if (!values[i]->_isEquivalentTo(otherValues[i].get(), criterion, + dbContext)) { return false; } } @@ -1911,7 +1916,8 @@ bool SingleOperation::_isEquivalentTo(const util::IComparable *other, bool sameNameDifferentValue = false; for (size_t j = 0; j < otherValuesSize; j++) { if (candidateIndices[j] && - values[i]->_isEquivalentTo(otherValues[j].get(), criterion)) { + values[i]->_isEquivalentTo(otherValues[j].get(), criterion, + dbContext)) { candidateIndices[j] = false; equivalent = true; break; @@ -1923,7 +1929,8 @@ bool SingleOperation::_isEquivalentTo(const util::IComparable *other, return false; sameNameDifferentValue = opParamvalue->parameter()->_isEquivalentTo( - otherOpParamvalue->parameter().get(), criterion); + otherOpParamvalue->parameter().get(), criterion, + dbContext); if (sameNameDifferentValue) { candidateIndices[j] = false; break; @@ -1951,13 +1958,13 @@ bool SingleOperation::_isEquivalentTo(const util::IComparable *other, ->parameterValue( EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) .get(), - criterion) && + criterion, dbContext) && value_2nd->_isEquivalentTo( otherSO ->parameterValue( EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) .get(), - criterion); + criterion, dbContext); } } } @@ -1989,7 +1996,7 @@ bool SingleOperation::_isEquivalentTo(const util::IComparable *other, // In the case the arguments don't perfectly match, try the reverse // check. if (equivalent && foundMissingArgs && !inOtherDirection) { - return otherSO->_isEquivalentTo(this, criterion, true); + return otherSO->_isEquivalentTo(this, criterion, dbContext, true); } // Equivalent formulations of 2SP can have different parameters @@ -2004,8 +2011,8 @@ bool SingleOperation::_isEquivalentTo(const util::IComparable *other, auto otherAs1SP = otherConv->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); if (thisAs1SP && otherAs1SP) { - equivalent = - thisAs1SP->_isEquivalentTo(otherAs1SP.get(), criterion); + equivalent = thisAs1SP->_isEquivalentTo(otherAs1SP.get(), + criterion, dbContext); } } } @@ -2415,9 +2422,9 @@ void ParameterValue::_exportToWKT(io::WKTFormatter *formatter) const { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress -bool ParameterValue::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { +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; @@ -8763,7 +8770,8 @@ TransformationNNPtr Transformation::substitutePROJAlternativeGridNames( } } - if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON) { + if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON || + methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD) { auto fileParameter = parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE); @@ -9437,6 +9445,19 @@ void Transformation::_exportToPROJString( } } + if (methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD) { + 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, @@ -10189,10 +10210,12 @@ void ConcatenatedOperation::_exportToPROJString( //! @cond Doxygen_Suppress bool ConcatenatedOperation::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherCO = dynamic_cast<const ConcatenatedOperation *>(other); - if (otherCO == nullptr || !ObjectUsage::_isEquivalentTo(other, criterion)) { + if (otherCO == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { return false; } const auto &steps = operations(); @@ -10201,7 +10224,8 @@ bool ConcatenatedOperation::_isEquivalentTo( return false; } for (size_t i = 0; i < steps.size(); i++) { - if (!steps[i]->_isEquivalentTo(otherSteps[i].get(), criterion)) { + if (!steps[i]->_isEquivalentTo(otherSteps[i].get(), criterion, + dbContext)) { return false; } } @@ -11313,9 +11337,17 @@ struct FilterResults { setOfSetOfGrids.end()) { continue; } + + const bool sameNameOrEmptyName = + ((!curExtent && !lastExtent) || + (curExtent && lastExtent && + !curExtent->description()->empty() && + *(curExtent->description()) == + *(lastExtent->description()))); + // If we have already found a operation without grids for // that extent, no need to add any lower accuracy operation - if (!lastHasGrids) { + if (!lastHasGrids && sameNameOrEmptyName) { continue; } // If we had only operations involving grids, but one @@ -12613,6 +12645,18 @@ void CoordinateOperationFactory::Private::createOperationsWithDatumPivot( // 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) { @@ -12666,7 +12710,7 @@ void CoordinateOperationFactory::Private::createOperationsWithDatumPivot( #endif createTransformations(candidateSrcGeod, candidateDstGeod, opsFirst[0], isNullFirst); - if (!res.empty() && hasResultSetOnlyResultsWithPROJStep(res)) { + if (!res.empty() && !hasResultSetOnlyResultsWithPROJStep(res)) { return; } } @@ -14870,14 +14914,15 @@ void InverseCoordinateOperation::_exportToPROJString( // --------------------------------------------------------------------------- bool InverseCoordinateOperation::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + 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)) { + !ObjectUsage::_isEquivalentTo(other, criterion, dbContext)) { return false; } - return inverse()->_isEquivalentTo(otherICO->inverse().get(), criterion); + return inverse()->_isEquivalentTo(otherICO->inverse().get(), criterion, + dbContext); } // --------------------------------------------------------------------------- diff --git a/src/iso19111/coordinatesystem.cpp b/src/iso19111/coordinatesystem.cpp index 6769b486..fc4b7492 100644 --- a/src/iso19111/coordinatesystem.cpp +++ b/src/iso19111/coordinatesystem.cpp @@ -429,8 +429,8 @@ void CoordinateSystemAxis::_exportToJSON( //! @cond Doxygen_Suppress bool CoordinateSystemAxis::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherCSA = dynamic_cast<const CoordinateSystemAxis *>(other); if (otherCSA == nullptr) { return false; @@ -441,7 +441,7 @@ bool CoordinateSystemAxis::_isEquivalentTo( return false; } if (criterion == util::IComparable::Criterion::STRICT) { - if (!IdentifiedObject::_isEquivalentTo(other, criterion)) { + if (!IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { return false; } if (abbreviation() != otherCSA->abbreviation()) { @@ -581,11 +581,13 @@ void CoordinateSystem::_exportToJSON( writer.Add(getWKT2Type(true)); writer.AddObjKey("axis"); - auto axisContext(writer.MakeArrayContext(false)); - const auto &l_axisList = axisList(); - for (auto &axis : l_axisList) { - formatter->setOmitTypeInImmediateChild(); - axis->_exportToJSON(formatter); + { + auto axisContext(writer.MakeArrayContext(false)); + const auto &l_axisList = axisList(); + for (auto &axis : l_axisList) { + formatter->setOmitTypeInImmediateChild(); + axis->_exportToJSON(formatter); + } } if (formatter->outputId()) { @@ -599,11 +601,11 @@ void CoordinateSystem::_exportToJSON( //! @cond Doxygen_Suppress bool CoordinateSystem::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherCS = dynamic_cast<const CoordinateSystem *>(other); if (otherCS == nullptr || - !IdentifiedObject::_isEquivalentTo(other, criterion)) { + !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { return false; } const auto &list = axisList(); @@ -615,7 +617,8 @@ bool CoordinateSystem::_isEquivalentTo( return false; } for (size_t i = 0; i < list.size(); i++) { - if (!list[i]->_isEquivalentTo(otherList[i].get(), criterion)) { + if (!list[i]->_isEquivalentTo(otherList[i].get(), criterion, + dbContext)) { return false; } } diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index 49cc050f..d71d527e 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -49,6 +49,7 @@ #include <algorithm> #include <cassert> +#include <cmath> #include <cstring> #include <iostream> #include <memory> @@ -390,7 +391,7 @@ CRSNNPtr CRS::createBoundCRSToWGS84IfPossible( if (boundCRS) { if (boundCRS->hubCRS()->_isEquivalentTo( GeographicCRS::EPSG_4326.get(), - util::IComparable::Criterion::EQUIVALENT)) { + util::IComparable::Criterion::EQUIVALENT, dbContext)) { return NN_NO_CHECK(boundCRS); } } @@ -399,16 +400,16 @@ CRSNNPtr CRS::createBoundCRSToWGS84IfPossible( auto geogCRS = extractGeographicCRS(); auto hubCRS = util::nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326); if (geodCRS && !geogCRS) { - if (geodCRS->_isEquivalentTo( - GeographicCRS::EPSG_4978.get(), - util::IComparable::Criterion::EQUIVALENT)) { + if (geodCRS->_isEquivalentTo(GeographicCRS::EPSG_4978.get(), + util::IComparable::Criterion::EQUIVALENT, + dbContext)) { return thisAsCRS; } hubCRS = util::nn_static_pointer_cast<CRS>(GeodeticCRS::EPSG_4978); } else if (!geogCRS || geogCRS->_isEquivalentTo( GeographicCRS::EPSG_4326.get(), - util::IComparable::Criterion::EQUIVALENT)) { + util::IComparable::Criterion::EQUIVALENT, dbContext)) { return thisAsCRS; } else { geodCRS = geogCRS; @@ -595,12 +596,44 @@ CRSNNPtr CRS::alterId(const std::string &authName, //! @cond Doxygen_Suppress -static bool isAxisListNorthEast( +static bool mustAxisOrderBeSwitchedForVisualizationInternal( const std::vector<cs::CoordinateSystemAxisNNPtr> &axisList) { const auto &dir0 = axisList[0]->direction(); const auto &dir1 = axisList[1]->direction(); - return (&dir0 == &cs::AxisDirection::NORTH && - &dir1 == &cs::AxisDirection::EAST); + if (&dir0 == &cs::AxisDirection::NORTH && + &dir1 == &cs::AxisDirection::EAST) { + return true; + } + + // Address EPSG:32661 "WGS 84 / UPS North (N,E)" + if (&dir0 == &cs::AxisDirection::SOUTH && + &dir1 == &cs::AxisDirection::SOUTH) { + const auto &meridian0 = axisList[0]->meridian(); + const auto &meridian1 = axisList[1]->meridian(); + return meridian0 != nullptr && meridian1 != nullptr && + std::abs(meridian0->longitude().convertToUnit( + common::UnitOfMeasure::DEGREE) - + 180.0) < 1e-10 && + std::abs(meridian1->longitude().convertToUnit( + common::UnitOfMeasure::DEGREE) - + 90.0) < 1e-10; + } + + // Address EPSG:32761 "WGS 84 / UPS South (N,E)" + if (&dir0 == &cs::AxisDirection::NORTH && + &dir1 == &cs::AxisDirection::NORTH) { + const auto &meridian0 = axisList[0]->meridian(); + const auto &meridian1 = axisList[1]->meridian(); + return meridian0 != nullptr && meridian1 != nullptr && + std::abs(meridian0->longitude().convertToUnit( + common::UnitOfMeasure::DEGREE) - + 0.0) < 1e-10 && + std::abs(meridian1->longitude().convertToUnit( + common::UnitOfMeasure::DEGREE) - + 90.0) < 1e-10; + } + + return false; } // --------------------------------------------------------------------------- @@ -616,12 +649,14 @@ bool CRS::mustAxisOrderBeSwitchedForVisualization() const { const GeographicCRS *geogCRS = dynamic_cast<const GeographicCRS *>(this); if (geogCRS) { - return isAxisListNorthEast(geogCRS->coordinateSystem()->axisList()); + return mustAxisOrderBeSwitchedForVisualizationInternal( + geogCRS->coordinateSystem()->axisList()); } const ProjectedCRS *projCRS = dynamic_cast<const ProjectedCRS *>(this); if (projCRS) { - return isAxisListNorthEast(projCRS->coordinateSystem()->axisList()); + return mustAxisOrderBeSwitchedForVisualizationInternal( + projCRS->coordinateSystem()->axisList()); } return false; @@ -655,7 +690,7 @@ CRSNNPtr CRS::normalizeForVisualization() const { const GeographicCRS *geogCRS = dynamic_cast<const GeographicCRS *>(this); if (geogCRS) { const auto &axisList = geogCRS->coordinateSystem()->axisList(); - if (isAxisListNorthEast(axisList)) { + if (mustAxisOrderBeSwitchedForVisualizationInternal(axisList)) { auto cs = axisList.size() == 2 ? cs::EllipsoidalCS::create(util::PropertyMap(), axisList[1], axisList[0]) @@ -670,7 +705,7 @@ CRSNNPtr CRS::normalizeForVisualization() const { const ProjectedCRS *projCRS = dynamic_cast<const ProjectedCRS *>(this); if (projCRS) { const auto &axisList = projCRS->coordinateSystem()->axisList(); - if (isAxisListNorthEast(axisList)) { + if (mustAxisOrderBeSwitchedForVisualizationInternal(axisList)) { auto cs = axisList.size() == 2 ? cs::CartesianCS::create(util::PropertyMap(), axisList[1], @@ -695,6 +730,10 @@ CRSNNPtr CRS::normalizeForVisualization() const { * The candidate CRSs are either hard-coded, or looked in the database when * authorityFactory is not null. * + * Note that the implementation uses a set of heuristics to have a good + * compromise of successful identifications over execution time. It might miss + * legitimate matches in some circumstances. + * * The method returns a list of matching reference CRS, and the percentage * (0-100) of confidence in the match. The list is sorted by decreasing * confidence. @@ -987,18 +1026,19 @@ const cs::CoordinateSystemNNPtr &SingleCRS::coordinateSystem() PROJ_PURE_DEFN { // --------------------------------------------------------------------------- bool SingleCRS::baseIsEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherSingleCRS = dynamic_cast<const SingleCRS *>(other); if (otherSingleCRS == nullptr || (criterion == util::IComparable::Criterion::STRICT && - !ObjectUsage::_isEquivalentTo(other, criterion))) { + !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { return false; } const auto &thisDatum = d->datum; const auto &otherDatum = otherSingleCRS->d->datum; if (thisDatum) { - if (!thisDatum->_isEquivalentTo(otherDatum.get(), criterion)) { + if (!thisDatum->_isEquivalentTo(otherDatum.get(), criterion, + dbContext)) { return false; } } else { @@ -1009,7 +1049,8 @@ bool SingleCRS::baseIsEquivalentTo( // TODO test DatumEnsemble return d->coordinateSystem->_isEquivalentTo( - otherSingleCRS->d->coordinateSystem.get(), criterion) && + otherSingleCRS->d->coordinateSystem.get(), criterion, + dbContext) && getExtensionProj4() == otherSingleCRS->getExtensionProj4(); } @@ -1544,13 +1585,13 @@ getStandardCriterion(util::IComparable::Criterion criterion) { //! @cond Doxygen_Suppress bool GeodeticCRS::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { const auto standardCriterion = getStandardCriterion(criterion); auto otherGeodCRS = dynamic_cast<const GeodeticCRS *>(other); // TODO test velocityModel return otherGeodCRS != nullptr && - SingleCRS::baseIsEquivalentTo(other, standardCriterion); + SingleCRS::baseIsEquivalentTo(other, standardCriterion, dbContext); } //! @endcond @@ -1611,6 +1652,10 @@ static bool hasCodeCompatibleOfAuthorityFactory( * The candidate CRSs are either hard-coded, or looked in the database when * authorityFactory is not null. * + * Note that the implementation uses a set of heuristics to have a good + * compromise of successful identifications over execution time. It might miss + * legitimate matches in some circumstances. + * * The method returns a list of matching reference CRS, and the percentage * (0-100) of confidence in the match: * <ul> @@ -1651,6 +1696,9 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { std::list<Pair> res; const auto &thisName(nameStr()); + io::DatabaseContextPtr dbContext = + authorityFactory ? authorityFactory->databaseContext().as_nullable() + : nullptr; const bool l_implicitCS = CRS::getPrivate()->implicitCS_; const auto crsCriterion = l_implicitCS @@ -1664,7 +1712,7 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { const bool nameEquivalent = metadata::Identifier::isEquivalentName( thisName.c_str(), crs->nameStr().c_str()); const bool nameEqual = thisName == crs->nameStr(); - const bool isEq = _isEquivalentTo(crs.get(), crsCriterion); + const bool isEq = _isEquivalentTo(crs.get(), crsCriterion, dbContext); if (nameEquivalent && isEq && (!authorityFactory || nameEqual)) { res.emplace_back(util::nn_static_pointer_cast<GeodeticCRS>(crs), nameEqual ? 100 : 90); @@ -1699,13 +1747,14 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { const auto &thisDatum(datum()); auto searchByDatum = [this, &authorityFactory, &res, &thisDatum, - &geodetic_crs_type, crsCriterion]() { + &geodetic_crs_type, crsCriterion, &dbContext]() { for (const auto &id : thisDatum->identifiers()) { try { auto tempRes = authorityFactory->createGeodeticCRSFromDatum( *id->codeSpace(), id->code(), geodetic_crs_type); for (const auto &crs : tempRes) { - if (_isEquivalentTo(crs.get(), crsCriterion)) { + if (_isEquivalentTo(crs.get(), crsCriterion, + dbContext)) { res.emplace_back(crs, 70); } } @@ -1717,7 +1766,7 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { const auto &thisEllipsoid(ellipsoid()); auto searchByEllipsoid = [this, &authorityFactory, &res, &thisDatum, &thisEllipsoid, &geodetic_crs_type, - l_implicitCS]() { + l_implicitCS, &dbContext]() { const auto ellipsoids = thisEllipsoid->identifiers().empty() ? authorityFactory->createEllipsoidFromExisting( @@ -1735,15 +1784,17 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { if (crsDatum && crsDatum->ellipsoid()->_isEquivalentTo( ellps.get(), - util::IComparable::Criterion::EQUIVALENT) && + util::IComparable::Criterion::EQUIVALENT, + dbContext) && crsDatum->primeMeridian()->_isEquivalentTo( thisDatum->primeMeridian().get(), - util::IComparable::Criterion::EQUIVALENT) && + util::IComparable::Criterion::EQUIVALENT, + dbContext) && (!l_implicitCS || coordinateSystem()->_isEquivalentTo( crs->coordinateSystem().get(), - util::IComparable::Criterion:: - EQUIVALENT))) { + util::IComparable::Criterion::EQUIVALENT, + dbContext))) { res.emplace_back(crs, 60); } } @@ -1776,7 +1827,8 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { authorityFactory->databaseContext(), *id->codeSpace()) ->createGeodeticCRS(id->code()); - bool match = _isEquivalentTo(crs.get(), crsCriterion); + bool match = + _isEquivalentTo(crs.get(), crsCriterion, dbContext); res.emplace_back(crs, match ? 100 : 25); return res; } catch (const std::exception &) { @@ -1794,7 +1846,7 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { auto crs = util::nn_dynamic_pointer_cast<GeodeticCRS>(obj); assert(crs); auto crsNN = NN_NO_CHECK(crs); - if (_isEquivalentTo(crs.get(), crsCriterion)) { + if (_isEquivalentTo(crs.get(), crsCriterion, dbContext)) { if (crs->nameStr() == thisName) { res.clear(); res.emplace_back(crsNN, 100); @@ -1824,8 +1876,8 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { const auto &thisCS(coordinateSystem()); // Sort results - res.sort([&thisName, &thisDatum, &thisCS](const Pair &a, - const Pair &b) { + res.sort([&thisName, &thisDatum, &thisCS, &dbContext](const Pair &a, + const Pair &b) { // First consider confidence if (a.second > b.second) { return true; @@ -1849,9 +1901,11 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { const auto &bDatum(b.first->datum()); if (thisDatum && aDatum && bDatum) { const auto thisEquivADatum(thisDatum->_isEquivalentTo( - aDatum.get(), util::IComparable::Criterion::EQUIVALENT)); + aDatum.get(), util::IComparable::Criterion::EQUIVALENT, + dbContext)); const auto thisEquivBDatum(thisDatum->_isEquivalentTo( - bDatum.get(), util::IComparable::Criterion::EQUIVALENT)); + bDatum.get(), util::IComparable::Criterion::EQUIVALENT, + dbContext)); if (thisEquivADatum && !thisEquivBDatum) { return true; @@ -1865,9 +1919,11 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { const auto &aCS(a.first->coordinateSystem()); const auto &bCS(b.first->coordinateSystem()); const auto thisEquivACs(thisCS->_isEquivalentTo( - aCS.get(), util::IComparable::Criterion::EQUIVALENT)); + aCS.get(), util::IComparable::Criterion::EQUIVALENT, + dbContext)); const auto thisEquivBCs(thisCS->_isEquivalentTo( - bCS.get(), util::IComparable::Criterion::EQUIVALENT)); + bCS.get(), util::IComparable::Criterion::EQUIVALENT, + dbContext)); if (thisEquivACs && !thisEquivBCs) { return true; } @@ -2084,14 +2140,14 @@ bool GeographicCRS::is2DPartOf3D(util::nn<const GeographicCRS *> other) //! @cond Doxygen_Suppress bool GeographicCRS::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherGeogCRS = dynamic_cast<const GeographicCRS *>(other); if (otherGeogCRS == nullptr) { return false; } const auto standardCriterion = getStandardCriterion(criterion); - if (GeodeticCRS::_isEquivalentTo(other, standardCriterion)) { + if (GeodeticCRS::_isEquivalentTo(other, standardCriterion, dbContext)) { return true; } if (criterion != @@ -2110,7 +2166,7 @@ bool GeographicCRS::_isEquivalentTo( cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ? cs::EllipsoidalCS::createLatitudeLongitude(unit) : cs::EllipsoidalCS::createLongitudeLatitude(unit)) - ->GeodeticCRS::_isEquivalentTo(other, standardCriterion); + ->GeodeticCRS::_isEquivalentTo(other, standardCriterion, dbContext); } return false; } @@ -2682,12 +2738,12 @@ VerticalCRS::create(const util::PropertyMap &properties, //! @cond Doxygen_Suppress bool VerticalCRS::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherVertCRS = dynamic_cast<const VerticalCRS *>(other); // TODO test geoidModel and velocityModel return otherVertCRS != nullptr && - SingleCRS::baseIsEquivalentTo(other, criterion); + SingleCRS::baseIsEquivalentTo(other, criterion, dbContext); } //! @endcond @@ -2698,6 +2754,10 @@ bool VerticalCRS::_isEquivalentTo( * The candidate CRSs are looked in the database when * authorityFactory is not null. * + * Note that the implementation uses a set of heuristics to have a good + * compromise of successful identifications over execution time. It might miss + * legitimate matches in some circumstances. + * * The method returns a list of matching reference CRS, and the percentage * (0-100) of confidence in the match. * 100% means that the name of the reference entry @@ -2722,6 +2782,8 @@ VerticalCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { const auto &thisName(nameStr()); if (authorityFactory) { + const io::DatabaseContextNNPtr &dbContext = + authorityFactory->databaseContext(); const bool unsignificantName = thisName.empty() || ci_equal(thisName, "unknown") || @@ -2733,12 +2795,11 @@ VerticalCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { if (hasCodeCompatibleOfAuthorityFactory(id, authorityFactory)) { try { auto crs = io::AuthorityFactory::create( - authorityFactory->databaseContext(), - *id->codeSpace()) + dbContext, *id->codeSpace()) ->createVerticalCRS(id->code()); bool match = _isEquivalentTo( - crs.get(), - util::IComparable::Criterion::EQUIVALENT); + crs.get(), util::IComparable::Criterion::EQUIVALENT, + dbContext); res.emplace_back(crs, match ? 100 : 25); return res; } catch (const std::exception &) { @@ -2756,8 +2817,8 @@ VerticalCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { assert(crs); auto crsNN = NN_NO_CHECK(crs); if (_isEquivalentTo( - crs.get(), - util::IComparable::Criterion::EQUIVALENT)) { + crs.get(), util::IComparable::Criterion::EQUIVALENT, + dbContext)) { if (crs->nameStr() == thisName) { res.clear(); res.emplace_back(crsNN, 100); @@ -2922,19 +2983,20 @@ DerivedCRS::derivingConversionRef() PROJ_PURE_DEFN { // --------------------------------------------------------------------------- -bool DerivedCRS::_isEquivalentTo(const util::IComparable *other, - util::IComparable::Criterion criterion) const { +bool DerivedCRS::_isEquivalentTo( + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherDerivedCRS = dynamic_cast<const DerivedCRS *>(other); const auto standardCriterion = getStandardCriterion(criterion); if (otherDerivedCRS == nullptr || - !SingleCRS::baseIsEquivalentTo(other, standardCriterion)) { + !SingleCRS::baseIsEquivalentTo(other, standardCriterion, dbContext)) { return false; } return d->baseCRS_->_isEquivalentTo(otherDerivedCRS->d->baseCRS_.get(), - criterion) && + criterion, dbContext) && d->derivingConversion_->_isEquivalentTo( - otherDerivedCRS->d->derivingConversion_.get(), - standardCriterion); + otherDerivedCRS->d->derivingConversion_.get(), standardCriterion, + dbContext); } // --------------------------------------------------------------------------- @@ -3367,11 +3429,11 @@ ProjectedCRS::create(const util::PropertyMap &properties, // --------------------------------------------------------------------------- bool ProjectedCRS::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherProjCRS = dynamic_cast<const ProjectedCRS *>(other); return otherProjCRS != nullptr && - DerivedCRS::_isEquivalentTo(other, criterion); + DerivedCRS::_isEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- @@ -3394,23 +3456,33 @@ void ProjectedCRS::addUnitConvertAndAxisSwap(io::PROJStringFormatter *formatter, bool axisSpecFound) const { const auto &axisList = d->coordinateSystem()->axisList(); const auto &unit = axisList[0]->unit(); + const auto *zUnit = axisList.size() == 3 ? &(axisList[2]->unit()) : nullptr; if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE, - util::IComparable::Criterion::EQUIVALENT)) { + util::IComparable::Criterion::EQUIVALENT) || + (zUnit && + !zUnit->_isEquivalentTo(common::UnitOfMeasure::METRE, + util::IComparable::Criterion::EQUIVALENT))) { auto projUnit = unit.exportToPROJString(); const double toSI = unit.conversionToSI(); if (!formatter->getCRSExport()) { formatter->addStep("unitconvert"); formatter->addParam("xy_in", "m"); - if (!formatter->omitZUnitConversion()) + if (zUnit) formatter->addParam("z_in", "m"); + if (projUnit.empty()) { formatter->addParam("xy_out", toSI); - if (!formatter->omitZUnitConversion()) - formatter->addParam("z_out", toSI); } else { formatter->addParam("xy_out", projUnit); - if (!formatter->omitZUnitConversion()) - formatter->addParam("z_out", projUnit); + } + if (zUnit) { + auto projZUnit = zUnit->exportToPROJString(); + const double zToSI = zUnit->conversionToSI(); + if (projZUnit.empty()) { + formatter->addParam("z_out", zToSI); + } else { + formatter->addParam("z_out", projZUnit); + } } } else { if (projUnit.empty()) { @@ -3479,6 +3551,10 @@ void ProjectedCRS::addUnitConvertAndAxisSwap(io::PROJStringFormatter *formatter, * The candidate CRSs are either hard-coded, or looked in the database when * authorityFactory is not null. * + * Note that the implementation uses a set of heuristics to have a good + * compromise of successful identifications over execution time. It might miss + * legitimate matches in some circumstances. + * * The method returns a list of matching reference CRS, and the percentage * (0-100) of confidence in the match. The list is sorted by decreasing * confidence. @@ -3545,14 +3621,19 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { const auto &conv = derivingConversionRef(); const auto &cs = coordinateSystem(); + io::DatabaseContextPtr dbContext = + authorityFactory ? authorityFactory->databaseContext().as_nullable() + : nullptr; + if (baseRes.size() == 1 && baseRes.front().second >= 70 && conv->isUTM(zone, north) && cs->_isEquivalentTo( cs::CartesianCS::createEastingNorthing(common::UnitOfMeasure::METRE) - .get())) { + .get(), + util::IComparable::Criterion::EQUIVALENT, dbContext)) { if (baseRes.front().first->_isEquivalentTo( GeographicCRS::EPSG_4326.get(), - util::IComparable::Criterion::EQUIVALENT)) { + util::IComparable::Criterion::EQUIVALENT, dbContext)) { std::string crsName( computeUTMCRSName("WGS 84 / UTM zone ", zone, north)); res.emplace_back( @@ -3566,7 +3647,7 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { north && baseRes.front().first->_isEquivalentTo( GeographicCRS::EPSG_4267.get(), - util::IComparable::Criterion::EQUIVALENT)) { + util::IComparable::Criterion::EQUIVALENT, dbContext)) { std::string crsName( computeUTMCRSName("NAD27 / UTM zone ", zone, north)); res.emplace_back( @@ -3581,7 +3662,7 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { north && baseRes.front().first->_isEquivalentTo( GeographicCRS::EPSG_4269.get(), - util::IComparable::Criterion::EQUIVALENT)) { + util::IComparable::Criterion::EQUIVALENT, dbContext)) { std::string crsName( computeUTMCRSName("NAD83 / UTM zone ", zone, north)); res.emplace_back( @@ -3613,9 +3694,9 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { *id->codeSpace()) ->createProjectedCRS(id->code()); bool match = _isEquivalentTo( - crs.get(), - util::IComparable::Criterion:: - EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS); + crs.get(), util::IComparable::Criterion:: + EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, + dbContext); res.emplace_back(crs, match ? 100 : 25); return res; } catch (const std::exception &) { @@ -3636,9 +3717,9 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { thisName.c_str(), crs->nameStr().c_str()); foundEquivalentName |= eqName; if (_isEquivalentTo( - crs.get(), - util::IComparable::Criterion:: - EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)) { + crs.get(), util::IComparable::Criterion:: + EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, + dbContext)) { if (crs->nameStr() == thisName) { res.clear(); res.emplace_back(crsNN, 100); @@ -3650,10 +3731,12 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { l_baseCRS->_isEquivalentTo( crs->baseCRS().get(), util::IComparable::Criterion:: - EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS) && + EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, + dbContext) && derivingConversionRef()->_isEquivalentTo( crs->derivingConversionRef().get(), - util::IComparable::Criterion::EQUIVALENT) && + util::IComparable::Criterion::EQUIVALENT, + dbContext) && objects.size() == 1) { res.clear(); res.emplace_back(crsNN, 100); @@ -3720,17 +3803,21 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { if (_isEquivalentTo(crs.get(), util::IComparable::Criterion:: - EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)) { + EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, + dbContext)) { res.emplace_back(crs, unsignificantName ? 90 : 70); } else if (ellipsoid->_isEquivalentTo( crs->baseCRS()->ellipsoid().get(), - util::IComparable::Criterion::EQUIVALENT) && + util::IComparable::Criterion::EQUIVALENT, + dbContext) && derivingConversionRef()->_isEquivalentTo( crs->derivingConversionRef().get(), - util::IComparable::Criterion::EQUIVALENT)) { + util::IComparable::Criterion::EQUIVALENT, + dbContext)) { if (coordinateSystem()->_isEquivalentTo( crs->coordinateSystem().get(), - util::IComparable::Criterion::EQUIVALENT)) { + util::IComparable::Criterion::EQUIVALENT, + dbContext)) { res.emplace_back(crs, 70); } else { res.emplace_back(crs, 50); @@ -3960,12 +4047,12 @@ void CompoundCRS::_exportToPROJString( // --------------------------------------------------------------------------- bool CompoundCRS::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherCompoundCRS = dynamic_cast<const CompoundCRS *>(other); if (otherCompoundCRS == nullptr || (criterion == util::IComparable::Criterion::STRICT && - !ObjectUsage::_isEquivalentTo(other, criterion))) { + !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { return false; } const auto &components = componentReferenceSystems(); @@ -3974,8 +4061,8 @@ bool CompoundCRS::_isEquivalentTo( return false; } for (size_t i = 0; i < components.size(); i++) { - if (!components[i]->_isEquivalentTo(otherComponents[i].get(), - criterion)) { + if (!components[i]->_isEquivalentTo(otherComponents[i].get(), criterion, + dbContext)) { return false; } } @@ -3989,6 +4076,10 @@ bool CompoundCRS::_isEquivalentTo( * The candidate CRSs are looked in the database when * authorityFactory is not null. * + * Note that the implementation uses a set of heuristics to have a good + * compromise of successful identifications over execution time. It might miss + * legitimate matches in some circumstances. + * * The method returns a list of matching reference CRS, and the percentage * (0-100) of confidence in the match. The list is sorted by decreasing * confidence. @@ -4015,6 +4106,8 @@ CompoundCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { const auto &thisName(nameStr()); if (authorityFactory) { + const io::DatabaseContextNNPtr &dbContext = + authorityFactory->databaseContext(); const bool unsignificantName = thisName.empty() || ci_equal(thisName, "unknown") || @@ -4028,12 +4121,11 @@ CompoundCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { if (hasCodeCompatibleOfAuthorityFactory(id, authorityFactory)) { try { auto crs = io::AuthorityFactory::create( - authorityFactory->databaseContext(), - *id->codeSpace()) + dbContext, *id->codeSpace()) ->createCompoundCRS(id->code()); bool match = _isEquivalentTo( - crs.get(), - util::IComparable::Criterion::EQUIVALENT); + crs.get(), util::IComparable::Criterion::EQUIVALENT, + dbContext); res.emplace_back(crs, match ? 100 : 25); return res; } catch (const std::exception &) { @@ -4054,8 +4146,8 @@ CompoundCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { thisName.c_str(), crs->nameStr().c_str()); foundEquivalentName |= eqName; if (_isEquivalentTo( - crs.get(), - util::IComparable::Criterion::EQUIVALENT)) { + crs.get(), util::IComparable::Criterion::EQUIVALENT, + dbContext)) { if (crs->nameStr() == thisName) { res.clear(); res.emplace_back(crsNN, 100); @@ -4122,7 +4214,8 @@ CompoundCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { } if (_isEquivalentTo(crs.get(), - util::IComparable::Criterion::EQUIVALENT)) { + util::IComparable::Criterion::EQUIVALENT, + dbContext)) { res.emplace_back(crs, unsignificantName ? 90 : 70); } else { res.emplace_back(crs, 25); @@ -4494,20 +4587,22 @@ void BoundCRS::_exportToPROJString( // --------------------------------------------------------------------------- bool BoundCRS::_isEquivalentTo(const util::IComparable *other, - util::IComparable::Criterion criterion) const { + util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherBoundCRS = dynamic_cast<const BoundCRS *>(other); if (otherBoundCRS == nullptr || (criterion == util::IComparable::Criterion::STRICT && - !ObjectUsage::_isEquivalentTo(other, criterion))) { + !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { return false; } const auto standardCriterion = getStandardCriterion(criterion); return d->baseCRS_->_isEquivalentTo(otherBoundCRS->d->baseCRS_.get(), - criterion) && + criterion, dbContext) && d->hubCRS_->_isEquivalentTo(otherBoundCRS->d->hubCRS_.get(), - criterion) && + criterion, dbContext) && d->transformation_->_isEquivalentTo( - otherBoundCRS->d->transformation_.get(), standardCriterion); + otherBoundCRS->d->transformation_.get(), standardCriterion, + dbContext); } // --------------------------------------------------------------------------- @@ -4518,10 +4613,36 @@ std::list<std::pair<CRSNNPtr, int>> BoundCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { typedef std::pair<CRSNNPtr, int> Pair; std::list<Pair> res; - if (authorityFactory && - d->hubCRS_->_isEquivalentTo(GeographicCRS::EPSG_4326.get(), - util::IComparable::Criterion::EQUIVALENT)) { + if (!authorityFactory) + return res; + std::list<Pair> resMatchOfTransfToWGS84; + const io::DatabaseContextNNPtr &dbContext = + authorityFactory->databaseContext(); + if (d->hubCRS_->_isEquivalentTo(GeographicCRS::EPSG_4326.get(), + util::IComparable::Criterion::EQUIVALENT, + dbContext)) { auto resTemp = d->baseCRS_->identify(authorityFactory); + + std::string refTransfPROJString; + bool refTransfPROJStringValid = false; + auto refTransf = d->transformation_->normalizeForVisualization(); + try { + refTransfPROJString = refTransf->exportToPROJString( + io::PROJStringFormatter::create().get()); + refTransfPROJString = replaceAll( + refTransfPROJString, + " +rx=0 +ry=0 +rz=0 +s=0 +convention=position_vector", ""); + refTransfPROJStringValid = true; + } catch (const std::exception &) { + } + bool refIsNullTransform = false; + if (isTOWGS84Compatible()) { + auto params = transformation()->getTOWGS84Parameters(); + if (params == std::vector<double>{0, 0, 0, 0, 0, 0, 0}) { + refIsNullTransform = true; + } + } + for (const auto &pair : resTemp) { const auto &candidateBaseCRS = pair.first; auto projCRS = @@ -4532,54 +4653,47 @@ BoundCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { if (geodCRS) { auto context = operation::CoordinateOperationContext::create( authorityFactory, nullptr, 0.0); + context->setSpatialCriterion( + operation::CoordinateOperationContext::SpatialCriterion:: + PARTIAL_INTERSECTION); auto ops = operation::CoordinateOperationFactory::create() ->createOperations(NN_NO_CHECK(geodCRS), GeographicCRS::EPSG_4326, context); - std::string refTransfPROJString; - bool refTransfPROJStringValid = false; - try { - refTransfPROJString = - d->transformation_->exportToPROJString( - io::PROJStringFormatter::create().get()); - refTransfPROJStringValid = true; - if (refTransfPROJString == "+proj=axisswap +order=2,1") { - refTransfPROJString.clear(); - } - } catch (const std::exception &) { - } + bool foundOp = false; for (const auto &op : ops) { + auto opNormalized = op->normalizeForVisualization(); std::string opTransfPROJString; bool opTransfPROJStringValid = false; if (op->nameStr().find("Ballpark geographic") == 0) { - if (isTOWGS84Compatible()) { - auto params = - transformation()->getTOWGS84Parameters(); - if (params == - std::vector<double>{0, 0, 0, 0, 0, 0, 0}) { - res.emplace_back(create(candidateBaseCRS, - d->hubCRS_, - transformation()), - pair.second); - foundOp = true; - break; - } + if (refIsNullTransform) { + res.emplace_back(create(candidateBaseCRS, + d->hubCRS_, + transformation()), + pair.second); + foundOp = true; + break; } continue; } try { - opTransfPROJString = op->exportToPROJString( + opTransfPROJString = opNormalized->exportToPROJString( io::PROJStringFormatter::create().get()); opTransfPROJStringValid = true; + opTransfPROJString = replaceAll( + opTransfPROJString, " +rx=0 +ry=0 +rz=0 +s=0 " + "+convention=position_vector", + ""); } catch (const std::exception &) { } if ((refTransfPROJStringValid && opTransfPROJStringValid && refTransfPROJString == opTransfPROJString) || - op->_isEquivalentTo( - d->transformation_.get(), - util::IComparable::Criterion::EQUIVALENT)) { - res.emplace_back( + opNormalized->_isEquivalentTo( + refTransf.get(), + util::IComparable::Criterion::EQUIVALENT, + dbContext)) { + resMatchOfTransfToWGS84.emplace_back( create(candidateBaseCRS, d->hubCRS_, NN_NO_CHECK(util::nn_dynamic_pointer_cast< operation::Transformation>(op))), @@ -4596,7 +4710,7 @@ BoundCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { } } } - return res; + return !resMatchOfTransfToWGS84.empty() ? resMatchOfTransfToWGS84 : res; } // --------------------------------------------------------------------------- @@ -4758,11 +4872,11 @@ void DerivedGeodeticCRS::_exportToPROJString( // --------------------------------------------------------------------------- bool DerivedGeodeticCRS::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherDerivedCRS = dynamic_cast<const DerivedGeodeticCRS *>(other); return otherDerivedCRS != nullptr && - DerivedCRS::_isEquivalentTo(other, criterion); + DerivedCRS::_isEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- @@ -4908,11 +5022,11 @@ void DerivedGeographicCRS::_exportToPROJString( // --------------------------------------------------------------------------- bool DerivedGeographicCRS::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherDerivedCRS = dynamic_cast<const DerivedGeographicCRS *>(other); return otherDerivedCRS != nullptr && - DerivedCRS::_isEquivalentTo(other, criterion); + DerivedCRS::_isEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- @@ -5055,11 +5169,11 @@ void DerivedProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const { // --------------------------------------------------------------------------- bool DerivedProjectedCRS::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherDerivedCRS = dynamic_cast<const DerivedProjectedCRS *>(other); return otherDerivedCRS != nullptr && - DerivedCRS::_isEquivalentTo(other, criterion); + DerivedCRS::_isEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- @@ -5185,11 +5299,11 @@ void TemporalCRS::_exportToJSON( // --------------------------------------------------------------------------- bool TemporalCRS::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherTemporalCRS = dynamic_cast<const TemporalCRS *>(other); return otherTemporalCRS != nullptr && - SingleCRS::baseIsEquivalentTo(other, criterion); + SingleCRS::baseIsEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- @@ -5323,11 +5437,11 @@ void EngineeringCRS::_exportToJSON( // --------------------------------------------------------------------------- bool EngineeringCRS::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherEngineeringCRS = dynamic_cast<const EngineeringCRS *>(other); return otherEngineeringCRS != nullptr && - SingleCRS::baseIsEquivalentTo(other, criterion); + SingleCRS::baseIsEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- @@ -5455,11 +5569,11 @@ void ParametricCRS::_exportToJSON( // --------------------------------------------------------------------------- bool ParametricCRS::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherParametricCRS = dynamic_cast<const ParametricCRS *>(other); return otherParametricCRS != nullptr && - SingleCRS::baseIsEquivalentTo(other, criterion); + SingleCRS::baseIsEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- @@ -5560,11 +5674,11 @@ void DerivedVerticalCRS::_exportToPROJString( // --------------------------------------------------------------------------- bool DerivedVerticalCRS::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherDerivedCRS = dynamic_cast<const DerivedVerticalCRS *>(other); return otherDerivedCRS != nullptr && - DerivedCRS::_isEquivalentTo(other, criterion); + DerivedCRS::_isEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- @@ -5681,11 +5795,11 @@ void DerivedCRSTemplate<DerivedCRSTraits>::_exportToWKT( template <class DerivedCRSTraits> bool DerivedCRSTemplate<DerivedCRSTraits>::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherDerivedCRS = dynamic_cast<const DerivedCRSTemplate *>(other); return otherDerivedCRS != nullptr && - DerivedCRS::_isEquivalentTo(other, criterion); + DerivedCRS::_isEquivalentTo(other, criterion, dbContext); } //! @endcond diff --git a/src/iso19111/datum.cpp b/src/iso19111/datum.cpp index 321fe93f..d9d9c261 100644 --- a/src/iso19111/datum.cpp +++ b/src/iso19111/datum.cpp @@ -213,11 +213,13 @@ void Datum::setProperties( // --------------------------------------------------------------------------- -bool Datum::__isEquivalentTo(const util::IComparable *other, - util::IComparable::Criterion criterion) const { +//! @cond Doxygen_Suppress +bool Datum::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherDatum = dynamic_cast<const Datum *>(other); if (otherDatum == nullptr || - !ObjectUsage::_isEquivalentTo(other, criterion)) { + !ObjectUsage::_isEquivalentTo(other, criterion, dbContext)) { return false; } if (criterion == util::IComparable::Criterion::STRICT) { @@ -248,12 +250,13 @@ bool Datum::__isEquivalentTo(const util::IComparable *other, } if (conventionalRS() && otherDatum->conventionalRS() && conventionalRS()->_isEquivalentTo( - otherDatum->conventionalRS().get(), criterion)) { + otherDatum->conventionalRS().get(), criterion, dbContext)) { return false; } } return true; } +//! @endcond // --------------------------------------------------------------------------- @@ -450,11 +453,11 @@ void PrimeMeridian::_exportToPROJString( //! @cond Doxygen_Suppress bool PrimeMeridian::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherPM = dynamic_cast<const PrimeMeridian *>(other); if (otherPM == nullptr || - !IdentifiedObject::_isEquivalentTo(other, criterion)) { + !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { return false; } // In MapInfo, the Paris prime meridian is returned as 2.3372291666667 @@ -984,11 +987,12 @@ EllipsoidNNPtr Ellipsoid::identify() const { //! @cond Doxygen_Suppress bool Ellipsoid::_isEquivalentTo(const util::IComparable *other, - util::IComparable::Criterion criterion) const { + util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherEllipsoid = dynamic_cast<const Ellipsoid *>(other); if (otherEllipsoid == nullptr || (criterion == util::IComparable::Criterion::STRICT && - !IdentifiedObject::_isEquivalentTo(other, criterion))) { + !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext))) { return false; } @@ -1197,7 +1201,8 @@ void GeodeticReferenceFrame::_exportToWKT( io::WKTFormatter *formatter) const // throw(FormattingException) { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; - formatter->startNode(io::WKTConstants::DATUM, !identifiers().empty()); + const auto &ids = identifiers(); + formatter->startNode(io::WKTConstants::DATUM, !ids.empty()); auto l_name = nameStr(); if (l_name.empty()) { l_name = "unnamed"; @@ -1232,10 +1237,35 @@ void GeodeticReferenceFrame::_exportToWKT( } } } - // Replace spaces by underscore, except if it is a special MapInfo - // datum name - } else if (!starts_with(l_name, "MIF ")) { - l_name = io::WKTFormatter::morphNameToESRI(l_name); + } else { + // Replace spaces by underscore for datum names coming from EPSG + // so as to emulate GDAL < 3 importFromEPSG() + if (ids.size() == 1 && *(ids.front()->codeSpace()) == "EPSG") { + l_name = io::WKTFormatter::morphNameToESRI(l_name); + } else if (ids.empty()) { + const auto &dbContext = formatter->databaseContext(); + if (dbContext) { + auto factory = io::AuthorityFactory::create( + NN_NO_CHECK(dbContext), std::string()); + // We use anonymous autority and approximate matching, so + // as to trigger the caching done in createObjectsFromName() + // in that case. + auto matches = factory->createObjectsFromName( + l_name, {io::AuthorityFactory::ObjectType:: + GEODETIC_REFERENCE_FRAME}, + true, 2); + if (matches.size() == 1) { + const auto &match = matches.front(); + const auto &matchId = match->identifiers(); + if (matchId.size() == 1 && + *(matchId.front()->codeSpace()) == "EPSG" && + metadata::Identifier::isEquivalentName( + l_name.c_str(), match->nameStr().c_str())) { + l_name = io::WKTFormatter::morphNameToESRI(l_name); + } + } + } + } if (l_name == "World_Geodetic_System_1984") { l_name = "WGS_1984"; } @@ -1324,20 +1354,63 @@ void GeodeticReferenceFrame::_exportToJSON( //! @cond Doxygen_Suppress bool GeodeticReferenceFrame::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherGRF = dynamic_cast<const GeodeticReferenceFrame *>(other); - if (otherGRF == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + if (otherGRF == nullptr || + !Datum::_isEquivalentTo(other, criterion, dbContext)) { return false; } return primeMeridian()->_isEquivalentTo(otherGRF->primeMeridian().get(), - criterion) && - ellipsoid()->_isEquivalentTo(otherGRF->ellipsoid().get(), criterion); + criterion, dbContext) && + ellipsoid()->_isEquivalentTo(otherGRF->ellipsoid().get(), criterion, + dbContext); } //! @endcond // --------------------------------------------------------------------------- +bool GeodeticReferenceFrame::hasEquivalentNameToUsingAlias( + const IdentifiedObject *other, + const io::DatabaseContextPtr &dbContext) const { + if (dbContext) { + if (!identifiers().empty()) { + const auto &id = identifiers().front(); + auto aliases = + dbContext->getAliases(*(id->codeSpace()), id->code(), nameStr(), + "geodetic_datum", std::string()); + const char *otherName = other->nameStr().c_str(); + for (const auto &alias : aliases) { + if (metadata::Identifier::isEquivalentName(otherName, + alias.c_str())) { + return true; + } + } + return false; + } else if (!other->identifiers().empty()) { + auto otherGRF = dynamic_cast<const GeodeticReferenceFrame *>(other); + if (otherGRF) { + return otherGRF->hasEquivalentNameToUsingAlias(this, dbContext); + } + return false; + } + + auto aliases = + dbContext->getAliases(std::string(), std::string(), nameStr(), + "geodetic_datum", std::string()); + const char *otherName = other->nameStr().c_str(); + for (const auto &alias : aliases) { + if (metadata::Identifier::isEquivalentName(otherName, + alias.c_str())) { + return true; + } + } + } + return false; +} + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress struct DynamicGeodeticReferenceFrame::Private { common::Measure frameReferenceEpoch{}; @@ -1407,11 +1480,11 @@ DynamicGeodeticReferenceFrame::deformationModelName() const { //! @cond Doxygen_Suppress bool DynamicGeodeticReferenceFrame::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherDGRF = dynamic_cast<const DynamicGeodeticReferenceFrame *>(other); if (otherDGRF == nullptr || - !GeodeticReferenceFrame::_isEquivalentTo(other, criterion)) { + !GeodeticReferenceFrame::_isEquivalentTo(other, criterion, dbContext)) { return false; } return frameReferenceEpoch()._isEquivalentTo( @@ -1842,10 +1915,11 @@ void VerticalReferenceFrame::_exportToJSON( //! @cond Doxygen_Suppress bool VerticalReferenceFrame::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherVRF = dynamic_cast<const VerticalReferenceFrame *>(other); - if (otherVRF == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + if (otherVRF == nullptr || + !Datum::_isEquivalentTo(other, criterion, dbContext)) { return false; } if ((realizationMethod().has_value() ^ @@ -1932,11 +2006,11 @@ DynamicVerticalReferenceFrame::deformationModelName() const { //! @cond Doxygen_Suppress bool DynamicVerticalReferenceFrame::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherDGRF = dynamic_cast<const DynamicVerticalReferenceFrame *>(other); if (otherDGRF == nullptr || - !VerticalReferenceFrame::_isEquivalentTo(other, criterion)) { + !VerticalReferenceFrame::_isEquivalentTo(other, criterion, dbContext)) { return false; } return frameReferenceEpoch()._isEquivalentTo( @@ -2131,10 +2205,11 @@ void TemporalDatum::_exportToJSON( //! @cond Doxygen_Suppress bool TemporalDatum::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherTD = dynamic_cast<const TemporalDatum *>(other); - if (otherTD == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + if (otherTD == nullptr || + !Datum::_isEquivalentTo(other, criterion, dbContext)) { return false; } return temporalOrigin().toString() == @@ -2223,10 +2298,11 @@ void EngineeringDatum::_exportToJSON( //! @cond Doxygen_Suppress bool EngineeringDatum::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherTD = dynamic_cast<const EngineeringDatum *>(other); - if (otherTD == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + if (otherTD == nullptr || + !Datum::_isEquivalentTo(other, criterion, dbContext)) { return false; } return true; @@ -2308,10 +2384,11 @@ void ParametricDatum::_exportToJSON( //! @cond Doxygen_Suppress bool ParametricDatum::_isEquivalentTo( - const util::IComparable *other, - util::IComparable::Criterion criterion) const { + const util::IComparable *other, util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherTD = dynamic_cast<const ParametricDatum *>(other); - if (otherTD == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + if (otherTD == nullptr || + !Datum::_isEquivalentTo(other, criterion, dbContext)) { return false; } return true; diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp index 96e62c16..7d15172e 100644 --- a/src/iso19111/factory.cpp +++ b/src/iso19111/factory.cpp @@ -264,6 +264,9 @@ struct DatabaseContext::Private { std::map<std::string, std::vector<std::string>> cacheAllowedAuthorities_{}; + lru11::Cache<std::string, std::list<std::string>> cacheAliasNames_{ + CACHE_SIZE}; + static void insertIntoCache(LRUCacheOfObjects &cache, const std::string &code, const util::BaseObjectPtr &obj); @@ -1014,7 +1017,7 @@ bool DatabaseContext::isKnownName(const std::string &name, /** \brief Gets the alias name from an official name. * - * @param officialName Official name. + * @param officialName Official name. Mandatory * @param tableName Table name/category. Mandatory * @param source Source of the alias. Mandatory * @return Alias name (or empty if not found). @@ -1046,6 +1049,63 @@ DatabaseContext::getAliasFromOfficialName(const std::string &officialName, // --------------------------------------------------------------------------- +/** \brief Gets the alias names for an object. + * + * Either authName + code or officialName must be non empty. + * + * @param authName Authority. + * @param code Code. + * @param officialName Official name. + * @param tableName Table name/category. Mandatory + * @param source Source of the alias. May be empty. + * @return Aliases + */ +std::list<std::string> DatabaseContext::getAliases( + const std::string &authName, const std::string &code, + const std::string &officialName, const std::string &tableName, + const std::string &source) const { + + std::list<std::string> res; + const auto key(authName + code + officialName + tableName + source); + if (d->cacheAliasNames_.tryGet(key, res)) { + return res; + } + + std::string resolvedAuthName(authName); + std::string resolvedCode(code); + if (authName.empty() || code.empty()) { + std::string sql("SELECT auth_name, code FROM \""); + sql += replaceAll(tableName, "\"", "\"\""); + sql += "\" WHERE name = ?"; + if (tableName == "geodetic_crs") { + sql += " AND type = " GEOG_2D_SINGLE_QUOTED; + } + auto resSql = d->run(sql, {officialName}); + if (resSql.empty()) { + d->cacheAliasNames_.insert(key, res); + return res; + } + const auto &row = resSql.front(); + resolvedAuthName = row[0]; + resolvedCode = row[1]; + } + std::string sql("SELECT alt_name FROM alias_name WHERE table_name = ? AND " + "auth_name = ? AND code = ?"); + ListOfParams params{tableName, resolvedAuthName, resolvedCode}; + if (!source.empty()) { + sql += " AND source = ?"; + params.emplace_back(source); + } + auto resSql = d->run(sql, params); + for (const auto &row : resSql) { + res.emplace_back(row[0]); + } + d->cacheAliasNames_.insert(key, res); + return res; +} + +// --------------------------------------------------------------------------- + /** \brief Return the 'text_definition' column of a table for an object * * @param tableName Table name/category. @@ -1349,9 +1409,17 @@ AuthorityFactory::AuthorityFactory(const DatabaseContextNNPtr &context, AuthorityFactoryNNPtr AuthorityFactory::create(const DatabaseContextNNPtr &context, const std::string &authorityName) { - - auto factory = AuthorityFactory::nn_make_shared<AuthorityFactory>( - context, authorityName); + const auto getFactory = [&context, &authorityName]() { + for (const auto &knownName : {"EPSG", "ESRI", "PROJ"}) { + if (ci_equal(authorityName, knownName)) { + return AuthorityFactory::nn_make_shared<AuthorityFactory>( + context, knownName); + } + } + return AuthorityFactory::nn_make_shared<AuthorityFactory>( + context, authorityName); + }; + auto factory = getFactory(); factory->d->setThis(factory); return factory; } @@ -5105,13 +5173,12 @@ static void addToListString(std::string &out, const char *in) { out += in; } -static void addToListStringWithOR(std::string &out, const char *in) { +static void addToListStringWithOR(std::string &out, const std::string &in) { if (!out.empty()) { out += " OR "; } out += in; } - //! @endcond // --------------------------------------------------------------------------- @@ -5148,6 +5215,7 @@ AuthorityFactory::createObjectsFromName( } std::string sql( + "SELECT table_name, auth_name, code, name, deprecated FROM (" "SELECT table_name, auth_name, code, name, deprecated FROM object_view " "WHERE "); if (deprecated) { @@ -5163,108 +5231,141 @@ AuthorityFactory::createObjectsFromName( params.emplace_back(d->authority()); } - if (allowedObjectTypes.empty()) { - sql += "table_name IN (" - "'prime_meridian','ellipsoid','geodetic_datum'," - "'vertical_datum','geodetic_crs','projected_crs'," - "'vertical_crs','compound_crs','conversion'," - "'helmert_transformation','grid_transformation'," - "'other_transformation','concatenated_operation'" - ")"; - } else { - std::string tableNameList; - std::string otherConditions; - for (const auto type : allowedObjectTypes) { - switch (type) { - case ObjectType::PRIME_MERIDIAN: - addToListString(tableNameList, "'prime_meridian'"); - break; - case ObjectType::ELLIPSOID: - addToListString(tableNameList, "'ellipsoid'"); - break; - case ObjectType::DATUM: - addToListString(tableNameList, - "'geodetic_datum','vertical_datum'"); - break; - case ObjectType::GEODETIC_REFERENCE_FRAME: - addToListString(tableNameList, "'geodetic_datum'"); - break; - case ObjectType::VERTICAL_REFERENCE_FRAME: - addToListString(tableNameList, "'vertical_datum'"); - break; - case ObjectType::CRS: - addToListString(tableNameList, "'geodetic_crs','projected_crs'," - "'vertical_crs','compound_crs'"); - break; - case ObjectType::GEODETIC_CRS: - addToListString(tableNameList, "'geodetic_crs'"); - break; - case ObjectType::GEOCENTRIC_CRS: - addToListStringWithOR(otherConditions, - "(table_name = " GEOCENTRIC_SINGLE_QUOTED - " AND " - "type = " GEOCENTRIC_SINGLE_QUOTED ")"); - break; - case ObjectType::GEOGRAPHIC_CRS: - addToListStringWithOR(otherConditions, - "(table_name = 'geodetic_crs' AND " - "type IN (" GEOG_2D_SINGLE_QUOTED - "," GEOG_3D_SINGLE_QUOTED "))"); - break; - case ObjectType::GEOGRAPHIC_2D_CRS: - addToListStringWithOR(otherConditions, - "(table_name = 'geodetic_crs' AND " - "type = " GEOG_2D_SINGLE_QUOTED ")"); - break; - case ObjectType::GEOGRAPHIC_3D_CRS: - addToListStringWithOR(otherConditions, - "(table_name = 'geodetic_crs' AND " - "type = " GEOG_3D_SINGLE_QUOTED ")"); - break; - case ObjectType::PROJECTED_CRS: - addToListString(tableNameList, "'projected_crs'"); - break; - case ObjectType::VERTICAL_CRS: - addToListString(tableNameList, "'vertical_crs'"); - break; - case ObjectType::COMPOUND_CRS: - addToListString(tableNameList, "'compound_crs'"); - break; - case ObjectType::COORDINATE_OPERATION: - addToListString(tableNameList, - "'conversion','helmert_transformation'," - "'grid_transformation','other_transformation'," - "'concatenated_operation'"); - break; - case ObjectType::CONVERSION: - addToListString(tableNameList, "'conversion'"); - break; - case ObjectType::TRANSFORMATION: - addToListString(tableNameList, - "'helmert_transformation'," - "'grid_transformation','other_transformation'"); - break; - case ObjectType::CONCATENATED_OPERATION: - addToListString(tableNameList, "'concatenated_operation'"); - break; + const auto getTableNameConstraint = [&allowedObjectTypes]( + const std::string &colName) { + if (allowedObjectTypes.empty()) { + return colName + " IN (" + "'prime_meridian','ellipsoid','geodetic_datum'," + "'vertical_datum','geodetic_crs','projected_crs'," + "'vertical_crs','compound_crs','conversion'," + "'helmert_transformation','grid_transformation'," + "'other_transformation','concatenated_operation'" + ")"; + } else { + std::string tableNameList; + std::string otherConditions; + for (const auto type : allowedObjectTypes) { + switch (type) { + case ObjectType::PRIME_MERIDIAN: + addToListString(tableNameList, "'prime_meridian'"); + break; + case ObjectType::ELLIPSOID: + addToListString(tableNameList, "'ellipsoid'"); + break; + case ObjectType::DATUM: + addToListString(tableNameList, + "'geodetic_datum','vertical_datum'"); + break; + case ObjectType::GEODETIC_REFERENCE_FRAME: + addToListString(tableNameList, "'geodetic_datum'"); + break; + case ObjectType::VERTICAL_REFERENCE_FRAME: + addToListString(tableNameList, "'vertical_datum'"); + break; + case ObjectType::CRS: + addToListString(tableNameList, + "'geodetic_crs','projected_crs'," + "'vertical_crs','compound_crs'"); + break; + case ObjectType::GEODETIC_CRS: + addToListString(tableNameList, "'geodetic_crs'"); + break; + case ObjectType::GEOCENTRIC_CRS: + addToListStringWithOR( + otherConditions, + "(" + colName + " = " GEOCENTRIC_SINGLE_QUOTED " AND " + "type = " GEOCENTRIC_SINGLE_QUOTED ")"); + break; + case ObjectType::GEOGRAPHIC_CRS: + addToListStringWithOR(otherConditions, + "(" + colName + + " = 'geodetic_crs' AND " + "type IN (" GEOG_2D_SINGLE_QUOTED + "," GEOG_3D_SINGLE_QUOTED "))"); + break; + case ObjectType::GEOGRAPHIC_2D_CRS: + addToListStringWithOR( + otherConditions, + "(" + colName + " = 'geodetic_crs' AND " + "type = " GEOG_2D_SINGLE_QUOTED ")"); + break; + case ObjectType::GEOGRAPHIC_3D_CRS: + addToListStringWithOR( + otherConditions, + "(" + colName + " = 'geodetic_crs' AND " + "type = " GEOG_3D_SINGLE_QUOTED ")"); + break; + case ObjectType::PROJECTED_CRS: + addToListString(tableNameList, "'projected_crs'"); + break; + case ObjectType::VERTICAL_CRS: + addToListString(tableNameList, "'vertical_crs'"); + break; + case ObjectType::COMPOUND_CRS: + addToListString(tableNameList, "'compound_crs'"); + break; + case ObjectType::COORDINATE_OPERATION: + addToListString( + tableNameList, + "'conversion','helmert_transformation'," + "'grid_transformation','other_transformation'," + "'concatenated_operation'"); + break; + case ObjectType::CONVERSION: + addToListString(tableNameList, "'conversion'"); + break; + case ObjectType::TRANSFORMATION: + addToListString( + tableNameList, + "'helmert_transformation'," + "'grid_transformation','other_transformation'"); + break; + case ObjectType::CONCATENATED_OPERATION: + addToListString(tableNameList, "'concatenated_operation'"); + break; + } } - } - if (!tableNameList.empty()) { - sql += "((table_name IN ("; - sql += tableNameList; - sql += "))"; - if (!otherConditions.empty()) { - sql += " OR "; - sql += otherConditions; + std::string l_sql; + if (!tableNameList.empty()) { + l_sql = "((" + colName + " IN ("; + l_sql += tableNameList; + l_sql += "))"; + if (!otherConditions.empty()) { + l_sql += " OR "; + l_sql += otherConditions; + } + l_sql += ')'; + } else if (!otherConditions.empty()) { + l_sql = "("; + l_sql += otherConditions; + l_sql += ')'; } - sql += ')'; - } else if (!otherConditions.empty()) { - sql += "("; - sql += otherConditions; - sql += ')'; + return l_sql; } + }; + + sql += getTableNameConstraint("table_name"); + + sql += " UNION SELECT ov.table_name AS table_name, " + "ov.auth_name AS auth_name, " + "ov.code AS code, a.alt_name AS name, " + "ov.deprecated AS deprecated FROM object_view ov " + "JOIN alias_name a ON ov.table_name = a.table_name AND " + "ov.auth_name = a.auth_name AND ov.code = a.code WHERE "; + if (deprecated) { + sql += "ov.deprecated = 1 AND "; } - sql += " ORDER BY deprecated, length(name), name"; + if (!approximateMatch) { + sql += "a.alt_name LIKE ? AND "; + params.push_back(searchedNameWithoutDeprecated); + } + if (d->hasAuthorityRestriction()) { + sql += "ov.auth_name = ? AND "; + params.emplace_back(d->authority()); + } + sql += getTableNameConstraint("ov.table_name"); + + sql += ") ORDER BY deprecated, length(name), name"; if (limitResultCount > 0 && limitResultCount < static_cast<size_t>(std::numeric_limits<int>::max()) && @@ -5274,6 +5375,7 @@ AuthorityFactory::createObjectsFromName( } std::list<common::IdentifiedObjectNNPtr> res; + std::set<std::pair<std::string, std::string>> setIdentified; // Querying geodetic datum is a super hot path when importing from WKT1 // so cache results. @@ -5301,6 +5403,12 @@ AuthorityFactory::createObjectsFromName( for (const auto &row : listOfRow) { const auto &auth_name = row[1]; const auto &code = row[2]; + const auto key = + std::pair<std::string, std::string>(auth_name, code); + if (setIdentified.find(key) != setIdentified.end()) { + continue; + } + setIdentified.insert(key); auto factory = d->createFactory(auth_name); res.emplace_back(factory->createGeodeticDatum(code)); if (limitResultCount > 0 && res.size() == limitResultCount) { @@ -5326,6 +5434,12 @@ AuthorityFactory::createObjectsFromName( const auto &auth_name = row[1]; const auto &code = row[2]; + const auto key = + std::pair<std::string, std::string>(auth_name, code); + if (setIdentified.find(key) != setIdentified.end()) { + continue; + } + setIdentified.insert(key); auto factory = d->createFactory(auth_name); res.emplace_back(factory->createGeodeticDatum(code)); if (limitResultCount > 0 && @@ -5361,6 +5475,12 @@ AuthorityFactory::createObjectsFromName( const auto &table_name = row[0]; const auto &auth_name = row[1]; const auto &code = row[2]; + const auto key = + std::pair<std::string, std::string>(auth_name, code); + if (setIdentified.find(key) != setIdentified.end()) { + continue; + } + setIdentified.insert(key); const auto &deprecatedStr = row[4]; if (isFirst) { firstIsDeprecated = deprecatedStr == "1"; @@ -5526,6 +5646,7 @@ std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum( sql += " AND type = ?"; params.emplace_back(geodetic_crs_type); } + sql += " ORDER BY auth_name, code"; auto sqlRes = d->run(sql, params); std::list<crs::GeodeticCRSNNPtr> res; for (const auto &row : sqlRes) { @@ -5607,7 +5728,9 @@ static std::string buildSqlLookForAuthNameCode( std::set<std::string> authorities; for (const auto &crs : list) { - const auto &ids = crs.first->identifiers(); + auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get()); + const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers() + : crs.first->identifiers(); if (!ids.empty()) { authorities.insert(*(ids[0]->codeSpace())); } @@ -5626,7 +5749,9 @@ static std::string buildSqlLookForAuthNameCode( params.emplace_back(auth_name); bool firstGeodCRSForAuth = true; for (const auto &crs : list) { - const auto &ids = crs.first->identifiers(); + auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get()); + const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers() + : crs.first->identifiers(); if (!ids.empty() && *(ids[0]->codeSpace()) == auth_name) { if (!firstGeodCRSForAuth) { sql += ','; diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index 2aec5fac..c013ce24 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -2051,12 +2051,27 @@ GeodeticReferenceFrameNNPtr WKTParser::Private::buildGeodeticReferenceFrame( auto res = authFactory->createObjectsFromName( name, {AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, true, 1); - bool foundDatumName = false; if (!res.empty()) { + bool foundDatumName = false; const auto &refDatum = res.front(); if (metadata::Identifier::isEquivalentName( name.c_str(), refDatum->nameStr().c_str())) { foundDatumName = true; + } else if (refDatum->identifiers().size() == 1) { + const auto &id = refDatum->identifiers()[0]; + const auto aliases = + authFactory->databaseContext()->getAliases( + *id->codeSpace(), id->code(), refDatum->nameStr(), + "geodetic_datum", std::string()); + for (const auto &alias : aliases) { + if (metadata::Identifier::isEquivalentName( + name.c_str(), alias.c_str())) { + foundDatumName = true; + break; + } + } + } + if (foundDatumName) { properties.set(IdentifiedObject::NAME_KEY, refDatum->nameStr()); if (!properties.get(Identifier::CODESPACE_KEY) && @@ -2083,25 +2098,12 @@ GeodeticReferenceFrameNNPtr WKTParser::Private::buildGeodeticReferenceFrame( NN_NO_CHECK(dbContext_), *id->codeSpace()); auto dbDatum = authFactory2->createGeodeticDatum(id->code()); - foundDatumName = true; properties.set(IdentifiedObject::NAME_KEY, dbDatum->nameStr()); } catch (const std::exception &) { } } } - - if (!foundDatumName) { - std::string outTableName; - std::string authNameFromAlias; - std::string codeFromAlias; - auto officialName = authFactory->getOfficialNameFromAlias( - name, "geodetic_datum", std::string(), true, outTableName, - authNameFromAlias, codeFromAlias); - if (!officialName.empty()) { - properties.set(IdentifiedObject::NAME_KEY, officialName); - } - } } } @@ -2683,7 +2685,9 @@ WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node) { auto cs = buildCS(csNode, node, angularUnit); auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs); if (ellipsoidalCS) { - assert(!ci_equal(nodeName, WKTConstants::GEOCCS)); + if (ci_equal(nodeName, WKTConstants::GEOCCS)) { + throw ParsingException("ellipsoidal CS not expected in GEOCCS"); + } try { auto crs = GeographicCRS::create(props, datum, datumEnsemble, NN_NO_CHECK(ellipsoidalCS)); @@ -5669,13 +5673,18 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext)); const auto &authName = tokens[0]; const auto &code = tokens[1]; - static const std::string epsg_lowercase("epsg"); - auto factory = AuthorityFactory::create( - dbContextNNPtr, - authName == epsg_lowercase ? Identifier::EPSG : authName); + auto factory = AuthorityFactory::create(dbContextNNPtr, authName); try { return factory->createCoordinateReferenceSystem(code); } catch (...) { + + // Convenience for well-known misused code + // See https://github.com/OSGeo/PROJ/issues/1730 + if (ci_equal(authName, "EPSG") && code == "102100") { + factory = AuthorityFactory::create(dbContextNNPtr, "ESRI"); + return factory->createCoordinateReferenceSystem(code); + } + const auto authorities = dbContextNNPtr->getAuthorities(); for (const auto &authCandidate : authorities) { if (ci_equal(authCandidate, authName)) { @@ -7157,6 +7166,12 @@ void PROJStringFormatter::stopInversion() { // the current end of steps for (auto iter = startIter; iter != d->steps_.end(); ++iter) { iter->inverted = !iter->inverted; + for (auto ¶mValue : iter->paramValues) { + if (paramValue.key == "omit_fwd") + paramValue.key = "omit_inv"; + else if (paramValue.key == "omit_inv") + paramValue.key = "omit_fwd"; + } } // And reverse the order of steps in that range as well. std::reverse(startIter, d->steps_.end()); diff --git a/src/iso19111/metadata.cpp b/src/iso19111/metadata.cpp index 41653b32..6266a86d 100644 --- a/src/iso19111/metadata.cpp +++ b/src/iso19111/metadata.cpp @@ -237,7 +237,8 @@ GeographicBoundingBoxNNPtr GeographicBoundingBox::create(double west, //! @cond Doxygen_Suppress bool GeographicBoundingBox::_isEquivalentTo( - const util::IComparable *other, util::IComparable::Criterion) const { + const util::IComparable *other, util::IComparable::Criterion, + const io::DatabaseContextPtr &) const { auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other); if (!otherExtent) return false; @@ -502,7 +503,8 @@ VerticalExtent::create(double minimumIn, double maximumIn, //! @cond Doxygen_Suppress bool VerticalExtent::_isEquivalentTo(const util::IComparable *other, - util::IComparable::Criterion) const { + util::IComparable::Criterion, + const io::DatabaseContextPtr &) const { auto otherExtent = dynamic_cast<const VerticalExtent *>(other); if (!otherExtent) return false; @@ -587,7 +589,8 @@ TemporalExtentNNPtr TemporalExtent::create(const std::string &start, //! @cond Doxygen_Suppress bool TemporalExtent::_isEquivalentTo(const util::IComparable *other, - util::IComparable::Criterion) const { + util::IComparable::Criterion, + const io::DatabaseContextPtr &) const { auto otherExtent = dynamic_cast<const TemporalExtent *>(other); if (!otherExtent) return false; @@ -734,7 +737,8 @@ Extent::createFromBBOX(double west, double south, double east, double north, //! @cond Doxygen_Suppress bool Extent::_isEquivalentTo(const util::IComparable *other, - util::IComparable::Criterion criterion) const { + util::IComparable::Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { auto otherExtent = dynamic_cast<const Extent *>(other); bool ret = (otherExtent && @@ -749,15 +753,18 @@ bool Extent::_isEquivalentTo(const util::IComparable *other, if (ret) { for (size_t i = 0; ret && i < d->geographicElements_.size(); ++i) { ret = d->geographicElements_[i]->_isEquivalentTo( - otherExtent->d->geographicElements_[i].get(), criterion); + otherExtent->d->geographicElements_[i].get(), criterion, + dbContext); } for (size_t i = 0; ret && i < d->verticalElements_.size(); ++i) { ret = d->verticalElements_[i]->_isEquivalentTo( - otherExtent->d->verticalElements_[i].get(), criterion); + otherExtent->d->verticalElements_[i].get(), criterion, + dbContext); } for (size_t i = 0; ret && i < d->temporalElements_.size(); ++i) { ret = d->temporalElements_[i]->_isEquivalentTo( - otherExtent->d->temporalElements_[i].get(), criterion); + otherExtent->d->temporalElements_[i].get(), criterion, + dbContext); } } return ret; diff --git a/src/iso19111/util.cpp b/src/iso19111/util.cpp index 25207d5c..2a6178e2 100644 --- a/src/iso19111/util.cpp +++ b/src/iso19111/util.cpp @@ -687,11 +687,13 @@ IComparable::~IComparable() = default; /** \brief Returns whether an object is equivalent to another one. * @param other other object to compare to * @param criterion comparaison criterion. + * @param dbContext Database context, or nullptr. * @return true if objects are equivalent. */ -bool IComparable::isEquivalentTo(const IComparable *other, - Criterion criterion) const { - return _isEquivalentTo(other, criterion); +bool IComparable::isEquivalentTo( + const IComparable *other, Criterion criterion, + const io::DatabaseContextPtr &dbContext) const { + return _isEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- diff --git a/src/jniproj.cpp b/src/jniproj.cpp deleted file mode 100644 index 6f441529..00000000 --- a/src/jniproj.cpp +++ /dev/null @@ -1,469 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Java/JNI wrappers for PROJ API. - * Author: Antonello Andrea - * Martin Desruisseaux - * - ****************************************************************************** - * Copyright (c) 2005, Andrea Antonello - * Copyright (c) 2011, Martin Desruisseaux - * Copyright (c) 2018, Even Rouault - * - * 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. - *****************************************************************************/ - -/*! - * \file jniproj.c - * - * \brief - * Functions used by the Java Native Interface (JNI) wrappers of PROJ. - * - * - * \author Antonello Andrea - * \date Wed Oct 20 23:10:24 CEST 2004 - * - * \author Martin Desruisseaux - * \date August 2011 - */ - -#include "proj_config.h" - -#ifdef JNI_ENABLED - -#include <math.h> -#include <string.h> -#include "proj_internal.h" -#include "org_proj4_PJ.h" -#include <jni.h> - -#define PJ_FIELD_NAME "ptr" -#define PJ_FIELD_TYPE "J" - -/*! - * \brief - * Internal method returning the address of the PJ structure wrapped by the given Java object. - * This function looks for a field named "ptr" and of type "long" (Java signature "J") in the - * given object. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be nullptr). - * \return The address of the PJ structure, or nullptr if the operation fails (for example - * because the "ptr" field was not found). - */ -static PJ *getPJ(JNIEnv *env, jobject object) -{ - jfieldID id = env->GetFieldID(env->GetObjectClass(object), PJ_FIELD_NAME, PJ_FIELD_TYPE); - return (id) ? (PJ*) env->GetLongField(object, id) : nullptr; -} - -/*! - * \brief - * Internal method returning the java.lang.Double.NaN constant value. - * Efficiency is no a high concern for this particular method, because it - * is used mostly when the user wrongly attempt to use a disposed PJ object. - * - * \param env - The JNI environment. - * \return The java.lang.Double.NaN constant value. - */ -static jdouble javaNaN(JNIEnv *env) -{ - jclass c = env->FindClass("java/lang/Double"); - if (c) { // Should never be nullptr, but let be paranoiac. - jfieldID id = env->GetStaticFieldID(c, "NaN", "D"); - if (id) { // Should never be nullptr, but let be paranoiac. - return env->GetStaticDoubleField(c, id); - } - } - return 0.0; // Should never happen. -} - -/*! - * \brief - * Returns the Proj4 release number. - * - * \param env - The JNI environment. - * \return The Proj4 release number, or nullptr. - */ -JNIEXPORT jstring JNICALL Java_org_proj4_PJ_getVersion - (JNIEnv *env, jclass) -{ - const char *desc = pj_get_release(); - return (desc) ? env->NewStringUTF(desc) : nullptr; -} - -/*! - * \brief - * Allocates a new PJ structure from a definition string. - * - * \param env - The JNI environment. - * \param definition - The string definition to be given to Proj4. - * \return The address of the new PJ structure, or 0 in case of failure. - */ -JNIEXPORT jlong JNICALL Java_org_proj4_PJ_allocatePJ - (JNIEnv *env, jclass, jstring definition) -{ - const char *def_utf = env->GetStringUTFChars(definition, nullptr); - if (!def_utf) return 0; /* OutOfMemoryError already thrown. */ - PJ *pj = pj_init_plus(def_utf); - env->ReleaseStringUTFChars(definition, def_utf); - return (jlong) pj; -} - -/*! - * \brief - * Allocates a new geographic PJ structure from an existing one. - * - * \param env - The JNI environment. - * \param projected - The PJ object from which to derive a new one. - * \return The address of the new PJ structure, or 0 in case of failure. - */ -JNIEXPORT jlong JNICALL Java_org_proj4_PJ_allocateGeoPJ - (JNIEnv *env, jclass, jobject projected) -{ - PJ *pj = getPJ(env, projected); - return (pj) ? (jlong) pj_latlong_from_proj(pj) : 0; -} - -/*! - * \brief - * Returns the definition string. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be nullptr). - * \return The definition string. - */ -JNIEXPORT jstring JNICALL Java_org_proj4_PJ_getDefinition - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - if (pj) { - char *desc = pj_get_def(pj, 0); - if (desc) { - jstring str = env->NewStringUTF(desc); - pj_dalloc(desc); - return str; - } - } - return nullptr; -} - -/*! - * \brief - * Returns the description associated to the PJ structure. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be nullptr). - * \return The description associated to the PJ structure. - */ -JNIEXPORT jstring JNICALL Java_org_proj4_PJ_toString - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - if (pj) { - const char *desc = pj->descr; - if (desc) { - return env->NewStringUTF(desc); - } - } - return nullptr; -} - -/*! - * \brief - * Returns the CRS type as one of the PJ.Type enum: GEOGRAPHIC, GEOCENTRIC or PROJECTED. - * This function should never return nullptr, unless class or fields have been renamed in - * such a way that we can not find anymore the expected enum values. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be nullptr). - * \return The CRS type as one of the PJ.Type enum. - */ -JNIEXPORT jobject JNICALL Java_org_proj4_PJ_getType - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - if (pj) { - const char *type; - if (pj_is_latlong(pj)) { - type = "GEOGRAPHIC"; - } else if (pj_is_geocent(pj)) { - type = "GEOCENTRIC"; - } else { - type = "PROJECTED"; - } - jclass c = env->FindClass("org/proj4/PJ$Type"); - if (c) { - jfieldID id = env->GetStaticFieldID(c, type, "Lorg/proj4/PJ$Type;"); - if (id) { - return env->GetStaticObjectField(c, id); - } - } - } - return nullptr; -} - -/*! - * \brief - * Returns the semi-major axis length. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be nullptr). - * \return The semi-major axis length. - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getSemiMajorAxis - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - return pj ? pj->a_orig : javaNaN(env); -} - -/*! - * \brief - * Computes the semi-minor axis length from the semi-major axis length and the eccentricity - * squared. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be nullptr). - * \return The semi-minor axis length. - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getSemiMinorAxis - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - if (!pj) return javaNaN(env); - double a = pj->a_orig; - return sqrt(a*a * (1.0 - pj->es_orig)); -} - -/*! - * \brief - * Returns the eccentricity squared. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be nullptr). - * \return The eccentricity. - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getEccentricitySquared - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - return pj ? pj->es_orig : javaNaN(env); -} - -/*! - * \brief - * Returns an array of character indicating the direction of each axis. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be nullptr). - * \return The axis directions. - */ -JNIEXPORT jcharArray JNICALL Java_org_proj4_PJ_getAxisDirections - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - if (pj) { - int length = static_cast<int>(strlen(pj->axis)); - jcharArray array = env->NewCharArray(length); - if (array) { - jchar* axis = env->GetCharArrayElements(array, nullptr); - if (axis) { - /* Don't use memcp because the type may not be the same. */ - int i; - for (i=0; i<length; i++) { - axis[i] = pj->axis[i]; - } - env->ReleaseCharArrayElements(array, axis, 0); - } - return array; - } - } - return nullptr; -} - -/*! - * \brief - * Longitude of the prime meridian measured from the Greenwich meridian, positive eastward. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be nullptr). - * \return The prime meridian longitude, in degrees. - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getGreenwichLongitude - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - return (pj) ? (pj->from_greenwich)*(180/M_PI) : javaNaN(env); -} - -/*! - * \brief - * Returns the conversion factor from linear units to metres. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be nullptr). - * \param vertical - JNI_FALSE for horizontal axes, or JNI_TRUE for the vertical axis. - * \return The conversion factor to metres. - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getLinearUnitToMetre - (JNIEnv *env, jobject object, jboolean vertical) -{ - PJ *pj = getPJ(env, object); - if (pj) { - return (vertical) ? pj->vto_meter : pj->to_meter; - } - return javaNaN(env); -} - -/*! - * \brief - * Converts input values from degrees to radians before coordinate operation, or the output - * values from radians to degrees after the coordinate operation. - * - * \param pj - The PROJ.4 PJ structure. - * \param data - The coordinate array to transform. - * \param numPts - Number of points to transform. - * \param dimension - Dimension of points in the coordinate array. - * \param factor - The scale factor to apply: M_PI/180 for inputs or 180/M_PI for outputs. - */ -static void convertAngularOrdinates(PJ *pj, double* data, jint numPts, int dimension, double factor) { - int dimToSkip; - if (pj_is_latlong(pj)) { - /* Convert only the 2 first ordinates and skip all the other dimensions. */ - dimToSkip = dimension - 2; - } else { - /* Not a geographic CRS: nothing to convert. */ - return; - } - double *stop = data + dimension*numPts; - if (dimToSkip > 0) { - while (data != stop) { - (*data++) *= factor; - (*data++) *= factor; - data += dimToSkip; - } - } else { - while (data != stop) { - (*data++) *= factor; - } - } -} - -/*! - * \brief - * Transforms in-place the coordinates in the given array. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be nullptr). - * \param target - The target CRS. - * \param dimension - The dimension of each coordinate value. Must be equals or greater than 2. - * \param coordinates - The coordinates to transform, as a sequence of (x,y,<z>,...) tuples. - * \param offset - Offset of the first coordinate in the given array. - * \param numPts - Number of points to transform. - */ -JNIEXPORT void JNICALL Java_org_proj4_PJ_transform - (JNIEnv *env, jobject object, jobject target, jint dimension, jdoubleArray coordinates, jint offset, jint numPts) -{ - if (!target || !coordinates) { - jclass c = env->FindClass("java/lang/NullPointerException"); - if (c) env->ThrowNew(c, "The target CRS and the coordinates array can not be null."); - return; - } - if (dimension < 2 || dimension > org_proj4_PJ_DIMENSION_MAX) { /* Arbitrary upper value for catching potential misuse. */ - jclass c = env->FindClass("java/lang/IllegalArgumentException"); - if (c) env->ThrowNew(c, "Illegal number of dimensions."); - return; - } - if ((offset < 0) || (numPts < 0) || (offset + dimension*numPts) > env->GetArrayLength(coordinates)) { - jclass c = env->FindClass("java/lang/ArrayIndexOutOfBoundsException"); - if (c) env->ThrowNew(c, "Illegal offset or illegal number of points."); - return; - } - PJ *src_pj = getPJ(env, object); - PJ *dst_pj = getPJ(env, target); - if (src_pj && dst_pj) { - /* Using GetPrimitiveArrayCritical/ReleasePrimitiveArrayCritical rather than - GetDoubleArrayElements/ReleaseDoubleArrayElements increase the chances that - the JVM returns direct reference to its internal array without copying data. - However we must promise to run the "critical" code fast, to not make any - system call that may wait for the JVM and to not invoke any other JNI method. */ - double *data = static_cast<double*>(env->GetPrimitiveArrayCritical(coordinates, nullptr)); - if (data) { - double *x = data + offset; - double *y = x + 1; - double *z = (dimension >= 3) ? y+1 : nullptr; - convertAngularOrdinates(src_pj, x, numPts, dimension, M_PI/180); - int err = pj_transform(src_pj, dst_pj, numPts, dimension, x, y, z); - convertAngularOrdinates(dst_pj, x, numPts, dimension, 180/M_PI); - env->ReleasePrimitiveArrayCritical(coordinates, data, 0); - if (err) { - jclass c = env->FindClass("org/proj4/PJException"); - if (c) env->ThrowNew(c, pj_strerrno(err)); - } - } - } -} - -/*! - * \brief - * Returns a description of the last error that occurred, or nullptr if none. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be nullptr). - * \return The last error, or nullptr. - */ -JNIEXPORT jstring JNICALL Java_org_proj4_PJ_getLastError - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - if (pj) { - int err = pj_ctx_get_errno(pj->ctx); - if (err) { - return env->NewStringUTF(pj_strerrno(err)); - } - } - return nullptr; -} - -/*! - * \brief - * Deallocate the PJ structure. This method is invoked by the garbage collector exactly once. - * This method will also set the Java "ptr" final field to 0 as a safety. In theory we are not - * supposed to change the value of a final field. But no Java code should use this field, and - * the PJ object is being garbage collected anyway. We set the field to 0 as a safety in case - * some user invoked the finalize() method explicitly despite our warning in the Javadoc to - * never do such thing. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be nullptr). - */ -JNIEXPORT void JNICALL Java_org_proj4_PJ_finalize - (JNIEnv *env, jobject object) -{ - jfieldID id = env->GetFieldID(env->GetObjectClass(object), PJ_FIELD_NAME, PJ_FIELD_TYPE); - if (id) { - PJ *pj = (PJ*) env->GetLongField(object, id); - if (pj) { - env->SetLongField(object, id, (jlong) 0); - pj_free(pj); - } - } -} - -#endif diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake index d5a4d63e..704ece3d 100644 --- a/src/lib_proj.cmake +++ b/src/lib_proj.cmake @@ -240,7 +240,6 @@ set(SRC_LIBPROJ_CORE initcache.cpp internal.cpp inv.cpp - jniproj.cpp list.cpp log.cpp malloc.cpp @@ -319,26 +318,6 @@ source_group("CMake Files" FILES CMakeLists.txt) add_definitions(-DPROJ_LIB="${CMAKE_INSTALL_PREFIX}/${DATADIR}") ################################################# -## java wrapping with jni -################################################# -option(JNI_SUPPORT "Build support of java/jni wrapping for proj library" OFF) -find_package(JNI QUIET) -if(JNI_SUPPORT AND NOT JNI_FOUND) - message(FATAL_ERROR "jni support is required but jni is not found") -endif() -boost_report_value(JNI_SUPPORT) -if(JNI_SUPPORT) - set(SRC_LIBPROJ_CORE - ${SRC_LIBPROJ_CORE} jniproj.cpp) - set(HEADERS_LIBPROJ - ${HEADERS_LIBPROJ} org_proj4_PJ.h) - source_group("Source Files\\JNI" FILES ${SRC_LIBPROJ_JNI}) - add_definitions(-DJNI_ENABLED) - include_directories(${JNI_INCLUDE_DIRS}) - boost_report_value(JNI_INCLUDE_DIRS) -endif() - -################################################# ## targets: libproj and proj_config.h ################################################# set(ALL_LIBPROJ_SOURCES @@ -455,7 +434,7 @@ if(CURL_FOUND) target_link_libraries(${PROJ_CORE_TARGET} ${CURL_LIBRARY}) endif() -if(MSVC) +if(MSVC AND BUILD_LIBPROJ_SHARED) target_compile_definitions(${PROJ_CORE_TARGET} PRIVATE PROJ_MSVC_DLL_EXPORT=1) endif() diff --git a/src/networkfilemanager.cpp b/src/networkfilemanager.cpp index 5d15c5af..64969d0f 100644 --- a/src/networkfilemanager.cpp +++ b/src/networkfilemanager.cpp @@ -1316,6 +1316,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()); + pj_ctx_set_errno(ctx, PJD_ERR_NETWORK_ERROR); } bool ok = false; @@ -1403,6 +1404,7 @@ size_t NetworkFile::read(void *buffer, size_t sizeBytes) { &nRead, errorBuffer.size(), &errorBuffer[0], m_ctx->networking.user_data); if (!m_handle) { + pj_ctx_set_errno(m_ctx, PJD_ERR_NETWORK_ERROR); return 0; } } else { @@ -1418,6 +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()); } + pj_ctx_set_errno(m_ctx, PJD_ERR_NETWORK_ERROR); return 0; } diff --git a/src/org_proj4_PJ.h b/src/org_proj4_PJ.h deleted file mode 100644 index be5d3f58..00000000 --- a/src/org_proj4_PJ.h +++ /dev/null @@ -1,135 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include <jni.h> -/* Header for class org_proj4_PJ */ - -#ifndef _Included_org_proj4_PJ -#define _Included_org_proj4_PJ -#ifdef __cplusplus -extern "C" { -#endif -#undef org_proj4_PJ_DIMENSION_MAX -#define org_proj4_PJ_DIMENSION_MAX 100L -/* - * Class: org_proj4_PJ - * Method: allocatePJ - * Signature: (Ljava/lang/String;)J - */ -JNIEXPORT jlong JNICALL Java_org_proj4_PJ_allocatePJ - (JNIEnv *, jclass, jstring); - -/* - * Class: org_proj4_PJ - * Method: allocateGeoPJ - * Signature: (Lorg/proj4/PJ;)J - */ -JNIEXPORT jlong JNICALL Java_org_proj4_PJ_allocateGeoPJ - (JNIEnv *, jclass, jobject); - -/* - * Class: org_proj4_PJ - * Method: getVersion - * Signature: ()Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_org_proj4_PJ_getVersion - (JNIEnv *, jclass); - -/* - * Class: org_proj4_PJ - * Method: getDefinition - * Signature: ()Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_org_proj4_PJ_getDefinition - (JNIEnv *, jobject); - -/* - * Class: org_proj4_PJ - * Method: getType - * Signature: ()Lorg/proj4/PJ/Type; - */ -JNIEXPORT jobject JNICALL Java_org_proj4_PJ_getType - (JNIEnv *, jobject); - -/* - * Class: org_proj4_PJ - * Method: getSemiMajorAxis - * Signature: ()D - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getSemiMajorAxis - (JNIEnv *, jobject); - -/* - * Class: org_proj4_PJ - * Method: getSemiMinorAxis - * Signature: ()D - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getSemiMinorAxis - (JNIEnv *, jobject); - -/* - * Class: org_proj4_PJ - * Method: getEccentricitySquared - * Signature: ()D - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getEccentricitySquared - (JNIEnv *, jobject); - -/* - * Class: org_proj4_PJ - * Method: getAxisDirections - * Signature: ()[C - */ -JNIEXPORT jcharArray JNICALL Java_org_proj4_PJ_getAxisDirections - (JNIEnv *, jobject); - -/* - * Class: org_proj4_PJ - * Method: getGreenwichLongitude - * Signature: ()D - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getGreenwichLongitude - (JNIEnv *, jobject); - -/* - * Class: org_proj4_PJ - * Method: getLinearUnitToMetre - * Signature: (Z)D - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getLinearUnitToMetre - (JNIEnv *, jobject, jboolean); - -/* - * Class: org_proj4_PJ - * Method: transform - * Signature: (Lorg/proj4/PJ;I[DII)V - */ -JNIEXPORT void JNICALL Java_org_proj4_PJ_transform - (JNIEnv *, jobject, jobject, jint, jdoubleArray, jint, jint); - -/* - * Class: org_proj4_PJ - * Method: getLastError - * Signature: ()Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_org_proj4_PJ_getLastError - (JNIEnv *, jobject); - -/* - * Class: org_proj4_PJ - * Method: toString - * Signature: ()Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_org_proj4_PJ_toString - (JNIEnv *, jobject); - -/* - * Class: org_proj4_PJ - * Method: finalize - * Signature: ()V - */ -JNIEXPORT void JNICALL Java_org_proj4_PJ_finalize - (JNIEnv *, jobject); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/pipeline.cpp b/src/pipeline.cpp index f65dbfa0..511a69fe 100644 --- a/src/pipeline.cpp +++ b/src/pipeline.cpp @@ -170,6 +170,9 @@ static PJ_COORD pipeline_forward_4d (PJ_COORD point, PJ *P) { if( !step.omit_fwd ) { point = proj_trans (step.pj, PJ_FWD, point); + if( point.xyzt.x == HUGE_VAL ) { + break; + } } } @@ -186,6 +189,9 @@ static PJ_COORD pipeline_reverse_4d (PJ_COORD point, PJ *P) { if( !step.omit_inv ) { point = proj_trans (step.pj, PJ_INV, point); + if( point.xyzt.x == HUGE_VAL ) { + break; + } } } @@ -204,6 +210,9 @@ static PJ_XYZ pipeline_forward_3d (PJ_LPZ lpz, PJ *P) { if( !step.omit_fwd ) { point = pj_approx_3D_trans (step.pj, PJ_FWD, point); + if( point.xyzt.x == HUGE_VAL ) { + break; + } } } @@ -222,6 +231,9 @@ static PJ_LPZ pipeline_reverse_3d (PJ_XYZ xyz, PJ *P) { if( !step.omit_inv ) { point = proj_trans (step.pj, PJ_INV, point); + if( point.xyzt.x == HUGE_VAL ) { + break; + } } } @@ -240,6 +252,9 @@ static PJ_XY pipeline_forward (PJ_LP lp, PJ *P) { if( !step.omit_fwd ) { point = pj_approx_2D_trans (step.pj, PJ_FWD, point); + if( point.xyzt.x == HUGE_VAL ) { + break; + } } } @@ -258,6 +273,9 @@ static PJ_LP pipeline_reverse (PJ_XY xy, PJ *P) { if( !step.omit_inv ) { point = pj_approx_2D_trans (step.pj, PJ_INV, point); + if( point.xyzt.x == HUGE_VAL ) { + break; + } } } @@ -152,8 +152,8 @@ extern "C" { #endif /* The version numbers should be updated with every release! **/ -#define PROJ_VERSION_MAJOR 6 -#define PROJ_VERSION_MINOR 3 +#define PROJ_VERSION_MAJOR 7 +#define PROJ_VERSION_MINOR 0 #define PROJ_VERSION_PATCH 0 extern char const PROJ_DLL pj_release[]; /* global release id string */ @@ -1002,6 +1002,10 @@ PJ_OBJ_LIST PROJ_DLL *proj_get_non_deprecated(PJ_CONTEXT *ctx, int PROJ_DLL proj_is_equivalent_to(const PJ *obj, const PJ *other, PJ_COMPARISON_CRITERION criterion); +int PROJ_DLL proj_is_equivalent_to_with_ctx(PJ_CONTEXT *ctx, + const PJ *obj, const PJ *other, + PJ_COMPARISON_CRITERION criterion); + int PROJ_DLL proj_is_crs(const PJ *obj); const char PROJ_DLL* proj_get_name(const PJ *obj); @@ -1257,6 +1261,9 @@ int PROJ_DLL proj_coordoperation_get_towgs84_values(PJ_CONTEXT *ctx, int value_count, int emit_error_if_incompatible); +PJ PROJ_DLL *proj_coordoperation_create_inverse(PJ_CONTEXT *ctx, const PJ *obj); + + int PROJ_DLL proj_concatoperation_get_step_count(PJ_CONTEXT *ctx, const PJ *concatoperation); diff --git a/src/proj_api.h b/src/proj_api.h index 852cb14c..24046026 100644 --- a/src/proj_api.h +++ b/src/proj_api.h @@ -38,7 +38,7 @@ #endif #ifndef PJ_VERSION -#define PJ_VERSION 630 +#define PJ_VERSION 700 #endif #ifdef PROJ_RENAME_SYMBOLS diff --git a/src/proj_constants.h b/src/proj_constants.h index 5c642862..21cd87fb 100644 --- a/src/proj_constants.h +++ b/src/proj_constants.h @@ -513,6 +513,9 @@ #define EPSG_CODE_METHOD_VERTCON 9658 #define EPSG_NAME_METHOD_VERTCON "VERTCON" +#define EPSG_CODE_METHOD_VERTICALGRID_NZLVD 1071 +#define EPSG_NAME_METHOD_VERTICALGRID_NZLVD "Vertical Offset by Grid Interpolation (NZLVD)" + #define EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE "Vertical offset file" #define EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE 8732 diff --git a/src/proj_internal.h b/src/proj_internal.h index 15b98859..bb0e3453 100644 --- a/src/proj_internal.h +++ b/src/proj_internal.h @@ -658,6 +658,7 @@ struct FACTORS { #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 */ diff --git a/src/release.cpp b/src/release.cpp index 425bdb9a..648c5398 100644 --- a/src/release.cpp +++ b/src/release.cpp @@ -11,7 +11,7 @@ char const pj_release[] = STR(PROJ_VERSION_MAJOR)"." STR(PROJ_VERSION_MINOR)"." STR(PROJ_VERSION_PATCH)", " - "January 1st, 2020"; + "March 1st, 2020"; const char *pj_get_release() { return pj_release; diff --git a/src/strerrno.cpp b/src/strerrno.cpp index 9bf5f45a..5ae0d7e1 100644 --- a/src/strerrno.cpp +++ b/src/strerrno.cpp @@ -71,6 +71,7 @@ pj_err_list[] = { "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 projects.h, and transient_error array in pj_transform */ diff --git a/src/transform.cpp b/src/transform.cpp index 811e2a6a..ea3d9ae2 100644 --- a/src/transform.cpp +++ b/src/transform.cpp @@ -234,6 +234,14 @@ static int geographic_to_projected (PJ *P, long n, int dist, double *x, double * return 0; } + // Ugly hack. See https://github.com/OSGeo/PROJ/issues/1782 + if( P->right == PJ_IO_UNITS_WHATEVER && P->descr && + strncmp(P->descr, "General Oblique Transformation", + strlen("General Oblique Transformation")) == 0 ) + { + P->right = PJ_IO_UNITS_PROJECTED; + } + for( i = 0; i <n; i++ ) { PJ_XY projected_loc; @@ -345,6 +353,14 @@ static int projected_to_geographic (PJ *P, long n, int dist, double *x, double * return 0; } + // Ugly hack. See https://github.com/OSGeo/PROJ/issues/1782 + if( P->right == PJ_IO_UNITS_WHATEVER && P->descr && + strncmp(P->descr, "General Oblique Transformation", + strlen("General Oblique Transformation")) == 0 ) + { + P->right = PJ_IO_UNITS_PROJECTED; + } + /* Fallback to the original PROJ.4 API 2d inversion - inv */ for( i = 0; i < n; i++ ) { PJ_XY projected_loc; diff --git a/src/transformations/helmert.cpp b/src/transformations/helmert.cpp index 7a3e64d0..2045dea2 100644 --- a/src/transformations/helmert.cpp +++ b/src/transformations/helmert.cpp @@ -665,18 +665,18 @@ PJ *TRANSFORMATION(helmert, 0) { } /* Let's help with debugging */ - if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { - proj_log_debug(P, "Helmert parameters:"); - proj_log_debug(P, "x= %8.5f y= %8.5f z= %8.5f", Q->xyz.x, Q->xyz.y, Q->xyz.z); - proj_log_debug(P, "rx= %8.5f ry= %8.5f rz= %8.5f", + if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_TRACE) { + proj_log_trace(P, "Helmert parameters:"); + proj_log_trace(P, "x= %8.5f y= %8.5f z= %8.5f", Q->xyz.x, Q->xyz.y, Q->xyz.z); + proj_log_trace(P, "rx= %8.5f ry= %8.5f rz= %8.5f", Q->opk.o / ARCSEC_TO_RAD, Q->opk.p / ARCSEC_TO_RAD, Q->opk.k / ARCSEC_TO_RAD); - proj_log_debug(P, "s= %8.5f exact=%d%s", Q->scale, Q->exact, + proj_log_trace(P, "s= %8.5f exact=%d%s", Q->scale, Q->exact, Q->no_rotation ? "" : Q->is_position_vector ? " convention=position_vector" : " convention=coordinate_frame"); - proj_log_debug(P, "dx= %8.5f dy= %8.5f dz= %8.5f", Q->dxyz.x, Q->dxyz.y, Q->dxyz.z); - proj_log_debug(P, "drx=%8.5f dry=%8.5f drz=%8.5f", Q->dopk.o, Q->dopk.p, Q->dopk.k); - proj_log_debug(P, "ds= %8.5f t_epoch=%8.5f", Q->dscale, Q->t_epoch); + proj_log_trace(P, "dx= %8.5f dy= %8.5f dz= %8.5f", Q->dxyz.x, Q->dxyz.y, Q->dxyz.z); + proj_log_trace(P, "drx=%8.5f dry=%8.5f drz=%8.5f", Q->dopk.o, Q->dopk.p, Q->dopk.k); + proj_log_trace(P, "ds= %8.5f t_epoch=%8.5f", Q->dscale, Q->t_epoch); } if (Q->no_rotation) { @@ -729,15 +729,15 @@ PJ *TRANSFORMATION(molobadekas, 0) { /* Let's help with debugging */ - if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { - proj_log_debug(P, "Molodensky-Badekas parameters:"); - proj_log_debug(P, "x= %8.5f y= %8.5f z= %8.5f", Q->xyz_0.x, Q->xyz_0.y, Q->xyz_0.z); - proj_log_debug(P, "rx= %8.5f ry= %8.5f rz= %8.5f", + if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_TRACE) { + proj_log_trace(P, "Molodensky-Badekas parameters:"); + proj_log_trace(P, "x= %8.5f y= %8.5f z= %8.5f", Q->xyz_0.x, Q->xyz_0.y, Q->xyz_0.z); + proj_log_trace(P, "rx= %8.5f ry= %8.5f rz= %8.5f", Q->opk.o / ARCSEC_TO_RAD, Q->opk.p / ARCSEC_TO_RAD, Q->opk.k / ARCSEC_TO_RAD); - proj_log_debug(P, "s= %8.5f exact=%d%s", Q->scale, Q->exact, + proj_log_trace(P, "s= %8.5f exact=%d%s", Q->scale, Q->exact, Q->is_position_vector ? " convention=position_vector" : " convention=coordinate_frame"); - proj_log_debug(P, "px= %8.5f py= %8.5f pz= %8.5f", Q->refp.x, Q->refp.y, Q->refp.z); + proj_log_trace(P, "px= %8.5f py= %8.5f pz= %8.5f", Q->refp.x, Q->refp.y, Q->refp.z); } /* as an optimization, we incorporate the refp in the translation terms */ |
