diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/4D_api.cpp | 309 | ||||
| -rw-r--r-- | src/apps/cs2cs.cpp | 44 | ||||
| -rw-r--r-- | src/apps/geod.cpp | 17 | ||||
| -rw-r--r-- | src/apps/geod_set.cpp | 24 | ||||
| -rw-r--r-- | src/apps/proj.cpp | 17 | ||||
| -rw-r--r-- | src/conversions/unitconvert.cpp | 6 | ||||
| -rw-r--r-- | src/grids.cpp | 13 | ||||
| -rw-r--r-- | src/init.cpp | 2 | ||||
| -rw-r--r-- | src/iso19111/c_api.cpp | 243 | ||||
| -rw-r--r-- | src/iso19111/common.cpp | 5 | ||||
| -rw-r--r-- | src/iso19111/coordinateoperation.cpp | 2 | ||||
| -rw-r--r-- | src/iso19111/crs.cpp | 43 | ||||
| -rw-r--r-- | src/iso19111/factory.cpp | 58 | ||||
| -rw-r--r-- | src/iso19111/io.cpp | 39 | ||||
| -rw-r--r-- | src/lib_proj.cmake | 2 | ||||
| -rw-r--r-- | src/proj.h | 69 | ||||
| -rw-r--r-- | src/proj_internal.h | 109 | ||||
| -rw-r--r-- | src/projections/stere.cpp | 5 | ||||
| -rw-r--r-- | src/projections/tmerc.cpp | 184 | ||||
| -rw-r--r-- | src/units.cpp | 12 |
20 files changed, 852 insertions, 351 deletions
diff --git a/src/4D_api.cpp b/src/4D_api.cpp index 52bd7f4f..6e494793 100644 --- a/src/4D_api.cpp +++ b/src/4D_api.cpp @@ -179,7 +179,58 @@ double proj_roundtrip (PJ *P, PJ_DIRECTION direction, int n, PJ_COORD *coord) { return proj_xyz_dist (org, t); } +/**************************************************************************************/ +int pj_get_suggested_operation(PJ_CONTEXT*, + const std::vector<CoordOperation>& opList, + const int iExcluded[2], + PJ_DIRECTION direction, + PJ_COORD coord) +/**************************************************************************************/ +{ + // Select the operations that match the area of use + // and has the best accuracy. + int iBest = -1; + double bestAccuracy = std::numeric_limits<double>::max(); + const int nOperations = static_cast<int>(opList.size()); + for( int i = 0; i < nOperations; i++ ) { + if( i == iExcluded[0] || i == iExcluded[1] ) { + continue; + } + const auto &alt = opList[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; + } + } + } + + return iBest; +} /**************************************************************************************/ PJ_COORD proj_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coord) { @@ -211,45 +262,11 @@ similarly, but prefers the 2D resp. 3D interfaces if available. { // 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; - } - 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; - } - } - } - + int iBest = pj_get_suggested_operation(P->ctx, + P->alternativeCoordinateOperations, + iExcluded, + direction, + coord); if( iBest < 0 ) { break; } @@ -968,13 +985,15 @@ static void reproject_bbox(PJ* pjGeogToCrs, /*****************************************************************************/ -static PJ* add_coord_op_to_list(PJ* op, +static PJ* add_coord_op_to_list( + int idxInOriginalList, + PJ* op, double west_lon, double south_lat, double east_lon, double north_lat, PJ* pjGeogToSrc, PJ* pjGeogToDst, bool isOffshore, - std::vector<PJconsts::CoordOperation>& altCoordOps) { + std::vector<CoordOperation>& altCoordOps) { /*****************************************************************************/ double minxSrc; @@ -997,9 +1016,10 @@ static PJ* add_coord_op_to_list(PJ* op, std::string name(c_name ? c_name : ""); const double accuracy = proj_coordoperation_get_accuracy(op->ctx, op); - altCoordOps.emplace_back(minxSrc, minySrc, maxxSrc, maxySrc, - minxDst, minyDst, maxxDst, maxyDst, - op, name, accuracy, isOffshore); + altCoordOps.emplace_back(idxInOriginalList, + minxSrc, minySrc, maxxSrc, maxySrc, + minxDst, minyDst, maxxDst, maxyDst, + op, name, accuracy, isOffshore); op = nullptr; } return op; @@ -1140,6 +1160,91 @@ PJ *proj_create_crs_to_crs (PJ_CONTEXT *ctx, const char *source_crs, const char return ret; } + +/*****************************************************************************/ +std::vector<CoordOperation> pj_create_prepared_operations(PJ_CONTEXT *ctx, + const PJ *source_crs, + const PJ *target_crs, + PJ_OBJ_LIST* op_list) +/*****************************************************************************/ +{ + auto pjGeogToSrc = create_operation_to_geog_crs(ctx, source_crs); + if( !pjGeogToSrc ) + { + proj_context_log_debug(ctx, + "Cannot create transformation from geographic CRS of source CRS to source CRS"); + return {}; + } + + auto pjGeogToDst = create_operation_to_geog_crs(ctx, target_crs); + if( !pjGeogToDst ) + { + proj_context_log_debug(ctx, + "Cannot create transformation from geographic CRS of target CRS to target CRS"); + proj_destroy(pjGeogToSrc); + return {}; + } + + try + { + std::vector<CoordOperation> preparedOpList; + + // Iterate over source->target candidate transformations and reproject + // their long-lat bounding box into the source CRS. + const auto op_count = proj_list_get_count(op_list); + for( int i = 0; i < op_count; i++ ) + { + auto op = proj_list_get(ctx, op_list, i); + assert(op); + double west_lon = 0.0; + double south_lat = 0.0; + double east_lon = 0.0; + double north_lat = 0.0; + + const char* areaName = nullptr; + if( proj_get_area_of_use(ctx, op, + &west_lon, &south_lat, &east_lon, &north_lat, &areaName) ) + { + const bool isOffshore = + areaName && strstr(areaName, "offshore"); + if( west_lon <= east_lon ) + { + op = add_coord_op_to_list(i, op, + west_lon, south_lat, east_lon, north_lat, + pjGeogToSrc, pjGeogToDst, isOffshore, + preparedOpList); + } + else + { + auto op_clone = proj_clone(ctx, op); + + op = add_coord_op_to_list(i, op, + west_lon, south_lat, 180, north_lat, + pjGeogToSrc, pjGeogToDst, isOffshore, + preparedOpList); + op_clone = add_coord_op_to_list(i, op_clone, + -180, south_lat, east_lon, north_lat, + pjGeogToSrc, pjGeogToDst, isOffshore, + preparedOpList); + proj_destroy(op_clone); + } + } + + proj_destroy(op); + } + + proj_destroy(pjGeogToSrc); + proj_destroy(pjGeogToDst); + return preparedOpList; + } + catch( const std::exception& ) + { + proj_destroy(pjGeogToSrc); + proj_destroy(pjGeogToDst); + return {}; + } +} + /*****************************************************************************/ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, const PJ *target_crs, PJ_AREA *area, const char* const *) { /****************************************************************************** @@ -1177,16 +1282,15 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); auto op_list = proj_create_operations(ctx, source_crs, target_crs, operation_ctx); + proj_operation_factory_context_destroy(operation_ctx); if( !op_list ) { - proj_operation_factory_context_destroy(operation_ctx); return nullptr; } auto op_count = proj_list_get_count(op_list); if( op_count == 0 ) { proj_list_destroy(op_list); - proj_operation_factory_context_destroy(operation_ctx); proj_context_log_debug(ctx, "No operation found matching criteria"); return nullptr; @@ -1199,112 +1303,39 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons proj_get_type(source_crs) == PJ_TYPE_GEOCENTRIC_CRS || proj_get_type(target_crs) == PJ_TYPE_GEOCENTRIC_CRS ) { proj_list_destroy(op_list); - proj_operation_factory_context_destroy(operation_ctx); return P; } - auto pjGeogToSrc = create_operation_to_geog_crs(ctx, source_crs); - if( !pjGeogToSrc ) + auto preparedOpList = pj_create_prepared_operations(ctx, source_crs, target_crs, + op_list); + proj_list_destroy(op_list); + + if( preparedOpList.empty() ) { - proj_list_destroy(op_list); - proj_operation_factory_context_destroy(operation_ctx); - proj_context_log_debug(ctx, - "Cannot create transformation from geographic CRS of source CRS to source CRS"); proj_destroy(P); return nullptr; } - auto pjGeogToDst = create_operation_to_geog_crs(ctx, target_crs); - if( !pjGeogToDst ) + // If there's finally juste a single result, return it directly + if( preparedOpList.size() == 1 ) { - proj_list_destroy(op_list); - proj_operation_factory_context_destroy(operation_ctx); - proj_context_log_debug(ctx, - "Cannot create transformation from geographic CRS of target CRS to target CRS"); + auto retP = preparedOpList[0].pj; + preparedOpList[0].pj = nullptr; proj_destroy(P); - proj_destroy(pjGeogToSrc); - return nullptr; + return retP; } - try - { - // Iterate over source->target candidate transformations and reproject - // their long-lat bounding box into the source CRS. - for( int i = 0; i < op_count; i++ ) - { - auto op = proj_list_get(ctx, op_list, i); - assert(op); - double west_lon = 0.0; - double south_lat = 0.0; - double east_lon = 0.0; - double north_lat = 0.0; + P->alternativeCoordinateOperations = std::move(preparedOpList); + // The returned P is rather dummy + P->iso_obj = nullptr; + P->fwd = nullptr; + P->inv = nullptr; + P->fwd3d = nullptr; + P->inv3d = nullptr; + P->fwd4d = nullptr; + P->inv4d = nullptr; - const char* areaName = nullptr; - if( proj_get_area_of_use(ctx, op, - &west_lon, &south_lat, &east_lon, &north_lat, &areaName) ) - { - const bool isOffshore = - areaName && strstr(areaName, "offshore"); - if( west_lon <= east_lon ) - { - op = add_coord_op_to_list(op, - west_lon, south_lat, east_lon, north_lat, - pjGeogToSrc, pjGeogToDst, isOffshore, - P->alternativeCoordinateOperations); - } - else - { - auto op_clone = proj_clone(ctx, op); - - op = add_coord_op_to_list(op, - west_lon, south_lat, 180, north_lat, - pjGeogToSrc, pjGeogToDst, isOffshore, - P->alternativeCoordinateOperations); - op_clone = add_coord_op_to_list(op_clone, - -180, south_lat, east_lon, north_lat, - pjGeogToSrc, pjGeogToDst, isOffshore, - P->alternativeCoordinateOperations); - proj_destroy(op_clone); - } - } - - proj_destroy(op); - } - - proj_list_destroy(op_list); - - proj_operation_factory_context_destroy(operation_ctx); - proj_destroy(pjGeogToSrc); - proj_destroy(pjGeogToDst); - - // If there's finally juste a single result, return it directly - if( P->alternativeCoordinateOperations.size() == 1 ) { - auto retP = P->alternativeCoordinateOperations[0].pj; - P->alternativeCoordinateOperations[0].pj = nullptr; - proj_destroy(P); - P = retP; - } else { - // The returned P is rather dummy - P->iso_obj = nullptr; - P->fwd = nullptr; - P->inv = nullptr; - P->fwd3d = nullptr; - P->inv3d = nullptr; - P->fwd4d = nullptr; - P->inv4d = nullptr; - } - - return P; - } - catch( const std::exception& ) - { - proj_list_destroy(op_list); - proj_operation_factory_context_destroy(operation_ctx); - proj_destroy(pjGeogToSrc); - proj_destroy(pjGeogToDst); - proj_destroy(P); - return nullptr; - } + return P; } PJ *proj_destroy (PJ *P) { diff --git a/src/apps/cs2cs.cpp b/src/apps/cs2cs.cpp index 6c85d4aa..c99e16e0 100644 --- a/src/apps/cs2cs.cpp +++ b/src/apps/cs2cs.cpp @@ -349,23 +349,14 @@ int main(int argc, char **argv) { exit(0); } - // First pass to check if we have "cs2cs [-bla]* <SRC> <DEST>" syntax - int countNonOptionArg = 0; + // First pass to check if we have "cs2cs [-bla]* <SRC> <DEST> [<filename>]" syntax + bool isProj4StyleSyntax = false; for (int i = 1; i < argc; i++) { - if (argv[i][0] == '-') { - if (argv[i][1] == 'f' || argv[i][1] == 'e' || argv[i][1] == 'd' || - argv[i][1] == 'D' ) { - i++; - } - } else { - if (strcmp(argv[i], "+to") == 0) { - countNonOptionArg = -1; - break; - } - countNonOptionArg++; + if (argv[i][0] == '+') { + isProj4StyleSyntax = true; + break; } } - const bool isSrcDestSyntax = (countNonOptionArg == 2); /* process run line arguments */ while (--argc > 0) { /* collect run line arguments */ @@ -425,11 +416,18 @@ int main(int argc, char **argv) { (void)printf("%9s %-16s %-16s %s\n", le->id, le->major, le->ell, le->name); } else if (arg[1] == 'u') { /* list units */ - const struct PJ_UNITS *lu; - - for (lu = proj_list_units(); lu->id; ++lu) - (void)printf("%12s %-20s %s\n", lu->id, - lu->to_meter, lu->name); + auto units = proj_get_units_from_database(nullptr, nullptr, "linear", false, nullptr); + for( int i = 0; units && units[i]; i++ ) + { + if( units[i]->proj_short_name ) + { + (void)printf("%12s %-20.15g %s\n", + units[i]->proj_short_name, + units[i]->conv_factor, + units[i]->name); + } + } + proj_unit_list_destroy(units); } else if (arg[1] == 'm') { /* list prime meridians */ const struct PJ_PRIME_MERIDIANS *lpm; @@ -485,11 +483,15 @@ int main(int argc, char **argv) { } break; } - } else if (isSrcDestSyntax) { + } else if (!isProj4StyleSyntax) { if (fromStr.empty()) fromStr = *argv; - else + else if( toStr.empty() ) toStr = *argv; + else { + /* assumed to be input file name(s) */ + eargv[eargc++] = *argv; + } } else if (strcmp(*argv, "+to") == 0) { have_to_flag = 1; diff --git a/src/apps/geod.cpp b/src/apps/geod.cpp index b46188d3..919430ca 100644 --- a/src/apps/geod.cpp +++ b/src/apps/geod.cpp @@ -185,11 +185,18 @@ noargument: emess(1,"missing argument for -%c",*arg); (void)printf("%9s %-16s %-16s %s\n", le->id, le->major, le->ell, le->name); } else if (arg[1] == 'u') { /* list of units */ - const struct PJ_UNITS *lu; - - for (lu = proj_list_units();lu->id ; ++lu) - (void)printf("%12s %-20s %s\n", - lu->id, lu->to_meter, lu->name); + auto units = proj_get_units_from_database(nullptr, nullptr, "linear", false, nullptr); + for( int i = 0; units && units[i]; i++ ) + { + if( units[i]->proj_short_name ) + { + (void)printf("%12s %-20.15g %s\n", + units[i]->proj_short_name, + units[i]->conv_factor, + units[i]->name); + } + } + proj_unit_list_destroy(units); } else emess(1,"invalid list option: l%c",arg[1]); exit( 0 ); diff --git a/src/apps/geod_set.cpp b/src/apps/geod_set.cpp index ed7edeb9..603f0d95 100644 --- a/src/apps/geod_set.cpp +++ b/src/apps/geod_set.cpp @@ -14,7 +14,6 @@ geod_set(int argc, char **argv) { paralist *start = nullptr, *curr; double es; char *name; - int i; /* put arguments into internal linked list */ if (argc <= 0) @@ -22,7 +21,7 @@ geod_set(int argc, char **argv) { start = curr = pj_mkparam(argv[0]); if (!curr) emess(1, "memory allocation failed"); - for (i = 1; curr != nullptr && i < argc; ++i) { + for (int i = 1; curr != nullptr && i < argc; ++i) { curr->next = pj_mkparam(argv[i]); if (!curr->next) emess(1, "memory allocation failed"); @@ -32,13 +31,20 @@ geod_set(int argc, char **argv) { if (pj_ell_set(pj_get_default_ctx(),start, &geod_a, &es)) emess(1,"ellipse setup failure"); /* set units */ if ((name = pj_param(nullptr,start, "sunits").s) != nullptr) { - const char *s; - const struct PJ_UNITS *unit_list = proj_list_units(); - for (i = 0; (s = unit_list[i].id) && strcmp(name, s) ; ++i) ; - if (!s) - emess(1,"%s unknown unit conversion id", name); - to_meter = unit_list[i].factor; - fr_meter = 1 / to_meter; + bool unit_found = false; + auto units = proj_get_units_from_database(nullptr, nullptr, "linear", false, nullptr); + for( int i = 0; units && units[i]; i++ ) + { + if( units[i]->proj_short_name && + strcmp(units[i]->proj_short_name, name) == 0 ) { + unit_found = true; + to_meter = units[i]->conv_factor; + fr_meter = 1 / to_meter; + } + } + proj_unit_list_destroy(units); + if( !unit_found ) + emess(1,"%s unknown unit conversion id", name); } else to_meter = fr_meter = 1; geod_f = es/(1 + sqrt(1 - es)); diff --git a/src/apps/proj.cpp b/src/apps/proj.cpp index 852cea04..0bf98b3a 100644 --- a/src/apps/proj.cpp +++ b/src/apps/proj.cpp @@ -380,11 +380,18 @@ int main(int argc, char **argv) { (void)printf("%9s %-16s %-16s %s\n", le->id, le->major, le->ell, le->name); } else if (arg[1] == 'u') { /* list units */ - const struct PJ_UNITS *lu; - - for (lu = proj_list_units(); lu->id ; ++lu) - (void)printf("%12s %-20s %s\n", - lu->id, lu->to_meter, lu->name); + auto units = proj_get_units_from_database(nullptr, nullptr, "linear", false, nullptr); + for( int i = 0; units && units[i]; i++ ) + { + if( units[i]->proj_short_name ) + { + (void)printf("%12s %-20.15g %s\n", + units[i]->proj_short_name, + units[i]->conv_factor, + units[i]->name); + } + } + proj_unit_list_destroy(units); } else emess(1,"invalid list option: l%c",arg[1]); exit(0); diff --git a/src/conversions/unitconvert.cpp b/src/conversions/unitconvert.cpp index f8439aee..609b30e0 100644 --- a/src/conversions/unitconvert.cpp +++ b/src/conversions/unitconvert.cpp @@ -393,9 +393,7 @@ static double get_unit_conversion_factor(const char* name, /***********************************************************************/ int i; const char* s; - const PJ_UNITS *units; - - units = proj_list_units(); + const PJ_UNITS *units = pj_list_linear_units(); /* Try first with linear units */ for (i = 0; (s = units[i].id) ; ++i) { @@ -411,7 +409,7 @@ static double get_unit_conversion_factor(const char* name, } /* And then angular units */ - units = proj_list_angular_units(); + units = pj_list_angular_units(); for (i = 0; (s = units[i].id) ; ++i) { if ( strcmp(s, name) == 0 ) { if( p_normalized_name ) { diff --git a/src/grids.cpp b/src/grids.cpp index b9da072c..c5fc6c74 100644 --- a/src/grids.cpp +++ b/src/grids.cpp @@ -309,8 +309,11 @@ constexpr uint16 TIFFTAG_GEOTRANSMATRIX = 34264; constexpr uint16 TIFFTAG_GEOKEYDIRECTORY = 34735; constexpr uint16 TIFFTAG_GEODOUBLEPARAMS = 34736; constexpr uint16 TIFFTAG_GEOASCIIPARAMS = 34737; +#ifndef TIFFTAG_GDAL_METADATA +// Starting with libtiff > 4.1.0, those symbolic names are #define in tiff.h constexpr uint16 TIFFTAG_GDAL_METADATA = 42112; constexpr uint16 TIFFTAG_GDAL_NODATA = 42113; +#endif // --------------------------------------------------------------------------- @@ -1373,7 +1376,8 @@ VerticalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) { if (IsTIFF(header_size, header)) { #ifdef TIFF_ENABLED - auto set = GTiffVGridShiftSet::open(ctx, std::move(fp), actualName); + auto set = std::unique_ptr<VerticalShiftGridSet>( + GTiffVGridShiftSet::open(ctx, std::move(fp), actualName)); if (!set) pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); return set; @@ -2351,7 +2355,8 @@ HorizontalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) { } else if (IsTIFF(header_size, reinterpret_cast<const unsigned char *>(header))) { #ifdef TIFF_ENABLED - auto set = GTiffHGridShiftSet::open(ctx, std::move(fp), actualName); + auto set = std::unique_ptr<HorizontalShiftGridSet>( + GTiffHGridShiftSet::open(ctx, std::move(fp), actualName)); if (!set) pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); return set; @@ -2686,8 +2691,8 @@ GenericShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) { if (IsTIFF(header_size, header)) { #ifdef TIFF_ENABLED - auto set = - GTiffGenericGridShiftSet::open(ctx, std::move(fp), actualName); + auto set = std::unique_ptr<GenericShiftGridSet>( + GTiffGenericGridShiftSet::open(ctx, std::move(fp), actualName)); if (!set) pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); return set; diff --git a/src/init.cpp b/src/init.cpp index a25d1ccd..101fc8ad 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -738,7 +738,7 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i return pj_default_destructor (PIN, PJD_ERR_K_LESS_THAN_ZERO); /* Set units */ - units = proj_list_units(); + units = pj_list_linear_units(); s = nullptr; if ((name = pj_param(ctx, start, "sunits").s) != nullptr) { for (i = 0; (s = units[i].id) && strcmp(name, s) ; ++i) ; diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp index d5f299c2..17fbd079 100644 --- a/src/iso19111/c_api.cpp +++ b/src/iso19111/c_api.cpp @@ -216,12 +216,17 @@ struct PJ_OBJ_LIST { explicit PJ_OBJ_LIST(std::vector<IdentifiedObjectNNPtr> &&objectsIn) : objects(std::move(objectsIn)) {} + virtual ~PJ_OBJ_LIST(); PJ_OBJ_LIST(const PJ_OBJ_LIST &) = delete; PJ_OBJ_LIST &operator=(const PJ_OBJ_LIST &) = delete; //! @endcond }; +//! @cond Doxygen_Suppress +PJ_OBJ_LIST::~PJ_OBJ_LIST() = default; +//! @endcond + // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress @@ -662,7 +667,8 @@ PJ *proj_create_from_database(PJ_CONTEXT *ctx, const char *auth_name, // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress -static const char *get_unit_category(UnitOfMeasure::Type type) { +static const char *get_unit_category(const std::string &unit_name, + UnitOfMeasure::Type type) { const char *ret = nullptr; switch (type) { case UnitOfMeasure::Type::UNKNOWN: @@ -672,19 +678,26 @@ static const char *get_unit_category(UnitOfMeasure::Type type) { ret = "none"; break; case UnitOfMeasure::Type::ANGULAR: - ret = "angular"; + ret = unit_name.find(" per ") != std::string::npos ? "angular_per_time" + : "angular"; break; case UnitOfMeasure::Type::LINEAR: - ret = "linear"; + ret = unit_name.find(" per ") != std::string::npos ? "linear_per_time" + : "linear"; break; case UnitOfMeasure::Type::SCALE: - ret = "scale"; + ret = unit_name.find(" per year") != std::string::npos || + unit_name.find(" per second") != std::string::npos + ? "scale_per_time" + : "scale"; break; case UnitOfMeasure::Type::TIME: ret = "time"; break; case UnitOfMeasure::Type::PARAMETRIC: - ret = "parametric"; + ret = unit_name.find(" per ") != std::string::npos + ? "parametric_per_time" + : "parametric"; break; } return ret; @@ -704,8 +717,9 @@ static const char *get_unit_category(UnitOfMeasure::Type type) { * @param out_conv_factor Pointer to a value to store the conversion * factor of the prime meridian longitude unit to radian. or NULL * @param out_category Pointer to a string value to store the parameter name. or - * NULL. This value might be "unknown", "none", "linear", "angular", "scale", - * "time" or "parametric"; + * NULL. This value might be "unknown", "none", "linear", "linear_per_time", + * "angular", "angular_per_time", "scale", "scale_per_time", "time", + * "parametric" or "parametric_per_time" * @return TRUE in case of success */ int proj_uom_get_info_from_database(PJ_CONTEXT *ctx, const char *auth_name, @@ -726,7 +740,7 @@ int proj_uom_get_info_from_database(PJ_CONTEXT *ctx, const char *auth_name, *out_conv_factor = obj->conversionToSI(); } if (out_category) { - *out_category = get_unit_category(obj->type()); + *out_category = get_unit_category(obj->name(), obj->type()); } ctx->cpp_context->autoCloseDbIfNeeded(); return true; @@ -2585,6 +2599,100 @@ void proj_crs_info_list_destroy(PROJ_CRS_INFO **list) { // --------------------------------------------------------------------------- +/** \brief Enumerate units from the database, taking into account various + * criteria. + * + * The returned object is an array of PROJ_UNIT_INFO* pointers, whose last + * entry is NULL. This array should be freed with proj_unit_list_destroy() + * + * @param ctx PROJ context, or NULL for default context + * @param auth_name Authority name, used to restrict the search. + * Or NULL for all authorities. + * @param category Filter by category, if this parameter is not NULL. Category + * is one of "linear", "linear_per_time", "angular", "angular_per_time", + * "scale", "scale_per_time" or "time" + * @param allow_deprecated whether we should return deprecated objects as well. + * @param out_result_count Output parameter pointing to an integer to receive + * the size of the result list. Might be NULL + * @return an array of PROJ_UNIT_INFO* pointers to be freed with + * proj_unit_list_destroy(), or NULL in case of error. + * + * @since 7.1 + */ +PROJ_UNIT_INFO **proj_get_units_from_database(PJ_CONTEXT *ctx, + const char *auth_name, + const char *category, + int allow_deprecated, + int *out_result_count) { + SANITIZE_CTX(ctx); + PROJ_UNIT_INFO **ret = nullptr; + int i = 0; + try { + auto factory = AuthorityFactory::create(getDBcontext(ctx), + auth_name ? auth_name : ""); + auto list = factory->getUnitList(); + ret = new PROJ_UNIT_INFO *[list.size() + 1]; + for (const auto &info : list) { + if (category && info.category != category) { + continue; + } + if (!allow_deprecated && info.deprecated) { + continue; + } + ret[i] = new PROJ_UNIT_INFO; + ret[i]->auth_name = pj_strdup(info.authName.c_str()); + ret[i]->code = pj_strdup(info.code.c_str()); + ret[i]->name = pj_strdup(info.name.c_str()); + ret[i]->category = pj_strdup(info.category.c_str()); + ret[i]->conv_factor = info.convFactor; + ret[i]->proj_short_name = + info.projShortName.empty() + ? nullptr + : pj_strdup(info.projShortName.c_str()); + ret[i]->deprecated = info.deprecated; + i++; + } + ret[i] = nullptr; + if (out_result_count) + *out_result_count = i; + ctx->cpp_context->autoCloseDbIfNeeded(); + return ret; + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + if (ret) { + ret[i + 1] = nullptr; + proj_unit_list_destroy(ret); + } + if (out_result_count) + *out_result_count = 0; + } + ctx->cpp_context->autoCloseDbIfNeeded(); + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Destroy the result returned by + * proj_get_units_from_database(). + * + * @since 7.1 + */ +void proj_unit_list_destroy(PROJ_UNIT_INFO **list) { + if (list) { + for (int i = 0; list[i] != nullptr; i++) { + pj_dalloc(list[i]->auth_name); + pj_dalloc(list[i]->code); + pj_dalloc(list[i]->name); + pj_dalloc(list[i]->category); + pj_dalloc(list[i]->proj_short_name); + delete list[i]; + } + delete[] list; + } +} + +// --------------------------------------------------------------------------- + /** \brief Return the Conversion of a DerivedCRS (such as a ProjectedCRS), * or the Transformation from the baseCRS to the hubCRS of a BoundCRS * @@ -6749,8 +6857,9 @@ int proj_coordoperation_get_param_index(PJ_CONTEXT *ctx, * unit code. or NULL * @param out_unit_category Pointer to a string value to store the parameter * name. or - * NULL. This value might be "unknown", "none", "linear", "angular", "scale", - * "time" or "parametric"; + * NULL. This value might be "unknown", "none", "linear", "linear_per_time", + * "angular", "angular_per_time", "scale", "scale_per_time", "time", + * "parametric" or "parametric_per_time" * @return TRUE in case of success. */ @@ -6852,7 +6961,8 @@ int proj_coordoperation_get_param( *out_unit_code = unit.code().c_str(); } if (out_unit_category) { - *out_unit_category = get_unit_category(unit.type()); + *out_unit_category = + get_unit_category(unit.name(), unit.type()); } } } @@ -7406,6 +7516,62 @@ void PROJ_DLL proj_operation_factory_context_set_discard_superseded( // --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress +/** \brief Opaque object representing a set of operation results. */ +struct PJ_OPERATION_LIST : PJ_OBJ_LIST { + + PJ *source_crs; + PJ *target_crs; + bool hasPreparedOperation = false; + std::vector<CoordOperation> preparedOperations{}; + + explicit PJ_OPERATION_LIST(PJ_CONTEXT *ctx, const PJ *source_crsIn, + const PJ *target_crsIn, + std::vector<IdentifiedObjectNNPtr> &&objectsIn); + ~PJ_OPERATION_LIST() override; + + PJ_OPERATION_LIST(const PJ_OPERATION_LIST &) = delete; + PJ_OPERATION_LIST &operator=(const PJ_OPERATION_LIST &) = delete; + + const std::vector<CoordOperation> &getPreparedOperations(PJ_CONTEXT *ctx); +}; + +// --------------------------------------------------------------------------- + +PJ_OPERATION_LIST::PJ_OPERATION_LIST( + PJ_CONTEXT *ctx, const PJ *source_crsIn, const PJ *target_crsIn, + std::vector<IdentifiedObjectNNPtr> &&objectsIn) + : PJ_OBJ_LIST(std::move(objectsIn)), + source_crs(proj_clone(ctx, source_crsIn)), + target_crs(proj_clone(ctx, target_crsIn)) {} + +// --------------------------------------------------------------------------- + +PJ_OPERATION_LIST::~PJ_OPERATION_LIST() { + auto tmpCtxt = proj_context_create(); + proj_assign_context(source_crs, tmpCtxt); + proj_assign_context(target_crs, tmpCtxt); + proj_destroy(source_crs); + proj_destroy(target_crs); + proj_context_destroy(tmpCtxt); +} + +// --------------------------------------------------------------------------- + +const std::vector<CoordOperation> & +PJ_OPERATION_LIST::getPreparedOperations(PJ_CONTEXT *ctx) { + if (!hasPreparedOperation) { + hasPreparedOperation = true; + preparedOperations = + pj_create_prepared_operations(ctx, source_crs, target_crs, this); + } + return preparedOperations; +} + +//! @endcond + +// --------------------------------------------------------------------------- + /** \brief Find a list of CoordinateOperation from source_crs to target_crs. * * The operations are sorted with the most relevant ones first: by @@ -7457,7 +7623,8 @@ proj_create_operations(PJ_CONTEXT *ctx, const PJ *source_crs, for (const auto &op : ops) { objects.emplace_back(op); } - return new PJ_OBJ_LIST(std::move(objects)); + return new PJ_OPERATION_LIST(ctx, source_crs, target_crs, + std::move(objects)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; @@ -7466,6 +7633,54 @@ proj_create_operations(PJ_CONTEXT *ctx, const PJ *source_crs, // --------------------------------------------------------------------------- +/** Return the index of the operation that would be the most appropriate to + * transform the specified coordinates. + * + * This operation may use resources that are not locally available, depending + * on the search criteria used by proj_create_operations(). + * + * This could be done by using proj_create_operations() with a punctual bounding + * box, but this function is faster when one needs to evaluate on many points + * with the same (source_crs, target_crs) tuple. + * + * @param ctx PROJ context, or NULL for default context + * @param operations List of operations returned by proj_create_operations() + * @param direction Direction into which to transform the point. + * @param coord Coordinate to transform + * @return the index in operations that would be used to transform coord. Or -1 + * in case of error, or no match. + * + * @since 7.1 + */ +int proj_get_suggested_operation(PJ_CONTEXT *ctx, PJ_OBJ_LIST *operations, + PJ_DIRECTION direction, PJ_COORD coord) { + SANITIZE_CTX(ctx); + auto opList = dynamic_cast<PJ_OPERATION_LIST *>(operations); + if (opList == nullptr) { + proj_log_error(ctx, __FUNCTION__, + "operations is not a list of operations"); + return -1; + } + + // Special case: + // proj_create_crs_to_crs_from_pj() always use the unique operation + // if there's a single one + if (opList->objects.size() == 1) { + return 0; + } + + int iExcluded[2] = {-1, -1}; + const auto &preparedOps = opList->getPreparedOperations(ctx); + int idx = pj_get_suggested_operation(ctx, preparedOps, iExcluded, direction, + coord); + if (idx >= 0) { + idx = preparedOps[idx].idxInOriginalList; + } + return idx; +} + +// --------------------------------------------------------------------------- + /** \brief Return the number of objects in the result set * * @param result Object of type PJ_OBJ_LIST (must not be NULL) @@ -7787,8 +8002,8 @@ PJ *proj_normalize_for_visualization(PJ_CONTEXT *ctx, const PJ *obj) { } } pjNew->alternativeCoordinateOperations.emplace_back( - minxSrc, minySrc, maxxSrc, maxySrc, minxDst, minyDst, - maxxDst, maxyDst, + alt.idxInOriginalList, minxSrc, minySrc, maxxSrc, + maxySrc, minxDst, minyDst, maxxDst, maxyDst, pj_obj_create(ctx, co->normalizeForVisualization()), co->nameStr(), alt.accuracy, alt.isOffshore); } diff --git a/src/iso19111/common.cpp b/src/iso19111/common.cpp index f2e4de4c..e3c747b9 100644 --- a/src/iso19111/common.cpp +++ b/src/iso19111/common.cpp @@ -39,6 +39,7 @@ #include "proj/internal/io_internal.hpp" #include "proj.h" +#include "proj_internal.h" #include <cmath> // M_PI #include <cstdlib> @@ -312,7 +313,7 @@ bool UnitOfMeasure::operator!=(const UnitOfMeasure &other) PROJ_PURE_DEFN { //! @cond Doxygen_Suppress std::string UnitOfMeasure::exportToPROJString() const { if (type() == Type::LINEAR) { - auto proj_units = proj_list_units(); + auto proj_units = pj_list_linear_units(); for (int i = 0; proj_units[i].id != nullptr; i++) { if (::fabs(proj_units[i].factor - conversionToSI()) < 1e-10 * conversionToSI()) { @@ -320,7 +321,7 @@ std::string UnitOfMeasure::exportToPROJString() const { } } } else if (type() == Type::ANGULAR) { - auto proj_angular_units = proj_list_angular_units(); + auto proj_angular_units = pj_list_angular_units(); for (int i = 0; proj_angular_units[i].id != nullptr; i++) { if (::fabs(proj_angular_units[i].factor - conversionToSI()) < 1e-10 * conversionToSI()) { diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp index 63b11761..33210d24 100644 --- a/src/iso19111/coordinateoperation.cpp +++ b/src/iso19111/coordinateoperation.cpp @@ -2443,7 +2443,7 @@ bool ParameterValue::_isEquivalentTo(const util::IComparable *other, } switch (type()) { case Type::MEASURE: { - return value()._isEquivalentTo(otherPV->value(), criterion); + return value()._isEquivalentTo(otherPV->value(), criterion, 2e-10); } case Type::STRING: diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index e1181d43..b0f5bcf9 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -310,17 +310,15 @@ CRSNNPtr CRS::alterCSLinearUnit(const common::UnitOfMeasure &unit) const { auto cartCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>( engCRS->coordinateSystem()); if (cartCS) { - auto props = createPropertyMap(this); - props.set("FORCE_OUTPUT_CS", true); - return EngineeringCRS::create(props, engCRS->datum(), + return EngineeringCRS::create(createPropertyMap(this), + engCRS->datum(), cartCS->alterUnit(unit)); } else { auto vertCS = util::nn_dynamic_pointer_cast<cs::VerticalCS>( engCRS->coordinateSystem()); if (vertCS) { - auto props = createPropertyMap(this); - props.set("FORCE_OUTPUT_CS", true); - return EngineeringCRS::create(props, engCRS->datum(), + return EngineeringCRS::create(createPropertyMap(this), + engCRS->datum(), vertCS->alterUnit(unit)); } } @@ -3743,7 +3741,7 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { return res; } res.emplace_back(crsNN, eqName ? 90 : 70); - } else if (crs->nameStr() == thisName && + } else if (objects.size() == 1 && CRS::getPrivate()->implicitCS_ && coordinateSystem() ->axisList()[0] @@ -3762,10 +3760,11 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { derivingConversionRef()->_isEquivalentTo( crs->derivingConversionRef().get(), util::IComparable::Criterion::EQUIVALENT, - dbContext) && - objects.size() == 1) { + dbContext)) { res.clear(); - res.emplace_back(crsNN, 100); + res.emplace_back(crsNN, crs->nameStr() == thisName + ? 100 + : eqName ? 90 : 70); return res; } else { res.emplace_back(crsNN, 25); @@ -5433,9 +5432,7 @@ bool TemporalCRS::_isEquivalentTo( // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress -struct EngineeringCRS::Private { - bool forceOutputCS_ = false; -}; +struct EngineeringCRS::Private {}; //! @endcond // --------------------------------------------------------------------------- @@ -5493,17 +5490,6 @@ EngineeringCRS::create(const util::PropertyMap &properties, crs->assignSelf(crs); crs->setProperties(properties); - const auto pVal = properties.get("FORCE_OUTPUT_CS"); - if (pVal) { - if (const auto genVal = - dynamic_cast<const util::BoxedValue *>(pVal->get())) { - if (genVal->type() == util::BoxedValue::Type::BOOLEAN && - genVal->booleanValue()) { - crs->d->forceOutputCS_ = true; - } - } - } - return crs; } @@ -5518,11 +5504,16 @@ void EngineeringCRS::_exportToWKT(io::WKTFormatter *formatter) const { formatter->addQuotedString(nameStr()); if (isWKT2 || !datum()->nameStr().empty()) { datum()->_exportToWKT(formatter); - coordinateSystem()->_exportToWKT(formatter); } - if (!isWKT2 && d->forceOutputCS_) { + if (!isWKT2) { coordinateSystem()->axisList()[0]->unit()._exportToWKT(formatter); } + + const auto oldAxisOutputRule = formatter->outputAxis(); + formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES); + coordinateSystem()->_exportToWKT(formatter); + formatter->setOutputAxis(oldAxisOutputRule); + ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); } diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp index 1c836367..6a707e0d 100644 --- a/src/iso19111/factory.cpp +++ b/src/iso19111/factory.cpp @@ -5214,6 +5214,64 @@ std::list<AuthorityFactory::CRSInfo> AuthorityFactory::getCRSInfoList() const { // --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress +AuthorityFactory::UnitInfo::UnitInfo() + : authName{}, code{}, name{}, category{}, convFactor{}, projShortName{}, + deprecated{} {} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the list of units. + * @throw FactoryException + * + * @since 7.1 + */ +std::list<AuthorityFactory::UnitInfo> AuthorityFactory::getUnitList() const { + std::string sql = "SELECT auth_name, code, name, type, conv_factor, " + "proj_short_name, deprecated FROM unit_of_measure"; + ListOfParams params; + if (d->hasAuthorityRestriction()) { + sql += " WHERE auth_name = ?"; + params.emplace_back(d->authority()); + } + sql += " ORDER BY auth_name, code"; + + auto sqlRes = d->run(sql, params); + std::list<AuthorityFactory::UnitInfo> res; + for (const auto &row : sqlRes) { + AuthorityFactory::UnitInfo info; + info.authName = row[0]; + info.code = row[1]; + info.name = row[2]; + const std::string &raw_category(row[3]); + if (raw_category == "length") { + info.category = info.name.find(" per ") != std::string::npos + ? "linear_per_time" + : "linear"; + } else if (raw_category == "angle") { + info.category = info.name.find(" per ") != std::string::npos + ? "angular_per_time" + : "angular"; + } else if (raw_category == "scale") { + info.category = + info.name.find(" per year") != std::string::npos || + info.name.find(" per second") != std::string::npos + ? "scale_per_time" + : "scale"; + } else { + info.category = raw_category; + } + info.convFactor = row[4].empty() ? 0 : c_locale_stod(row[4]); + info.projShortName = row[5]; + info.deprecated = row[6] == "1"; + res.emplace_back(info); + } + return res; +} + +// --------------------------------------------------------------------------- + /** \brief Gets the official name from a possibly alias name. * * @param aliasedName Alias name. diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index c57014d9..6a1d7e32 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -3689,6 +3689,15 @@ WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) { auto props = buildProperties(node); + auto &csNode = nodeP->lookForChild(WKTConstants::CS_); + const auto &nodeValue = nodeP->value(); + if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::PROJCS) && + !ci_equal(nodeValue, WKTConstants::BASEPROJCRS)) { + ThrowMissing(WKTConstants::CS_); + } + auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); + auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs); + const std::string projCRSName = stripQuotes(nodeP->children()[0]); if (esriStyle_ && dbContext_) { // It is likely that the ESRI definition of EPSG:32661 (UPS North) & @@ -3709,6 +3718,27 @@ WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) { projCRSName, "projected_crs", "ESRI", false, outTableName, authNameFromAlias, codeFromAlias); if (!officialName.empty()) { + // Special case for https://github.com/OSGeo/PROJ/issues/2086 + // The name of the CRS to identify is + // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501 + // whereas it should be + // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501_Feet + constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37; + if (projCRSName.find("_FIPS_") != std::string::npos && + projCRSName.find("_Feet") == std::string::npos && + std::fabs( + cartesianCS->axisList()[0]->unit().conversionToSI() - + US_FOOT_CONV_FACTOR) < 1e-10 * US_FOOT_CONV_FACTOR) { + auto officialNameFromFeet = + authFactory->getOfficialNameFromAlias( + projCRSName + "_Feet", "projected_crs", "ESRI", + false, outTableName, authNameFromAlias, + codeFromAlias); + if (!officialNameFromFeet.empty()) { + officialName = officialNameFromFeet; + } + } + props.set(IdentifiedObject::NAME_KEY, officialName); } } @@ -3739,15 +3769,6 @@ WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) { ? buildConversion(conversionNode, linearUnit, angularUnit) : buildProjection(node, projectionNode, linearUnit, angularUnit); - auto &csNode = nodeP->lookForChild(WKTConstants::CS_); - const auto &nodeValue = nodeP->value(); - if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::PROJCS) && - !ci_equal(nodeValue, WKTConstants::BASEPROJCRS)) { - ThrowMissing(WKTConstants::CS_); - } - auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); - auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs); - // No explicit AXIS node ? (WKT1) if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) { props.set("IMPLICIT_CS", true); diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake index d08a0ddf..8978e20f 100644 --- a/src/lib_proj.cmake +++ b/src/lib_proj.cmake @@ -292,7 +292,7 @@ source_group("Source Files\\Transformations" source_group("Source Files\\ISO19111" FILES ${SRC_LIBPROJ_ISO19111}) -include_directories(${CMAKE_SOURCE_DIR}/include) +include_directories(${PROJ_SOURCE_DIR}/include) include_directories(${CMAKE_CURRENT_BINARY_DIR}) source_group("CMake Files" FILES CMakeLists.txt) @@ -151,6 +151,24 @@ extern "C" { #endif #endif +#ifdef PROJ_SUPPRESS_DEPRECATION_MESSAGE + #define PROJ_DEPRECATED(decl, msg) decl +#elif defined(__has_extension) + #if __has_extension(attribute_deprecated_with_message) + #define PROJ_DEPRECATED(decl, msg) decl __attribute__ ((deprecated(msg))) + #elif defined(__GNUC__) + #define PROJ_DEPRECATED(decl, msg) decl __attribute__ ((deprecated)) + #else + #define PROJ_DEPRECATED(decl, msg) decl + #endif +#elif defined(__GNUC__) + #define PROJ_DEPRECATED(decl, msg) decl __attribute__ ((deprecated)) +#elif defined(_MSVC_VER) + #define PROJ_DEPRECATED(decl, msg) __declspec(deprecated(msg)) decl +#else + #define PROJ_DEPRECATED(decl, msg) decl +#endif + /* The version numbers should be updated with every release! **/ #define PROJ_VERSION_MAJOR 7 #define PROJ_VERSION_MINOR 1 @@ -608,8 +626,8 @@ PJ_INIT_INFO PROJ_DLL proj_init_info(const char *initname); /* Get lists of operations, ellipsoids, units and prime meridians. */ const PJ_OPERATIONS PROJ_DLL *proj_list_operations(void); const PJ_ELLPS PROJ_DLL *proj_list_ellps(void); -const PJ_UNITS PROJ_DLL *proj_list_units(void); -const PJ_UNITS PROJ_DLL *proj_list_angular_units(void); +PROJ_DEPRECATED(const PJ_UNITS PROJ_DLL *proj_list_units(void), "Deprecated by proj_get_units_from_database"); +PROJ_DEPRECATED(const PJ_UNITS PROJ_DLL *proj_list_angular_units(void), "Deprecated by proj_get_units_from_database"); const PJ_PRIME_MERIDIANS PROJ_DLL *proj_list_prime_meridians(void); /* These are trivial, and while occasionally useful in real code, primarily here to */ @@ -912,6 +930,39 @@ typedef struct int allow_deprecated; } PROJ_CRS_LIST_PARAMETERS; +/** \brief Structure given description of a unit. + * + * This structure may grow over time, and should not be directly allocated by + * client code. + * @since 7.1 + */ +typedef struct +{ + /** Authority name. */ + char* auth_name; + + /** Object code. */ + char* code; + + /** Object name. For example "metre", "US survey foot", etc. */ + char* name; + + /** Category of the unit: one of "linear", "linear_per_time", "angular", + * "angular_per_time", "scale", "scale_per_time" or "time" */ + char* category; + + /** Conversion factor to apply to transform from that unit to the + * corresponding SI unit (metre for "linear", radian for "angular", etc.). + * It might be 0 in some cases to indicate no known conversion factor. */ + double conv_factor; + + /** PROJ short name, like "m", "ft", "us-ft", etc... Might be NULL */ + char* proj_short_name; + + /** Whether the object is deprecated */ + int deprecated; +} PROJ_UNIT_INFO; + /**@}*/ @@ -1077,6 +1128,15 @@ PROJ_CRS_INFO PROJ_DLL **proj_get_crs_info_list_from_database( void PROJ_DLL proj_crs_info_list_destroy(PROJ_CRS_INFO** list); +PROJ_UNIT_INFO PROJ_DLL **proj_get_units_from_database( + PJ_CONTEXT *ctx, + const char *auth_name, + const char *category, + int allow_deprecated, + int *out_result_count); + +void PROJ_DLL proj_unit_list_destroy(PROJ_UNIT_INFO** list); + /* ------------------------------------------------------------------------- */ @@ -1157,6 +1217,11 @@ PJ PROJ_DLL *proj_list_get(PJ_CONTEXT *ctx, void PROJ_DLL proj_list_destroy(PJ_OBJ_LIST *result); +int PROJ_DLL proj_get_suggested_operation(PJ_CONTEXT *ctx, + PJ_OBJ_LIST *operations, + PJ_DIRECTION direction, + PJ_COORD coord); + /* ------------------------------------------------------------------------- */ PJ PROJ_DLL *proj_crs_get_geodetic_crs(PJ_CONTEXT *ctx, const PJ *crs); diff --git a/src/proj_internal.h b/src/proj_internal.h index 8f73200d..a3bc9f17 100644 --- a/src/proj_internal.h +++ b/src/proj_internal.h @@ -287,6 +287,55 @@ typedef PJ_COORD (* PJ_OPERATOR) (PJ_COORD, PJ *); #define PJD_GRIDSHIFT 3 #define PJD_WGS84 4 /* WGS84 (or anything considered equivalent) */ +struct CoordOperation +{ + int idxInOriginalList; + double minxSrc = 0.0; + double minySrc = 0.0; + double maxxSrc = 0.0; + double maxySrc = 0.0; + double minxDst = 0.0; + double minyDst = 0.0; + double maxxDst = 0.0; + double maxyDst = 0.0; + PJ* pj = nullptr; + std::string name{}; + double accuracy = -1.0; + bool isOffshore = false; + + CoordOperation(int idxInOriginalListIn, + double minxSrcIn, double minySrcIn, double maxxSrcIn, double maxySrcIn, + double minxDstIn, double minyDstIn, double maxxDstIn, double maxyDstIn, + PJ* pjIn, const std::string& nameIn, double accuracyIn, bool isOffshoreIn): + idxInOriginalList(idxInOriginalListIn), + minxSrc(minxSrcIn), minySrc(minySrcIn), maxxSrc(maxxSrcIn), maxySrc(maxySrcIn), + minxDst(minxDstIn), minyDst(minyDstIn), maxxDst(maxxDstIn), maxyDst(maxyDstIn), + pj(pjIn), name(nameIn), + accuracy(accuracyIn), + isOffshore(isOffshoreIn) + { + } + + CoordOperation(const CoordOperation&) = delete; + + CoordOperation(CoordOperation&& other): + idxInOriginalList(other.idxInOriginalList), + minxSrc(other.minxSrc), minySrc(other.minySrc), maxxSrc(other.maxxSrc), maxySrc(other.maxySrc), + minxDst(other.minxDst), minyDst(other.minyDst), maxxDst(other.maxxDst), maxyDst(other.maxyDst), + name(std::move(other.name)), + accuracy(other.accuracy), + isOffshore(other.isOffshore) { + pj = other.pj; + other.pj = nullptr; + } + + CoordOperation& operator=(const CoordOperation&) = delete; + + ~CoordOperation() + { + proj_destroy(pj); + } +}; /* base projection data structure */ struct PJconsts { @@ -493,52 +542,6 @@ struct PJconsts { /************************************************************************************* proj_create_crs_to_crs() alternative coordinate operations **************************************************************************************/ - - struct CoordOperation - { - double minxSrc = 0.0; - double minySrc = 0.0; - double maxxSrc = 0.0; - double maxySrc = 0.0; - double minxDst = 0.0; - double minyDst = 0.0; - double maxxDst = 0.0; - double maxyDst = 0.0; - PJ* pj = nullptr; - std::string name{}; - double accuracy = -1.0; - bool isOffshore = false; - - CoordOperation(double minxSrcIn, double minySrcIn, double maxxSrcIn, double maxySrcIn, - double minxDstIn, double minyDstIn, double maxxDstIn, double maxyDstIn, - PJ* pjIn, const std::string& nameIn, double accuracyIn, bool isOffshoreIn): - minxSrc(minxSrcIn), minySrc(minySrcIn), maxxSrc(maxxSrcIn), maxySrc(maxySrcIn), - minxDst(minxDstIn), minyDst(minyDstIn), maxxDst(maxxDstIn), maxyDst(maxyDstIn), - pj(pjIn), name(nameIn), - accuracy(accuracyIn), - isOffshore(isOffshoreIn) - { - } - - CoordOperation(const CoordOperation&) = delete; - - CoordOperation(CoordOperation&& other): - minxSrc(other.minxSrc), minySrc(other.minySrc), maxxSrc(other.maxxSrc), maxySrc(other.maxySrc), - minxDst(other.minxDst), minyDst(other.minyDst), maxxDst(other.maxxDst), maxyDst(other.maxyDst), - name(std::move(other.name)), - accuracy(other.accuracy), - isOffshore(other.isOffshore) { - pj = other.pj; - other.pj = nullptr; - } - - CoordOperation& operator=(const CoordOperation&) = delete; - - ~CoordOperation() - { - proj_destroy(pj); - } - }; std::vector<CoordOperation> alternativeCoordinateOperations{}; int iCurCoordOp = -1; @@ -873,6 +876,20 @@ std::string PROJ_DLL pj_context_get_user_writable_directory(PJ_CONTEXT *ctx, boo void PROJ_DLL pj_context_set_user_writable_directory(PJ_CONTEXT* ctx, const std::string& path); std::string PROJ_DLL pj_get_relative_share_proj(PJ_CONTEXT *ctx); +std::vector<CoordOperation> pj_create_prepared_operations(PJ_CONTEXT *ctx, + const PJ *source_crs, + const PJ *target_crs, + PJ_OBJ_LIST* op_list); + +int pj_get_suggested_operation(PJ_CONTEXT *ctx, + const std::vector<CoordOperation>& opList, + const int iExcluded[2], + PJ_DIRECTION direction, + PJ_COORD coord); + +const PJ_UNITS *pj_list_linear_units(); +const PJ_UNITS *pj_list_angular_units(); + /* classic public API */ #include "proj_api.h" diff --git a/src/projections/stere.cpp b/src/projections/stere.cpp index d95bb7fa..abc4aa13 100644 --- a/src/projections/stere.cpp +++ b/src/projections/stere.cpp @@ -5,7 +5,7 @@ #include <math.h> PROJ_HEAD(stere, "Stereographic") "\n\tAzi, Sph&Ell\n\tlat_ts="; -PROJ_HEAD(ups, "Universal Polar Stereographic") "\n\tAzi, Sph&Ell\n\tsouth"; +PROJ_HEAD(ups, "Universal Polar Stereographic") "\n\tAzi, Ell\n\tsouth"; namespace { // anonymous namespace @@ -320,8 +320,7 @@ PJ *PROJECTION(ups) { /* International Ellipsoid */ P->phi0 = pj_param(P->ctx, P->params, "bsouth").i ? - M_HALFPI: M_HALFPI; if (P->es == 0.0) { - proj_errno_set(P, PJD_ERR_ELLIPSOID_USE_REQUIRED); - return pj_default_destructor (P, ENOMEM); + return pj_default_destructor (P, PJD_ERR_ELLIPSOID_USE_REQUIRED); } P->k0 = .994; P->x0 = 2000000.; diff --git a/src/projections/tmerc.cpp b/src/projections/tmerc.cpp index 1414e542..75a77ccd 100644 --- a/src/projections/tmerc.cpp +++ b/src/projections/tmerc.cpp @@ -22,7 +22,7 @@ PROJ_HEAD(tmerc, "Transverse Mercator") "\n\tCyl, Sph&Ell\n\tapprox"; PROJ_HEAD(etmerc, "Extended Transverse Mercator") "\n\tCyl, Sph"; -PROJ_HEAD(utm, "Universal Transverse Mercator (UTM)") "\n\tCyl, Sph\n\tzone= south approx"; +PROJ_HEAD(utm, "Universal Transverse Mercator (UTM)") "\n\tCyl, Ell\n\tzone= south approx"; namespace { // anonymous namespace struct pj_opaque_approx { @@ -345,42 +345,31 @@ static PJ *setup_approx(PJ *P) { /*****************************************************************************/ /* Helper functios for "exact" transverse mercator */ -#ifdef _GNU_SOURCE - inline -#endif -static double gatg(double *p1, int len_p1, double B) { - double *p; - double h = 0, h1, h2 = 0, cos_2B; +inline +static double gatg(const double *p1, int len_p1, double B, double cos_2B, double sin_2B) { + double h = 0, h1, h2 = 0; - cos_2B = 2*cos(2*B); - p = p1 + len_p1; + const double two_cos_2B = 2*cos_2B; + const double* p = p1 + len_p1; h1 = *--p; while (p - p1) { - h = -h2 + cos_2B*h1 + *--p; + h = -h2 + two_cos_2B*h1 + *--p; h2 = h1; h1 = h; } - return (B + h*sin(2*B)); + return (B + h*sin_2B); } /* Complex Clenshaw summation */ -#ifdef _GNU_SOURCE - inline -#endif -static double clenS(double *a, int size, double arg_r, double arg_i, double *R, double *I) { - double *p, r, i, hr, hr1, hr2, hi, hi1, hi2; - double sin_arg_r, cos_arg_r, sinh_arg_i, cosh_arg_i; +inline +static double clenS(const double *a, int size, + double sin_arg_r, double cos_arg_r, + double sinh_arg_i, double cosh_arg_i, + double *R, double *I) { + double r, i, hr, hr1, hr2, hi, hi1, hi2; /* arguments */ - p = a + size; -#ifdef _GNU_SOURCE - sincos(arg_r, &sin_arg_r, &cos_arg_r); -#else - sin_arg_r = sin(arg_r); - cos_arg_r = cos(arg_r); -#endif - sinh_arg_i = sinh(arg_i); - cosh_arg_i = cosh(arg_i); + const double* p = a + size; r = 2*cos_arg_r*cosh_arg_i; i = -2*sin_arg_r*sinh_arg_i; @@ -427,28 +416,70 @@ static double clens(double *a, int size, double arg_r) { static PJ_XY exact_e_fwd (PJ_LP lp, PJ *P) { PJ_XY xy = {0.0,0.0}; struct pj_opaque_exact *Q = static_cast<struct pj_opaque_exact*>(P->opaque); - double sin_Cn, cos_Cn, cos_Ce, sin_Ce, dCn, dCe; - double Cn = lp.phi, Ce = lp.lam; /* ell. LAT, LNG -> Gaussian LAT, LNG */ - Cn = gatg (Q->cbg, PROJ_ETMERC_ORDER, Cn); + double Cn = gatg (Q->cbg, PROJ_ETMERC_ORDER, lp.phi, cos(2*lp.phi), sin(2*lp.phi)); /* Gaussian LAT, LNG -> compl. sph. LAT */ -#ifdef _GNU_SOURCE - sincos (Cn, &sin_Cn, &cos_Cn); - sincos (Ce, &sin_Ce, &cos_Ce); -#else - sin_Cn = sin (Cn); - cos_Cn = cos (Cn); - sin_Ce = sin (Ce); - cos_Ce = cos (Ce); + const double sin_Cn = sin (Cn); + const double cos_Cn = cos (Cn); + const double sin_Ce = sin (lp.lam); + const double cos_Ce = cos (lp.lam); + + const double cos_Cn_cos_Ce = cos_Cn*cos_Ce; + Cn = atan2 (sin_Cn, cos_Cn_cos_Ce); + + const double inv_denom_tan_Ce = 1. / hypot (sin_Cn, cos_Cn_cos_Ce); + const double tan_Ce = sin_Ce*cos_Cn * inv_denom_tan_Ce; +#if 0 + // Variant of the above: found not to be measurably faster + const double sin_Ce_cos_Cn = sin_Ce*cos_Cn; + const double denom = sqrt(1 - sin_Ce_cos_Cn * sin_Ce_cos_Cn); + const double tan_Ce = sin_Ce_cos_Cn / denom; #endif - Cn = atan2 (sin_Cn, cos_Ce*cos_Cn); - Ce = atan2 (sin_Ce*cos_Cn, hypot (sin_Cn, cos_Cn*cos_Ce)); - /* compl. sph. N, E -> ell. norm. N, E */ - Ce = asinh ( tan (Ce) ); /* Replaces: Ce = log(tan(FORTPI + Ce*0.5)); */ - Cn += clenS (Q->gtu, PROJ_ETMERC_ORDER, 2*Cn, 2*Ce, &dCn, &dCe); + double Ce = asinh ( tan_Ce ); /* Replaces: Ce = log(tan(FORTPI + Ce*0.5)); */ + +/* + * Non-optimized version: + * const double sin_arg_r = sin(2*Cn); + * const double cos_arg_r = cos(2*Cn); + * + * Given: + * sin(2 * Cn) = 2 sin(Cn) cos(Cn) + * sin(atan(y)) = y / sqrt(1 + y^2) + * cos(atan(y)) = 1 / sqrt(1 + y^2) + * ==> sin(2 * Cn) = 2 tan_Cn / (1 + tan_Cn^2) + * + * cos(2 * Cn) = 2cos^2(Cn) - 1 + * = 2 / (1 + tan_Cn^2) - 1 + */ + const double tmp_r = 2 * cos_Cn_cos_Ce * inv_denom_tan_Ce * inv_denom_tan_Ce; + const double sin_arg_r = sin_Cn * tmp_r; + const double cos_arg_r = cos_Cn_cos_Ce * tmp_r - 1; + +/* + * Non-optimized version: + * const double sinh_arg_i = sinh(2*Ce); + * const double cosh_arg_i = cosh(2*Ce); + * + * Given + * sinh(2 * Ce) = 2 sinh(Ce) cosh(Ce) + * sinh(asinh(y)) = y + * cosh(asinh(y)) = sqrt(1 + y^2) + * ==> sinh(2 * Ce) = 2 tan_Ce sqrt(1 + tan_Ce^2) + * + * cosh(2 * Ce) = 2cosh^2(Ce) - 1 + * = 2 * (1 + tan_Ce^2) - 1 + */ + const double tmp_i = 1 + tan_Ce * tan_Ce; + const double sinh_arg_i = 2 * tan_Ce * sqrt(tmp_i); + const double cosh_arg_i = 2 * tmp_i - 1; + + double dCn, dCe; + Cn += clenS (Q->gtu, PROJ_ETMERC_ORDER, + sin_arg_r, cos_arg_r, sinh_arg_i, cosh_arg_i, + &dCn, &dCe); Ce += dCe; if (fabs (Ce) <= 2.623395162778) { xy.y = Q->Qn * Cn + Q->Zb; /* Northing */ @@ -463,32 +494,68 @@ static PJ_XY exact_e_fwd (PJ_LP lp, PJ *P) { static PJ_LP exact_e_inv (PJ_XY xy, PJ *P) { PJ_LP lp = {0.0,0.0}; struct pj_opaque_exact *Q = static_cast<struct pj_opaque_exact*>(P->opaque); - double sin_Cn, cos_Cn, cos_Ce, sin_Ce, dCn, dCe; - double Cn = xy.y, Ce = xy.x; /* normalize N, E */ - Cn = (Cn - Q->Zb)/Q->Qn; - Ce = Ce/Q->Qn; + double Cn = (xy.y - Q->Zb)/Q->Qn; + double Ce = xy.x/Q->Qn; if (fabs(Ce) <= 2.623395162778) { /* 150 degrees */ /* norm. N, E -> compl. sph. LAT, LNG */ - Cn += clenS(Q->utg, PROJ_ETMERC_ORDER, 2*Cn, 2*Ce, &dCn, &dCe); + const double sin_arg_r = sin(2*Cn); + const double cos_arg_r = cos(2*Cn); + + //const double sinh_arg_i = sinh(2*Ce); + //const double cosh_arg_i = cosh(2*Ce); + const double exp_2_Ce = exp(2*Ce); + const double half_inv_exp_2_Ce = 0.5 / exp_2_Ce; + const double sinh_arg_i = 0.5 * exp_2_Ce - half_inv_exp_2_Ce; + const double cosh_arg_i = 0.5 * exp_2_Ce + half_inv_exp_2_Ce; + + double dCn_ignored, dCe; + Cn += clenS(Q->utg, PROJ_ETMERC_ORDER, + sin_arg_r, cos_arg_r, sinh_arg_i, cosh_arg_i, + &dCn_ignored, &dCe); Ce += dCe; - Ce = atan (sinh (Ce)); /* Replaces: Ce = 2*(atan(exp(Ce)) - FORTPI); */ + /* compl. sph. LAT -> Gaussian LAT, LNG */ -#ifdef _GNU_SOURCE - sincos (Cn, &sin_Cn, &cos_Cn); - sincos (Ce, &sin_Ce, &cos_Ce); -#else - sin_Cn = sin (Cn); - cos_Cn = cos (Cn); + const double sin_Cn = sin (Cn); + const double cos_Cn = cos (Cn); + +#if 0 + // Non-optimized version: + double sin_Ce, cos_Ce; + Ce = atan (sinh (Ce)); // Replaces: Ce = 2*(atan(exp(Ce)) - FORTPI); sin_Ce = sin (Ce); cos_Ce = cos (Ce); -#endif Ce = atan2 (sin_Ce, cos_Ce*cos_Cn); Cn = atan2 (sin_Cn*cos_Ce, hypot (sin_Ce, cos_Ce*cos_Cn)); +#else +/* + * One can divide both member of Ce = atan2(...) by cos_Ce, which gives: + * Ce = atan2 (tan_Ce, cos_Cn) = atan2(sinh(Ce), cos_Cn) + * + * and the same for Cn = atan2(...) + * Cn = atan2 (sin_Cn, hypot (sin_Ce, cos_Ce*cos_Cn)/cos_Ce) + * = atan2 (sin_Cn, hypot (sin_Ce/cos_Ce, cos_Cn)) + * = atan2 (sin_Cn, hypot (tan_Ce, cos_Cn)) + * = atan2 (sin_Cn, hypot (sinhCe, cos_Cn)) + */ + const double sinhCe = sinh (Ce); + Ce = atan2 (sinhCe, cos_Cn); + const double modulus_Ce = hypot (sinhCe, cos_Cn); + Cn = atan2 (sin_Cn, modulus_Ce); +#endif + /* Gaussian LAT, LNG -> ell. LAT, LNG */ - lp.phi = gatg (Q->cgb, PROJ_ETMERC_ORDER, Cn); + + // Optimization of the computation of cos(2*Cn) and sin(2*Cn) + const double tmp = 2 * modulus_Ce / (sinhCe * sinhCe + 1); + const double sin_2_Cn = sin_Cn * tmp; + const double cos_2_Cn = tmp * modulus_Ce - 1.; + //const double cos_2_Cn = cos(2 * Cn); + //const double sin_2_Cn = sin(2 * Cn); + + lp.phi = gatg (Q->cgb, PROJ_ETMERC_ORDER, Cn, cos_2_Cn, sin_2_Cn); lp.lam = Ce; } else @@ -573,7 +640,7 @@ static PJ *setup_exact(PJ *P) { Q->gtu[5] = np*(212378941/319334400.0); /* Gaussian latitude value of the origin latitude */ - Z = gatg (Q->cbg, PROJ_ETMERC_ORDER, P->phi0); + Z = gatg (Q->cbg, PROJ_ETMERC_ORDER, P->phi0, cos(2*P->phi0), sin(2*P->phi0)); /* Origin northing minus true northing at the origin latitude */ /* i.e. true northing = N - P->Zb */ @@ -627,8 +694,7 @@ PJ *PROJECTION(etmerc) { PJ *PROJECTION(utm) { long zone; if (P->es == 0.0) { - proj_errno_set(P, PJD_ERR_ELLIPSOID_USE_REQUIRED); - return pj_default_destructor(P, ENOMEM); + return pj_default_destructor(P, PJD_ERR_ELLIPSOID_USE_REQUIRED); } if (P->lam0 < -1000.0 || P->lam0 > 1000.0) { return pj_default_destructor(P, PJD_ERR_INVALID_UTM_ZONE); diff --git a/src/units.cpp b/src/units.cpp index 34a71db1..53f43163 100644 --- a/src/units.cpp +++ b/src/units.cpp @@ -36,6 +36,12 @@ pj_units[] = { {nullptr, nullptr, nullptr, 0.0} }; +// For internal use +const PJ_UNITS *pj_list_linear_units() +{ + return pj_units; +} + const PJ_UNITS *proj_list_units() { return pj_units; @@ -52,6 +58,12 @@ pj_angular_units[] = { {nullptr, nullptr, nullptr, 0.0} }; +// For internal use +const PJ_UNITS *pj_list_angular_units() +{ + return pj_angular_units; +} + const PJ_UNITS *proj_list_angular_units() { return pj_angular_units; |
