diff options
| author | Even Rouault <even.rouault@spatialys.com> | 2018-11-14 17:40:42 +0100 |
|---|---|---|
| committer | Even Rouault <even.rouault@spatialys.com> | 2018-11-14 22:48:29 +0100 |
| commit | d928db15d53805d9b728b440079756081961c536 (patch) | |
| tree | e862a961d26bedb34c58e4f28ef0bdeedb5f3225 /src | |
| parent | 330e8bf686f9c4524075ca1ff50cbca6c9e091da (diff) | |
| download | PROJ-d928db15d53805d9b728b440079756081961c536.tar.gz PROJ-d928db15d53805d9b728b440079756081961c536.zip | |
Implement RFC 2: Initial integration of "GDAL SRS barn" work
This work mostly consists of:
- a C++ implementation of the ISO-19111:2018 / OGC Topic 2
"Referencing by coordinates" classes to represent Datums,
Coordinate systems, CRSs (Coordinate Reference Systems) and
Coordinate Operations.
- methods to convert between this C++ modeling and WKT1, WKT2
and PROJ string representations of those objects
- management and query of a SQLite3 database of CRS and Coordinate Operation definition
- a C API binding part of those capabilities
This is all-in-one squashed commit of the work of
https://github.com/OSGeo/proj.4/pull/1040
Diffstat (limited to 'src')
39 files changed, 40974 insertions, 355 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c4f4dd20..de8ca3e5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,6 +9,7 @@ option(BUILD_GEOD "Build geod (computation of geodesic lines)" ON) option(BUILD_GIE "Build gie (geospatial integrity investigation environment - a PROJ.4 test tool)" ON) option(BUILD_NAD2BIN "Build nad2bin (format conversion tool)" ON) option(BUILD_PROJ "Build proj (cartographic projection tool : latlong <-> projected coordinates)" ON) +option(BUILD_PROJINFO "Build projinfo (SRS and coordinate operation metadata/query tool)" ON) if(NOT MSVC) @@ -59,6 +60,11 @@ if(BUILD_PROJ) set(BIN_TARGETS ${BIN_TARGETS} binproj) endif(BUILD_PROJ) +if(BUILD_PROJINFO) + include(bin_projinfo.cmake) + set(BIN_TARGETS ${BIN_TARGETS} binprojinfo) +endif(BUILD_PROJINFO) + if(BUILD_GIE) include(bin_gie.cmake) set(BIN_TARGETS ${BIN_TARGETS} gie) diff --git a/src/Makefile.am b/src/Makefile.am index 359f2f28..81524e82 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,23 +1,24 @@ AM_CFLAGS = @C_WFLAGS@ -AM_CXXFLAGS = @CXX_WFLAGS@ -bin_PROGRAMS = proj nad2bin geod cs2cs gie cct +bin_PROGRAMS = proj nad2bin geod cs2cs gie cct projinfo EXTRA_PROGRAMS = multistresstest test228 TESTS = geodtest check_PROGRAMS = geodtest AM_CPPFLAGS = -DPROJ_LIB=\"$(pkgdatadir)\" \ - -DMUTEX_@MUTEX_SETTING@ @JNI_INCLUDE@ + -DMUTEX_@MUTEX_SETTING@ @JNI_INCLUDE@ -I$(top_srcdir)/include @SQLITE3_FLAGS@ +AM_CXXFLAGS = @CXX_WFLAGS@ @FLTO_FLAG@ -DPROJ_COMPILATION -include_HEADERS = proj.h proj_api.h geodesic.h \ - org_proj4_PJ.h +include_HEADERS = proj.h proj_constants.h proj_api.h geodesic.h \ + org_proj4_PJ.h proj_symbol_rename.h -EXTRA_DIST = proj.def bin_cct.cmake bin_gie.cmake bin_cs2cs.cmake \ - bin_geod.cmake bin_nad2bin.cmake bin_proj.cmake \ +EXTRA_DIST = bin_cct.cmake bin_gie.cmake bin_cs2cs.cmake \ + bin_geod.cmake bin_nad2bin.cmake bin_proj.cmake bin_projinfo.cmake \ lib_proj.cmake CMakeLists.txt bin_geodtest.cmake geodtest.c proj_SOURCES = proj.c gen_cheb.c p_series.c +projinfo_SOURCES = projinfo.cpp cs2cs_SOURCES = cs2cs.c gen_cheb.c p_series.c cct_SOURCES = cct.c proj_strtod.c proj_strtod.h optargpm.h nad2bin_SOURCES = nad2bin.c @@ -32,6 +33,7 @@ cct_LDADD = libproj.la cs2cs_LDADD = libproj.la geod_LDADD = libproj.la proj_LDADD = libproj.la +projinfo_LDADD = libproj.la nad2bin_LDADD = libproj.la gie_LDADD = libproj.la @@ -42,9 +44,12 @@ geodtest_LDADD = libproj.la lib_LTLIBRARIES = libproj.la libproj_la_LDFLAGS = -no-undefined -version-info 14:1:1 +libproj_la_LIBADD = @SQLITE3_LDFLAGS@ libproj_la_SOURCES = \ pj_list.h proj_internal.h proj_math.h projects.h\ + static.cpp util.cpp metadata.cpp common.cpp crs.cpp datum.cpp coordinatesystem.cpp coordinateoperation.cpp io.cpp \ + internal.cpp factory.cpp c_api.cpp \ PJ_aeqd.c PJ_gnom.c PJ_laea.c PJ_mod_ster.c \ PJ_nsper.c PJ_nzmg.c PJ_ortho.c PJ_stere.c PJ_sterea.c \ PJ_aea.c PJ_bipc.c PJ_bonne.c PJ_eqdc.c PJ_isea.c PJ_ccon.c\ diff --git a/src/PJ_unitconvert.c b/src/PJ_unitconvert.c index e3824c20..402941a4 100644 --- a/src/PJ_unitconvert.c +++ b/src/PJ_unitconvert.c @@ -382,18 +382,6 @@ static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) { return out; } -/* M_PI / 200 */ -#define GRAD_TO_RAD 0.015707963267948967 - -static const struct PJ_UNITS -pj_angular_units[] = { - {"rad", "1.0", "Radian", 1.0}, - {"deg", "0.017453292519943296", "Degree", DEG_TO_RAD}, - {"grad", "0.015707963267948967", "Grad", GRAD_TO_RAD}, - {NULL, NULL, NULL, 0.0} -}; - - /***********************************************************************/ static double get_unit_conversion_factor(const char* name, int* p_is_linear, @@ -419,15 +407,16 @@ static double get_unit_conversion_factor(const char* name, } /* And then angular units */ - for (i = 0; (s = pj_angular_units[i].id) ; ++i) { + units = proj_list_angular_units(); + for (i = 0; (s = units[i].id) ; ++i) { if ( strcmp(s, name) == 0 ) { if( p_normalized_name ) { - *p_normalized_name = pj_angular_units[i].name; + *p_normalized_name = units[i].name; } if( p_is_linear ) { *p_is_linear = 0; } - return pj_angular_units[i].factor; + return units[i].factor; } } if( p_normalized_name ) { diff --git a/src/bin_cct.cmake b/src/bin_cct.cmake index caa261a8..37752217 100644 --- a/src/bin_cct.cmake +++ b/src/bin_cct.cmake @@ -7,3 +7,7 @@ add_executable(cct ${CCT_SRC} ${CCT_INCLUDE}) target_link_libraries(cct ${PROJ_LIBRARIES}) install(TARGETS cct RUNTIME DESTINATION ${BINDIR}) + +if(MSVC AND BUILD_LIBPROJ_SHARED) + target_compile_definitions(cct PRIVATE PROJ_MSVC_DLL_IMPORT=1) +endif() diff --git a/src/bin_cs2cs.cmake b/src/bin_cs2cs.cmake index 59e57adc..bdb16e1a 100644 --- a/src/bin_cs2cs.cmake +++ b/src/bin_cs2cs.cmake @@ -4,11 +4,11 @@ set(CS2CS_SRC cs2cs.c source_group("Source Files\\Bin" FILES ${CS2CS_SRC}) -if(WIN32) - set(CS2CS_SRC ${CS2CS_SRC} emess.c) -endif(WIN32) - add_executable(cs2cs ${CS2CS_SRC} ${CS2CS_INCLUDE}) target_link_libraries(cs2cs ${PROJ_LIBRARIES}) install(TARGETS cs2cs RUNTIME DESTINATION ${BINDIR}) + +if(MSVC AND BUILD_LIBPROJ_SHARED) + target_compile_definitions(cs2cs PRIVATE PROJ_MSVC_DLL_IMPORT=1) +endif() diff --git a/src/bin_geod.cmake b/src/bin_geod.cmake index c6dff717..1f9ee788 100644 --- a/src/bin_geod.cmake +++ b/src/bin_geod.cmake @@ -4,13 +4,12 @@ set(GEOD_INCLUDE geod_interface.h) source_group("Source Files\\Bin" FILES ${GEOD_SRC} ${GEOD_INCLUDE}) -if(WIN32) - set(GEOD_SRC ${GEOD_SRC} emess.c) -endif(WIN32) - #Executable add_executable(geod ${GEOD_SRC} ${GEOD_INCLUDE}) target_link_libraries(geod ${PROJ_LIBRARIES}) install(TARGETS geod RUNTIME DESTINATION ${BINDIR}) +if(MSVC AND BUILD_LIBPROJ_SHARED) + target_compile_definitions(geod PRIVATE PROJ_MSVC_DLL_IMPORT=1) +endif() diff --git a/src/bin_geodtest.cmake b/src/bin_geodtest.cmake index b89f75be..743c630e 100644 --- a/src/bin_geodtest.cmake +++ b/src/bin_geodtest.cmake @@ -10,3 +10,7 @@ target_link_libraries(geodtest ${PROJ_LIBRARIES}) # Instead run as a test add_test (NAME geodesic-test COMMAND geodtest) + +if(MSVC AND BUILD_LIBPROJ_SHARED) + target_compile_definitions(geodtest PRIVATE PROJ_MSVC_DLL_IMPORT=1) +endif() diff --git a/src/bin_gie.cmake b/src/bin_gie.cmake index b5f8f8ef..596592fa 100644 --- a/src/bin_gie.cmake +++ b/src/bin_gie.cmake @@ -7,3 +7,7 @@ add_executable(gie ${GIE_SRC} ${GIE_INCLUDE}) target_link_libraries(gie ${PROJ_LIBRARIES}) install(TARGETS gie RUNTIME DESTINATION ${BINDIR}) + +if(MSVC AND BUILD_LIBPROJ_SHARED) + target_compile_definitions(gie PRIVATE PROJ_MSVC_DLL_IMPORT=1) +endif() diff --git a/src/bin_nad2bin.cmake b/src/bin_nad2bin.cmake index 33bd4d0b..0d0fd473 100644 --- a/src/bin_nad2bin.cmake +++ b/src/bin_nad2bin.cmake @@ -5,13 +5,13 @@ endif(WIN32 AND BUILD_LIBPROJ_SHARED) set(NAD2BIN_SRC nad2bin.c) source_group("Source Files\\Bin" FILES ${NAD2BIN_SRC}) -if(WIN32) - set(NAD2BIN_SRC ${NAD2BIN_SRC} emess.c) -endif(WIN32) - + #Executable add_executable(nad2bin ${NAD2BIN_SRC}) target_link_libraries(nad2bin ${PROJ_LIBRARIES}) install(TARGETS nad2bin RUNTIME DESTINATION ${BINDIR}) +if(MSVC AND BUILD_LIBPROJ_SHARED) + target_compile_definitions(nad2bin PRIVATE PROJ_MSVC_DLL_IMPORT=1) +endif() diff --git a/src/bin_proj.cmake b/src/bin_proj.cmake index f20d2f3f..fb7c885d 100644 --- a/src/bin_proj.cmake +++ b/src/bin_proj.cmake @@ -4,10 +4,6 @@ set(PROJ_SRC proj.c source_group("Source Files\\Bin" FILES ${PROJ_SRC}) -if(WIN32) - set(PROJ_SRC ${PROJ_SRC} emess.c) -endif(WIN32) - #Executable add_executable(binproj ${PROJ_SRC}) set_target_properties(binproj @@ -17,3 +13,6 @@ target_link_libraries(binproj ${PROJ_LIBRARIES}) install(TARGETS binproj RUNTIME DESTINATION ${BINDIR}) +if(MSVC AND BUILD_LIBPROJ_SHARED) + target_compile_definitions(binproj PRIVATE PROJ_MSVC_DLL_IMPORT=1) +endif() diff --git a/src/bin_projinfo.cmake b/src/bin_projinfo.cmake new file mode 100644 index 00000000..a422a16d --- /dev/null +++ b/src/bin_projinfo.cmake @@ -0,0 +1,16 @@ +set(PROJINFO_SRC projinfo.cpp) + +source_group("Source Files\\Bin" FILES ${PROJINFO_SRC}) + +#Executable +add_executable(binprojinfo ${PROJINFO_SRC}) +set_target_properties(binprojinfo + PROPERTIES + OUTPUT_NAME projinfo) +target_link_libraries(binprojinfo ${PROJ_LIBRARIES}) +install(TARGETS binprojinfo + RUNTIME DESTINATION ${BINDIR}) + +if(MSVC AND BUILD_LIBPROJ_SHARED) + target_compile_definitions(binprojinfo PRIVATE PROJ_MSVC_DLL_IMPORT=1) +endif() diff --git a/src/c_api.cpp b/src/c_api.cpp new file mode 100644 index 00000000..1720a7a1 --- /dev/null +++ b/src/c_api.cpp @@ -0,0 +1,4038 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: C API wraper of C++ API + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include <cassert> +#include <cstdarg> +#include <cstring> +#include <map> +#include <utility> +#include <vector> + +#include "proj/common.hpp" +#include "proj/coordinateoperation.hpp" +#include "proj/crs.hpp" +#include "proj/datum.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" + +// PROJ include order is sensitive +// clang-format off +#include "proj_internal.h" +#include "proj.h" +#include "projects.h" +// clang-format on + +using namespace NS_PROJ::common; +using namespace NS_PROJ::crs; +using namespace NS_PROJ::datum; +using namespace NS_PROJ::io; +using namespace NS_PROJ::internal; +using namespace NS_PROJ::metadata; +using namespace NS_PROJ::operation; +using namespace NS_PROJ::util; +using namespace NS_PROJ; + +// --------------------------------------------------------------------------- + +static void PROJ_NO_INLINE proj_log_error(PJ_CONTEXT *ctx, const char *function, + const char *text) { + std::string msg(function); + msg += ": "; + msg += text; + ctx->logger(ctx->app_data, PJ_LOG_ERROR, msg.c_str()); +} + +// --------------------------------------------------------------------------- + +static void PROJ_NO_INLINE proj_log_debug(PJ_CONTEXT *ctx, const char *function, + const char *text) { + std::string msg(function); + msg += ": "; + msg += text; + ctx->logger(ctx->app_data, PJ_LOG_DEBUG, msg.c_str()); +} + +// --------------------------------------------------------------------------- + +/** \brief Opaque object representing a Ellipsoid, Datum, CRS or Coordinate + * Operation. Should be used by at most one thread at a time. */ +struct PJ_OBJ { + //! @cond Doxygen_Suppress + PJ_CONTEXT *ctx; + IdentifiedObjectNNPtr obj; + + // cached results + std::string lastWKT{}; + std::string lastPROJString{}; + bool gridsNeededAsked = false; + std::vector<GridDescription> gridsNeeded{}; + + explicit PJ_OBJ(PJ_CONTEXT *ctxIn, const IdentifiedObjectNNPtr &objIn) + : ctx(ctxIn), obj(objIn) {} + static PJ_OBJ *create(PJ_CONTEXT *ctxIn, + const IdentifiedObjectNNPtr &objIn); + + PJ_OBJ(const PJ_OBJ &) = delete; + PJ_OBJ &operator=(const PJ_OBJ &) = delete; + //! @endcond +}; + +//! @cond Doxygen_Suppress +PJ_OBJ *PJ_OBJ::create(PJ_CONTEXT *ctxIn, const IdentifiedObjectNNPtr &objIn) { + return new PJ_OBJ(ctxIn, objIn); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Opaque object representing a set of operation results. */ +struct PJ_OBJ_LIST { + //! @cond Doxygen_Suppress + PJ_CONTEXT *ctx; + std::vector<IdentifiedObjectNNPtr> objects; + + explicit PJ_OBJ_LIST(PJ_CONTEXT *ctxIn, + std::vector<IdentifiedObjectNNPtr> &&objectsIn) + : ctx(ctxIn), objects(std::move(objectsIn)) {} + + PJ_OBJ_LIST(const PJ_OBJ_LIST &) = delete; + PJ_OBJ_LIST &operator=(const PJ_OBJ_LIST &) = delete; + //! @endcond +}; + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +/** Auxiliary structure to PJ_CONTEXT storing C++ context stuff. */ +struct projCppContext { + DatabaseContextNNPtr databaseContext; + + explicit projCppContext(PJ_CONTEXT *ctxt, const char *dbPath = nullptr, + const char *const *auxDbPaths = nullptr) + : databaseContext(DatabaseContext::create( + dbPath ? dbPath : std::string(), toVector(auxDbPaths))) { + databaseContext->attachPJContext(ctxt); + } + + static std::vector<std::string> toVector(const char *const *auxDbPaths) { + std::vector<std::string> res; + for (auto iter = auxDbPaths; iter && *iter; ++iter) { + res.emplace_back(std::string(*iter)); + } + return res; + } +}; + +// --------------------------------------------------------------------------- + +void proj_context_delete_cpp_context(struct projCppContext *cppContext) { + delete cppContext; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +#define SANITIZE_CTX(ctx) \ + do { \ + if (ctx == nullptr) { \ + ctx = pj_get_default_ctx(); \ + } \ + } while (0) + +// --------------------------------------------------------------------------- + +static PROJ_NO_INLINE const DatabaseContextNNPtr & +getDBcontext(PJ_CONTEXT *ctx) { + if (ctx->cpp_context == nullptr) { + ctx->cpp_context = new projCppContext(ctx); + } + return ctx->cpp_context->databaseContext; +} + +// --------------------------------------------------------------------------- + +static PROJ_NO_INLINE DatabaseContextPtr +getDBcontextNoException(PJ_CONTEXT *ctx, const char *function) { + try { + return getDBcontext(ctx).as_nullable(); + } catch (const std::exception &e) { + proj_log_debug(ctx, function, e.what()); + return nullptr; + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Explicitly point to the main PROJ CRS and coordinate operation + * definition database ("proj.db"), and potentially auxiliary databases with + * same structure. + * + * @param ctx PROJ context, or NULL for default context + * @param dbPath Path to main database, or NULL for default. + * @param auxDbPaths NULL-terminated list of auxiliary database filenames, or + * NULL. + * @param options should be set to NULL for now + * @return TRUE in case of success + */ +int proj_context_set_database_path(PJ_CONTEXT *ctx, const char *dbPath, + const char *const *auxDbPaths, + const char *const *options) { + SANITIZE_CTX(ctx); + (void)options; + delete ctx->cpp_context; + ctx->cpp_context = nullptr; + try { + ctx->cpp_context = new projCppContext(ctx, dbPath, auxDbPaths); + return true; + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return false; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the path to the database. + * + * The returned pointer remains valid while ctx is valid, and until + * proj_context_set_database_path() is called. + * + * @param ctx PROJ context, or NULL for default context + * @return path, or nullptr + */ +const char *proj_context_get_database_path(PJ_CONTEXT *ctx) { + SANITIZE_CTX(ctx); + try { + return getDBcontext(ctx)->getPath().c_str(); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Guess the "dialect" of the WKT string. + * + * @param ctx PROJ context, or NULL for default context + * @param wkt String (must not be NULL) + */ +PJ_GUESSED_WKT_DIALECT proj_context_guess_wkt_dialect(PJ_CONTEXT *ctx, + const char *wkt) { + (void)ctx; + assert(wkt); + switch (WKTParser().guessDialect(wkt)) { + case WKTParser::WKTGuessedDialect::WKT2_2018: + return PJ_GUESSED_WKT2_2018; + case WKTParser::WKTGuessedDialect::WKT2_2015: + return PJ_GUESSED_WKT2_2015; + case WKTParser::WKTGuessedDialect::WKT1_GDAL: + return PJ_GUESSED_WKT1_GDAL; + case WKTParser::WKTGuessedDialect::WKT1_ESRI: + return PJ_GUESSED_WKT1_ESRI; + case WKTParser::WKTGuessedDialect::NOT_WKT: + break; + } + return PJ_GUESSED_NOT_WKT; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate an object from a WKT string, PROJ string or object code + * (like "EPSG:4326", "urn:ogc:def:crs:EPSG::4326", + * "urn:ogc:def:coordinateOperation:EPSG::1671"). + * + * This function calls osgeo::proj::io::createFromUserInput() + * + * The returned object must be unreferenced with proj_obj_unref() after use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param text String (must not be NULL) + * @param options should be set to NULL for now + * @return Object that must be unreferenced with proj_obj_unref(), or NULL in + * case of error. + */ +PJ_OBJ *proj_obj_create_from_user_input(PJ_CONTEXT *ctx, const char *text, + const char *const *options) { + SANITIZE_CTX(ctx); + assert(text); + (void)options; + auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); + try { + auto identifiedObject = nn_dynamic_pointer_cast<IdentifiedObject>( + createFromUserInput(text, dbContext)); + if (identifiedObject) { + return PJ_OBJ::create(ctx, NN_NO_CHECK(identifiedObject)); + } + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate an object from a WKT string. + * + * This function calls osgeo::proj::io::WKTParser::createFromWKT() + * + * The returned object must be unreferenced with proj_obj_unref() after use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param wkt WKT string (must not be NULL) + * @param options should be set to NULL for now + * @return Object that must be unreferenced with proj_obj_unref(), or NULL in + * case of error. + */ +PJ_OBJ *proj_obj_create_from_wkt(PJ_CONTEXT *ctx, const char *wkt, + const char *const *options) { + SANITIZE_CTX(ctx); + assert(wkt); + (void)options; + try { + auto identifiedObject = nn_dynamic_pointer_cast<IdentifiedObject>( + WKTParser().createFromWKT(wkt)); + if (identifiedObject) { + return PJ_OBJ::create(ctx, NN_NO_CHECK(identifiedObject)); + } + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate an object from a PROJ string. + * + * This function calls osgeo::proj::io::PROJStringParser::createFromPROJString() + * + * The returned object must be unreferenced with proj_obj_unref() after use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param proj_string PROJ string (must not be NULL) + * @param options should be set to NULL for now + * @return Object that must be unreferenced with proj_obj_unref(), or NULL in + * case of error. + */ +PJ_OBJ *proj_obj_create_from_proj_string(PJ_CONTEXT *ctx, + const char *proj_string, + const char *const *options) { + SANITIZE_CTX(ctx); + (void)options; + assert(proj_string); + try { + auto identifiedObject = nn_dynamic_pointer_cast<IdentifiedObject>( + PROJStringParser().createFromPROJString(proj_string)); + if (identifiedObject) { + return PJ_OBJ::create(ctx, NN_NO_CHECK(identifiedObject)); + } + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate an object from a database lookup. + * + * The returned object must be unreferenced with proj_obj_unref() after use. + * It should be used by at most one thread at a time. + * + * @param ctx Context, or NULL for default context. + * @param auth_name Authority name (must not be NULL) + * @param code Object code (must not be NULL) + * @param category Object category + * @param usePROJAlternativeGridNames Whether PROJ alternative grid names + * should be substituted to the official grid names. Only used on + * transformations + * @param options should be set to NULL for now + * @return Object that must be unreferenced with proj_obj_unref(), or NULL in + * case of error. + */ +PJ_OBJ *proj_obj_create_from_database(PJ_CONTEXT *ctx, const char *auth_name, + const char *code, + PJ_OBJ_CATEGORY category, + int usePROJAlternativeGridNames, + const char *const *options) { + assert(auth_name); + assert(code); + (void)options; + SANITIZE_CTX(ctx); + const std::string codeStr(code); + try { + auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name); + IdentifiedObjectPtr obj; + switch (category) { + case PJ_OBJ_CATEGORY_ELLIPSOID: + obj = factory->createEllipsoid(codeStr).as_nullable(); + break; + case PJ_OBJ_CATEGORY_DATUM: + obj = factory->createDatum(codeStr).as_nullable(); + break; + case PJ_OBJ_CATEGORY_CRS: + obj = + factory->createCoordinateReferenceSystem(codeStr).as_nullable(); + break; + case PJ_OBJ_CATEGORY_COORDINATE_OPERATION: + obj = factory + ->createCoordinateOperation( + codeStr, usePROJAlternativeGridNames != 0) + .as_nullable(); + break; + } + return PJ_OBJ::create(ctx, NN_NO_CHECK(obj)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Drops a reference on an object. + * + * This method should be called one and exactly one for each function + * returning a PJ_OBJ* + * + * @param obj Object, or NULL. + */ +void proj_obj_unref(PJ_OBJ *obj) { delete obj; } + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static AuthorityFactory::ObjectType +convertPJObjectTypeToObjectType(PJ_OBJ_TYPE type, bool &valid) { + valid = true; + AuthorityFactory::ObjectType cppType = AuthorityFactory::ObjectType::CRS; + switch (type) { + case PJ_OBJ_TYPE_ELLIPSOID: + cppType = AuthorityFactory::ObjectType::ELLIPSOID; + break; + + case PJ_OBJ_TYPE_GEODETIC_REFERENCE_FRAME: + case PJ_OBJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME: + cppType = AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME; + break; + + case PJ_OBJ_TYPE_VERTICAL_REFERENCE_FRAME: + case PJ_OBJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME: + cppType = AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME; + break; + + case PJ_OBJ_TYPE_DATUM_ENSEMBLE: + cppType = AuthorityFactory::ObjectType::DATUM; + break; + + case PJ_OBJ_TYPE_CRS: + cppType = AuthorityFactory::ObjectType::CRS; + break; + + case PJ_OBJ_TYPE_GEODETIC_CRS: + cppType = AuthorityFactory::ObjectType::GEODETIC_CRS; + break; + + case PJ_OBJ_TYPE_GEOCENTRIC_CRS: + cppType = AuthorityFactory::ObjectType::GEOCENTRIC_CRS; + break; + + case PJ_OBJ_TYPE_GEOGRAPHIC_CRS: + cppType = AuthorityFactory::ObjectType::GEOGRAPHIC_CRS; + break; + + case PJ_OBJ_TYPE_GEOGRAPHIC_2D_CRS: + cppType = AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS; + break; + + case PJ_OBJ_TYPE_GEOGRAPHIC_3D_CRS: + cppType = AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS; + break; + + case PJ_OBJ_TYPE_VERTICAL_CRS: + cppType = AuthorityFactory::ObjectType::VERTICAL_CRS; + break; + + case PJ_OBJ_TYPE_PROJECTED_CRS: + cppType = AuthorityFactory::ObjectType::PROJECTED_CRS; + break; + + case PJ_OBJ_TYPE_COMPOUND_CRS: + cppType = AuthorityFactory::ObjectType::COMPOUND_CRS; + break; + + case PJ_OBJ_TYPE_TEMPORAL_CRS: + valid = false; + break; + + case PJ_OBJ_TYPE_BOUND_CRS: + valid = false; + break; + + case PJ_OBJ_TYPE_OTHER_CRS: + cppType = AuthorityFactory::ObjectType::CRS; + break; + + case PJ_OBJ_TYPE_CONVERSION: + cppType = AuthorityFactory::ObjectType::CONVERSION; + break; + + case PJ_OBJ_TYPE_TRANSFORMATION: + cppType = AuthorityFactory::ObjectType::TRANSFORMATION; + break; + + case PJ_OBJ_TYPE_CONCATENATED_OPERATION: + cppType = AuthorityFactory::ObjectType::CONCATENATED_OPERATION; + break; + + case PJ_OBJ_TYPE_OTHER_COORDINATE_OPERATION: + cppType = AuthorityFactory::ObjectType::COORDINATE_OPERATION; + break; + + case PJ_OBJ_TYPE_UNKNOWN: + valid = false; + break; + } + return cppType; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return a list of objects by their name. + * + * @param ctx Context, or NULL for default context. + * @param auth_name Authority name, used to restrict the search. + * Or NULL for all authorities. + * @param searchedName Searched name. Must be at least 2 character long. + * @param types List of object types into which to search. If + * NULL, all object types will be searched. + * @param typesCount Number of elements in types, or 0 if types is NULL + * @param approximateMatch Whether approximate name identification is allowed. + * @param limitResultCount Maximum number of results to return. + * Or 0 for unlimited. + * @param options should be set to NULL for now + * @return a result set that must be unreferenced with + * proj_obj_list_unref(), or NULL in case of error. + */ +PJ_OBJ_LIST *proj_obj_create_from_name(PJ_CONTEXT *ctx, const char *auth_name, + const char *searchedName, + const PJ_OBJ_TYPE *types, + size_t typesCount, int approximateMatch, + size_t limitResultCount, + const char *const *options) { + assert(searchedName); + assert((types != nullptr && typesCount > 0) || + (types == nullptr && typesCount == 0)); + (void)options; + SANITIZE_CTX(ctx); + try { + auto factory = AuthorityFactory::create(getDBcontext(ctx), + auth_name ? auth_name : ""); + std::vector<AuthorityFactory::ObjectType> allowedTypes; + for (size_t i = 0; i < typesCount; ++i) { + bool valid = false; + auto type = convertPJObjectTypeToObjectType(types[i], valid); + if (valid) { + allowedTypes.push_back(type); + } + } + auto res = factory->createObjectsFromName(searchedName, allowedTypes, + approximateMatch != 0, + limitResultCount); + std::vector<IdentifiedObjectNNPtr> objects; + for (const auto &obj : res) { + objects.push_back(obj); + } + return new PJ_OBJ_LIST(ctx, std::move(objects)); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the type of an object. + * + * @param obj Object (must not be NULL) + * @return its type. + */ +PJ_OBJ_TYPE proj_obj_get_type(PJ_OBJ *obj) { + assert(obj); + auto ptr = obj->obj.get(); + if (dynamic_cast<Ellipsoid *>(ptr)) { + return PJ_OBJ_TYPE_ELLIPSOID; + } + + if (dynamic_cast<DynamicGeodeticReferenceFrame *>(ptr)) { + return PJ_OBJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME; + } + if (dynamic_cast<GeodeticReferenceFrame *>(ptr)) { + return PJ_OBJ_TYPE_GEODETIC_REFERENCE_FRAME; + } + if (dynamic_cast<DynamicVerticalReferenceFrame *>(ptr)) { + return PJ_OBJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME; + } + if (dynamic_cast<VerticalReferenceFrame *>(ptr)) { + return PJ_OBJ_TYPE_VERTICAL_REFERENCE_FRAME; + } + if (dynamic_cast<DatumEnsemble *>(ptr)) { + return PJ_OBJ_TYPE_DATUM_ENSEMBLE; + } + + { + auto crs = dynamic_cast<GeographicCRS *>(ptr); + if (crs) { + if (crs->coordinateSystem()->axisList().size() == 2) { + return PJ_OBJ_TYPE_GEOGRAPHIC_2D_CRS; + } else { + return PJ_OBJ_TYPE_GEOGRAPHIC_3D_CRS; + } + } + } + + { + auto crs = dynamic_cast<GeodeticCRS *>(ptr); + if (crs) { + if (crs->isGeocentric()) { + return PJ_OBJ_TYPE_GEOCENTRIC_CRS; + } else { + return PJ_OBJ_TYPE_GEODETIC_CRS; + } + } + } + + if (dynamic_cast<VerticalCRS *>(ptr)) { + return PJ_OBJ_TYPE_VERTICAL_CRS; + } + if (dynamic_cast<ProjectedCRS *>(ptr)) { + return PJ_OBJ_TYPE_PROJECTED_CRS; + } + if (dynamic_cast<CompoundCRS *>(ptr)) { + return PJ_OBJ_TYPE_COMPOUND_CRS; + } + if (dynamic_cast<TemporalCRS *>(ptr)) { + return PJ_OBJ_TYPE_TEMPORAL_CRS; + } + if (dynamic_cast<BoundCRS *>(ptr)) { + return PJ_OBJ_TYPE_BOUND_CRS; + } + if (dynamic_cast<CRS *>(ptr)) { + return PJ_OBJ_TYPE_OTHER_CRS; + } + + if (dynamic_cast<Conversion *>(ptr)) { + return PJ_OBJ_TYPE_CONVERSION; + } + if (dynamic_cast<Transformation *>(ptr)) { + return PJ_OBJ_TYPE_TRANSFORMATION; + } + if (dynamic_cast<ConcatenatedOperation *>(ptr)) { + return PJ_OBJ_TYPE_CONCATENATED_OPERATION; + } + if (dynamic_cast<CoordinateOperation *>(ptr)) { + return PJ_OBJ_TYPE_OTHER_COORDINATE_OPERATION; + } + + return PJ_OBJ_TYPE_UNKNOWN; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether an object is deprecated. + * + * @param obj Object (must not be NULL) + * @return TRUE if it is deprecated, FALSE otherwise + */ +int proj_obj_is_deprecated(PJ_OBJ *obj) { + assert(obj); + return obj->obj->isDeprecated(); +} + +// --------------------------------------------------------------------------- + +/** \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_obj_is_equivalent_to(PJ_OBJ *obj, PJ_OBJ *other, + PJ_COMPARISON_CRITERION criterion) { + assert(obj); + assert(other); + + // Make sure that the C and C++ enumerations match + static_assert(static_cast<int>(PJ_COMP_STRICT) == + static_cast<int>(IComparable::Criterion::STRICT), + ""); + static_assert(static_cast<int>(PJ_COMP_EQUIVALENT) == + static_cast<int>(IComparable::Criterion::EQUIVALENT), + ""); + static_assert( + static_cast<int>(PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS) == + static_cast<int>( + IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS), + ""); + + // Make sure we enumerate all values. If adding a new value, as we + // don't have a default clause, the compiler will warn. + switch (criterion) { + case PJ_COMP_STRICT: + case PJ_COMP_EQUIVALENT: + case PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS: + break; + } + const IComparable::Criterion cppCriterion = + static_cast<IComparable::Criterion>(criterion); + return obj->obj->isEquivalentTo(other->obj.get(), cppCriterion); +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether an object is a CRS + * + * @param obj Object (must not be NULL) + */ +int proj_obj_is_crs(PJ_OBJ *obj) { + assert(obj); + return dynamic_cast<CRS *>(obj->obj.get()) != nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Get the name of an object. + * + * The lifetime of the returned string is the same as the input obj parameter. + * + * @param obj Object (must not be NULL) + * @return a string, or NULL in case of error or missing name. + */ +const char *proj_obj_get_name(PJ_OBJ *obj) { + assert(obj); + const auto &desc = obj->obj->name()->description(); + if (!desc.has_value()) { + return nullptr; + } + // The object will still be alived after the function call. + // cppcheck-suppress stlcstr + return desc->c_str(); +} + +// --------------------------------------------------------------------------- + +/** \brief Get the authority name / codespace of an identifier of an object. + * + * The lifetime of the returned string is the same as the input obj parameter. + * + * @param obj Object (must not be NULL) + * @param index Index of the identifier. 0 = first identifier + * @return a string, or NULL in case of error or missing name. + */ +const char *proj_obj_get_id_auth_name(PJ_OBJ *obj, int index) { + assert(obj); + const auto &ids = obj->obj->identifiers(); + if (static_cast<size_t>(index) >= ids.size()) { + return nullptr; + } + const auto &codeSpace = ids[index]->codeSpace(); + if (!codeSpace.has_value()) { + return nullptr; + } + // The object will still be alived after the function call. + // cppcheck-suppress stlcstr + return codeSpace->c_str(); +} + +// --------------------------------------------------------------------------- + +/** \brief Get the code of an identifier of an object. + * + * The lifetime of the returned string is the same as the input obj parameter. + * + * @param obj Object (must not be NULL) + * @param index Index of the identifier. 0 = first identifier + * @return a string, or NULL in case of error or missing name. + */ +const char *proj_obj_get_id_code(PJ_OBJ *obj, int index) { + assert(obj); + const auto &ids = obj->obj->identifiers(); + if (static_cast<size_t>(index) >= ids.size()) { + return nullptr; + } + return ids[index]->code().c_str(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const char *getOptionValue(const char *option, + const char *keyWithEqual) noexcept { + if (ci_starts_with(option, keyWithEqual)) { + return option + strlen(keyWithEqual); + } + return nullptr; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Get a WKT representation of an object. + * + * The returned string is valid while the input obj parameter is valid, + * and until a next call to proj_obj_as_wkt() with the same input object. + * + * This function calls osgeo::proj::io::IWKTExportable::exportToWKT(). + * + * This function may return NULL if the object is not compatible with an + * export to the requested type. + * + * @param obj Object (must not be NULL) + * @param type WKT version. + * @param options null-terminated list of options, or NULL. Currently + * supported options are: + * <ul> + * <li>MULTILINE=YES/NO. Defaults to YES, except for WKT1_ESRI</li> + * <li>INDENTATION_WIDTH=number. Defauls to 4 (when multiline output is + * on).</li> + * <li>OUTPUT_AXIS=YES/NO. Defaults to YES, except for WKT1_ESRI.</li> + * </ul> + * @return a string, or NULL in case of error. + */ +const char *proj_obj_as_wkt(PJ_OBJ *obj, PJ_WKT_TYPE type, + const char *const *options) { + assert(obj); + + // Make sure that the C and C++ enumerations match + static_assert(static_cast<int>(PJ_WKT2_2015) == + static_cast<int>(WKTFormatter::Convention::WKT2_2015), + ""); + static_assert( + static_cast<int>(PJ_WKT2_2015_SIMPLIFIED) == + static_cast<int>(WKTFormatter::Convention::WKT2_2015_SIMPLIFIED), + ""); + static_assert(static_cast<int>(PJ_WKT2_2018) == + static_cast<int>(WKTFormatter::Convention::WKT2_2018), + ""); + static_assert( + static_cast<int>(PJ_WKT2_2018_SIMPLIFIED) == + static_cast<int>(WKTFormatter::Convention::WKT2_2018_SIMPLIFIED), + ""); + static_assert(static_cast<int>(PJ_WKT1_GDAL) == + static_cast<int>(WKTFormatter::Convention::WKT1_GDAL), + ""); + static_assert(static_cast<int>(PJ_WKT1_ESRI) == + static_cast<int>(WKTFormatter::Convention::WKT1_ESRI), + ""); + // Make sure we enumerate all values. If adding a new value, as we + // don't have a default clause, the compiler will warn. + switch (type) { + case PJ_WKT2_2015: + case PJ_WKT2_2015_SIMPLIFIED: + case PJ_WKT2_2018: + case PJ_WKT2_2018_SIMPLIFIED: + case PJ_WKT1_GDAL: + case PJ_WKT1_ESRI: + break; + } + const WKTFormatter::Convention convention = + static_cast<WKTFormatter::Convention>(type); + try { + auto formatter = WKTFormatter::create(convention); + for (auto iter = options; iter && iter[0]; ++iter) { + const char *value; + if ((value = getOptionValue(*iter, "MULTILINE="))) { + formatter->setMultiLine(ci_equal(value, "YES")); + } else if ((value = getOptionValue(*iter, "INDENTATION_WIDTH="))) { + formatter->setIndentationWidth(std::atoi(value)); + } else if ((value = getOptionValue(*iter, "OUTPUT_AXIS="))) { + formatter->setOutputAxis(ci_equal(value, "YES")); + } else { + std::string msg("Unknown option :"); + msg += *iter; + proj_log_error(obj->ctx, __FUNCTION__, msg.c_str()); + return nullptr; + } + } + obj->lastWKT = obj->obj->exportToWKT(formatter.get()); + return obj->lastWKT.c_str(); + } catch (const std::exception &e) { + proj_log_error(obj->ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Get a PROJ string representation of an object. + * + * The returned string is valid while the input obj parameter is valid, + * and until a next call to proj_obj_as_proj_string() with the same input + * object. + * + * This function calls + * osgeo::proj::io::IPROJStringExportable::exportToPROJString(). + * + * This function may return NULL if the object is not compatible with an + * export to the requested type. + * + * @param obj Object (must not be NULL) + * @param type PROJ String version. + * @param options NULL-terminated list of strings with "KEY=VALUE" format. or + * NULL. + * The currently recognized option is USE_ETMERC=YES to use + * +proj=etmerc instead of +proj=tmerc + * @return a string, or NULL in case of error. + */ +const char *proj_obj_as_proj_string(PJ_OBJ *obj, PJ_PROJ_STRING_TYPE type, + const char *const *options) { + assert(obj); + auto exportable = + dynamic_cast<const IPROJStringExportable *>(obj->obj.get()); + if (!exportable) { + proj_log_error(obj->ctx, __FUNCTION__, + "Object type not exportable to PROJ"); + return nullptr; + } + // Make sure that the C and C++ enumeration match + static_assert(static_cast<int>(PJ_PROJ_5) == + static_cast<int>(PROJStringFormatter::Convention::PROJ_5), + ""); + static_assert(static_cast<int>(PJ_PROJ_4) == + static_cast<int>(PROJStringFormatter::Convention::PROJ_4), + ""); + // Make sure we enumerate all values. If adding a new value, as we + // don't have a default clause, the compiler will warn. + switch (type) { + case PJ_PROJ_5: + case PJ_PROJ_4: + break; + } + const PROJStringFormatter::Convention convention = + static_cast<PROJStringFormatter::Convention>(type); + auto dbContext = getDBcontextNoException(obj->ctx, __FUNCTION__); + try { + auto formatter = PROJStringFormatter::create(convention, dbContext); + if (options != nullptr && options[0] != nullptr) { + if (ci_equal(options[0], "USE_ETMERC=YES")) { + formatter->setUseETMercForTMerc(true); + } + } + obj->lastPROJString = exportable->exportToPROJString(formatter.get()); + return obj->lastPROJString.c_str(); + } catch (const std::exception &e) { + proj_log_error(obj->ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Return the area of use of an object. + * + * @param obj Object (must not be NULL) + * @param p_west_lon Pointer to a double to receive the west longitude (in + * degrees). Or NULL. If the returned value is -1000, the bounding box is + * unknown. + * @param p_south_lat Pointer to a double to receive the south latitude (in + * degrees). Or NULL. If the returned value is -1000, the bounding box is + * unknown. + * @param p_east_lon Pointer to a double to receive the east longitude (in + * degrees). Or NULL. If the returned value is -1000, the bounding box is + * unknown. + * @param p_north_lat Pointer to a double to receive the north latitude (in + * degrees). Or NULL. If the returned value is -1000, the bounding box is + * unknown. + * @param p_area_name Pointer to a string to receive the name of the area of + * use. Or NULL. *p_area_name is valid while obj is valid itself. + * @return TRUE in case of success, FALSE in case of error or if the area + * of use is unknown. + */ +int proj_obj_get_area_of_use(PJ_OBJ *obj, double *p_west_lon, + double *p_south_lat, double *p_east_lon, + double *p_north_lat, const char **p_area_name) { + if (p_area_name) { + *p_area_name = nullptr; + } + auto objectUsage = dynamic_cast<const ObjectUsage *>(obj->obj.get()); + if (!objectUsage) { + return false; + } + const auto &domains = objectUsage->domains(); + if (domains.empty()) { + return false; + } + const auto &extent = domains[0]->domainOfValidity(); + if (!extent) { + return false; + } + const auto &desc = extent->description(); + if (desc.has_value() && p_area_name) { + *p_area_name = desc->c_str(); + } + + const auto &geogElements = extent->geographicElements(); + if (!geogElements.empty()) { + auto bbox = + dynamic_cast<const GeographicBoundingBox *>(geogElements[0].get()); + if (bbox) { + if (p_west_lon) { + *p_west_lon = bbox->westBoundLongitude(); + } + if (p_south_lat) { + *p_south_lat = bbox->southBoundLatitude(); + } + if (p_east_lon) { + *p_east_lon = bbox->eastBoundLongitude(); + } + if (p_north_lat) { + *p_north_lat = bbox->northBoundLatitude(); + } + return true; + } + } + if (p_west_lon) { + *p_west_lon = -1000; + } + if (p_south_lat) { + *p_south_lat = -1000; + } + if (p_east_lon) { + *p_east_lon = -1000; + } + if (p_north_lat) { + *p_north_lat = -1000; + } + return true; +} + +// --------------------------------------------------------------------------- + +static const GeodeticCRS *extractGeodeticCRS(PJ_OBJ *crs, const char *fname) { + assert(crs); + auto l_crs = dynamic_cast<const CRS *>(crs->obj.get()); + if (!l_crs) { + proj_log_error(crs->ctx, fname, "Object is not a CRS"); + return nullptr; + } + auto geodCRS = l_crs->extractGeodeticCRSRaw(); + if (!geodCRS) { + proj_log_error(crs->ctx, fname, "CRS has no geodetic CRS"); + } + return geodCRS; +} + +// --------------------------------------------------------------------------- + +/** \brief Get the geodeticCRS / geographicCRS from a CRS + * + * The returned object must be unreferenced with proj_obj_unref() after + * use. + * It should be used by at most one thread at a time. + * + * @param crs Objet of type CRS (must not be NULL) + * @return Object that must be unreferenced with proj_obj_unref(), or NULL + * in case of error. + */ +PJ_OBJ *proj_obj_crs_get_geodetic_crs(PJ_OBJ *crs) { + auto geodCRS = extractGeodeticCRS(crs, __FUNCTION__); + if (!geodCRS) { + return nullptr; + } + return PJ_OBJ::create(crs->ctx, + NN_NO_CHECK(nn_dynamic_pointer_cast<IdentifiedObject>( + geodCRS->shared_from_this()))); +} + +// --------------------------------------------------------------------------- + +/** \brief Get a CRS component from a CompoundCRS + * + * The returned object must be unreferenced with proj_obj_unref() after + * use. + * It should be used by at most one thread at a time. + * + * @param crs Objet of type CRS (must not be NULL) + * @param index Index of the CRS component (typically 0 = horizontal, 1 = + * vertical) + * @return Object that must be unreferenced with proj_obj_unref(), or NULL + * in case of error. + */ +PJ_OBJ *proj_obj_crs_get_sub_crs(PJ_OBJ *crs, int index) { + assert(crs); + auto l_crs = dynamic_cast<CompoundCRS *>(crs->obj.get()); + if (!l_crs) { + proj_log_error(crs->ctx, __FUNCTION__, "Object is not a CompoundCRS"); + return nullptr; + } + const auto &components = l_crs->componentReferenceSystems(); + if (static_cast<size_t>(index) >= components.size()) { + return nullptr; + } + return PJ_OBJ::create(crs->ctx, components[index]); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns potentially + * a BoundCRS, with a transformation to EPSG:4326, wrapping this CRS + * + * The returned object must be unreferenced with proj_obj_unref() after + * use. + * It should be used by at most one thread at a time. + * + * This is the same as method + * osgeo::proj::crs::CRS::createBoundCRSToWGS84IfPossible() + * + * @param crs Objet of type CRS (must not be NULL) + * @return Object that must be unreferenced with proj_obj_unref(), or NULL + * in case of error. + */ +PJ_OBJ *proj_obj_crs_create_bound_crs_to_WGS84(PJ_OBJ *crs) { + assert(crs); + auto l_crs = dynamic_cast<const CRS *>(crs->obj.get()); + if (!l_crs) { + proj_log_error(crs->ctx, __FUNCTION__, "Object is not a CRS"); + return nullptr; + } + auto dbContext = getDBcontextNoException(crs->ctx, __FUNCTION__); + return PJ_OBJ::create(crs->ctx, + l_crs->createBoundCRSToWGS84IfPossible(dbContext)); +} + +// --------------------------------------------------------------------------- + +/** \brief Get the ellipsoid from a CRS or a GeodeticReferenceFrame. + * + * The returned object must be unreferenced with proj_obj_unref() after + * use. + * It should be used by at most one thread at a time. + * + * @param obj Objet of type CRS or GeodeticReferenceFrame (must not be NULL) + * @return Object that must be unreferenced with proj_obj_unref(), or NULL + * in case of error. + */ +PJ_OBJ *proj_obj_get_ellipsoid(PJ_OBJ *obj) { + auto ptr = obj->obj.get(); + if (dynamic_cast<const CRS *>(ptr)) { + auto geodCRS = extractGeodeticCRS(obj, __FUNCTION__); + if (geodCRS) { + return PJ_OBJ::create(obj->ctx, geodCRS->ellipsoid()); + } + } else { + auto datum = dynamic_cast<const GeodeticReferenceFrame *>(ptr); + if (datum) { + return PJ_OBJ::create(obj->ctx, datum->ellipsoid()); + } + } + proj_log_error(obj->ctx, __FUNCTION__, + "Object is not a CRS or GeodeticReferenceFrame"); + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Get the horizontal datum from a CRS + * + * The returned object must be unreferenced with proj_obj_unref() after + * use. + * It should be used by at most one thread at a time. + * + * @param crs Objet of type CRS (must not be NULL) + * @return Object that must be unreferenced with proj_obj_unref(), or NULL + * in case of error. + */ +PJ_OBJ *proj_obj_crs_get_horizontal_datum(PJ_OBJ *crs) { + auto geodCRS = extractGeodeticCRS(crs, __FUNCTION__); + if (!geodCRS) { + return nullptr; + } + const auto &datum = geodCRS->datum(); + if (datum) { + return PJ_OBJ::create(crs->ctx, NN_NO_CHECK(datum)); + } + + const auto &datumEnsemble = geodCRS->datumEnsemble(); + if (datumEnsemble) { + return PJ_OBJ::create(crs->ctx, NN_NO_CHECK(datumEnsemble)); + } + proj_log_error(crs->ctx, __FUNCTION__, "CRS has no datum"); + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return ellipsoid parameters. + * + * @param ellipsoid Object of type Ellipsoid (must not be NULL) + * @param pSemiMajorMetre Pointer to a value to store the semi-major axis in + * metre. or NULL + * @param pSemiMinorMetre Pointer to a value to store the semi-minor axis in + * metre. or NULL + * @param pIsSemiMinorComputed Pointer to a boolean value to indicate if the + * semi-minor value was computed. If FALSE, its value comes from the + * definition. or NULL + * @param pInverseFlattening Pointer to a value to store the inverse + * flattening. or NULL + * @return TRUE in case of success. + */ +int proj_obj_ellipsoid_get_parameters(PJ_OBJ *ellipsoid, + double *pSemiMajorMetre, + double *pSemiMinorMetre, + int *pIsSemiMinorComputed, + double *pInverseFlattening) { + assert(ellipsoid); + auto l_ellipsoid = dynamic_cast<const Ellipsoid *>(ellipsoid->obj.get()); + if (!l_ellipsoid) { + proj_log_error(ellipsoid->ctx, __FUNCTION__, + "Object is not a Ellipsoid"); + return FALSE; + } + + if (pSemiMajorMetre) { + *pSemiMajorMetre = l_ellipsoid->semiMajorAxis().getSIValue(); + } + if (pSemiMinorMetre) { + *pSemiMinorMetre = l_ellipsoid->computeSemiMinorAxis().getSIValue(); + } + if (pIsSemiMinorComputed) { + *pIsSemiMinorComputed = !(l_ellipsoid->semiMinorAxis().has_value()); + } + if (pInverseFlattening) { + *pInverseFlattening = l_ellipsoid->computedInverseFlattening(); + } + return TRUE; +} + +// --------------------------------------------------------------------------- + +/** \brief Get the prime meridian of a CRS or a GeodeticReferenceFrame. + * + * The returned object must be unreferenced with proj_obj_unref() after + * use. + * It should be used by at most one thread at a time. + * + * @param obj Objet of type CRS or GeodeticReferenceFrame (must not be NULL) + * @return Object that must be unreferenced with proj_obj_unref(), or NULL + * in case of error. + */ + +PJ_OBJ *proj_obj_get_prime_meridian(PJ_OBJ *obj) { + auto ptr = obj->obj.get(); + if (dynamic_cast<CRS *>(ptr)) { + auto geodCRS = extractGeodeticCRS(obj, __FUNCTION__); + if (geodCRS) { + return PJ_OBJ::create(obj->ctx, geodCRS->primeMeridian()); + } + } else { + auto datum = dynamic_cast<const GeodeticReferenceFrame *>(ptr); + if (datum) { + return PJ_OBJ::create(obj->ctx, datum->primeMeridian()); + } + } + proj_log_error(obj->ctx, __FUNCTION__, + "Object is not a CRS or GeodeticReferenceFrame"); + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return prime meridian parameters. + * + * @param prime_meridian Object of type PrimeMeridian (must not be NULL) + * @param pLongitude Pointer to a value to store the longitude of the prime + * meridian, in its native unit. or NULL + * @param pLongitudeUnitConvFactor Pointer to a value to store the conversion + * factor of the prime meridian longitude unit to radian. or NULL + * @param pLongitudeUnitName Pointer to a string value to store the unit name. + * or NULL + * @return TRUE in case of success. + */ +int proj_obj_prime_meridian_get_parameters(PJ_OBJ *prime_meridian, + double *pLongitude, + double *pLongitudeUnitConvFactor, + const char **pLongitudeUnitName) { + assert(prime_meridian); + auto l_pm = dynamic_cast<const PrimeMeridian *>(prime_meridian->obj.get()); + if (!l_pm) { + proj_log_error(prime_meridian->ctx, __FUNCTION__, + "Object is not a PrimeMeridian"); + return false; + } + const auto &longitude = l_pm->longitude(); + if (pLongitude) { + *pLongitude = longitude.value(); + } + const auto &unit = longitude.unit(); + if (pLongitudeUnitConvFactor) { + *pLongitudeUnitConvFactor = unit.conversionToSI(); + } + if (pLongitudeUnitName) { + *pLongitudeUnitName = unit.name().c_str(); + } + return true; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS of a BoundCRS or the source CRS of a + * CoordinateOperation. + * + * The returned object must be unreferenced with proj_obj_unref() after + * use. + * It should be used by at most one thread at a time. + * + * @param obj Objet of type BoundCRS or CoordinateOperation (must not be NULL) + * @return Object that must be unreferenced with proj_obj_unref(), or NULL + * in case of error, or missing source CRS. + */ +PJ_OBJ *proj_obj_get_source_crs(PJ_OBJ *obj) { + assert(obj); + auto ptr = obj->obj.get(); + auto boundCRS = dynamic_cast<const BoundCRS *>(ptr); + if (boundCRS) { + return PJ_OBJ::create(obj->ctx, boundCRS->baseCRS()); + } + auto co = dynamic_cast<const CoordinateOperation *>(ptr); + if (co) { + auto sourceCRS = co->sourceCRS(); + if (sourceCRS) { + return PJ_OBJ::create(obj->ctx, NN_NO_CHECK(sourceCRS)); + } + return nullptr; + } + proj_log_error(obj->ctx, __FUNCTION__, + "Object is not a BoundCRS or a CoordinateOperation"); + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the hub CRS of a BoundCRS or the target CRS of a + * CoordinateOperation. + * + * The returned object must be unreferenced with proj_obj_unref() after + * use. + * It should be used by at most one thread at a time. + * + * @param obj Objet of type BoundCRS or CoordinateOperation (must not be NULL) + * @return Object that must be unreferenced with proj_obj_unref(), or NULL + * in case of error, or missing target CRS. + */ +PJ_OBJ *proj_obj_get_target_crs(PJ_OBJ *obj) { + assert(obj); + auto ptr = obj->obj.get(); + auto boundCRS = dynamic_cast<const BoundCRS *>(ptr); + if (boundCRS) { + return PJ_OBJ::create(obj->ctx, boundCRS->hubCRS()); + } + auto co = dynamic_cast<const CoordinateOperation *>(ptr); + if (co) { + auto targetCRS = co->targetCRS(); + if (targetCRS) { + return PJ_OBJ::create(obj->ctx, NN_NO_CHECK(targetCRS)); + } + return nullptr; + } + proj_log_error(obj->ctx, __FUNCTION__, + "Object is not a BoundCRS or a CoordinateOperation"); + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are either hard-coded, or looked in the database when + * authorityFactory is not null. + * + * 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 + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent), but the names do not match at all. + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * Other confidence values may be returned by some specialized implementations. + * + * This is implemented for GeodeticCRS, ProjectedCRS, VerticalCRS and + * CompoundCRS. + * + * @param obj Object of type CRS. Must not be NULL + * @param auth_name Authority name, or NULL for all authorities + * @param options Placeholder for future options. Should be set to NULL. + * @param confidence Output parameter. Pointer to an array of integers that will + * be allocated by the function and filled with the confidence values + * (0-100). There are as many elements in this array as + * proj_obj_list_get_count() + * returns on the return value of this function. *confidence should be + * released with proj_free_int_list(). + * @return a list of matching reference CRS, or nullptr in case of error. + */ +PJ_OBJ_LIST *proj_obj_identify(PJ_OBJ *obj, const char *auth_name, + const char *const *options, int **confidence) { + assert(obj); + (void)options; + if (confidence) { + *confidence = nullptr; + } + auto ptr = obj->obj.get(); + auto crs = dynamic_cast<const CRS *>(ptr); + if (!crs) { + proj_log_error(obj->ctx, __FUNCTION__, "Object is not a CRS"); + } else { + int *confidenceTemp = nullptr; + try { + auto factory = AuthorityFactory::create(getDBcontext(obj->ctx), + auth_name ? auth_name : ""); + auto res = crs->identify(factory); + std::vector<IdentifiedObjectNNPtr> objects; + confidenceTemp = confidence ? new int[res.size()] : nullptr; + size_t i = 0; + for (const auto &pair : res) { + objects.push_back(pair.first); + if (confidenceTemp) { + confidenceTemp[i] = pair.second; + ++i; + } + } + auto ret = internal::make_unique<PJ_OBJ_LIST>(obj->ctx, + std::move(objects)); + if (confidence) { + *confidence = confidenceTemp; + confidenceTemp = nullptr; + } + return ret.release(); + } catch (const std::exception &e) { + delete[] confidenceTemp; + proj_log_error(obj->ctx, __FUNCTION__, e.what()); + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Free an array of integer. */ +void proj_free_int_list(int *list) { delete[] list; } + +// --------------------------------------------------------------------------- + +static PROJ_STRING_LIST set_to_string_list(std::set<std::string> &&set) { + auto ret = new char *[set.size() + 1]; + size_t i = 0; + for (const auto &str : set) { + ret[i] = new char[str.size() + 1]; + std::memcpy(ret[i], str.c_str(), str.size() + 1); + i++; + } + ret[i] = nullptr; + return ret; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the list of authorities used in the database. + * + * The returned list is NULL terminated and must be freed with + * proj_free_string_list(). + * + * @param ctx PROJ context, or NULL for default context + * + * @return a NULL terminated list of NUL-terminated strings that must be + * freed with proj_free_string_list(), or NULL in case of error. + */ +PROJ_STRING_LIST proj_get_authorities_from_database(PJ_CONTEXT *ctx) { + SANITIZE_CTX(ctx); + try { + return set_to_string_list(getDBcontext(ctx)->getAuthorities()); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the set of authority codes of the given object type. + * + * The returned list is NULL terminated and must be freed with + * proj_free_string_list(). + * + * @param ctx PROJ context, or NULL for default context. + * @param auth_name Authority name (must not be NULL) + * @param type Object type. + * @param allow_deprecated whether we should return deprecated objects as well. + * + * @return a NULL terminated list of NUL-terminated strings that must be + * freed with proj_free_string_list(), or NULL in case of error. + */ +PROJ_STRING_LIST proj_get_codes_from_database(PJ_CONTEXT *ctx, + const char *auth_name, + PJ_OBJ_TYPE type, + int allow_deprecated) { + assert(auth_name); + SANITIZE_CTX(ctx); + try { + auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name); + bool valid = false; + auto typeInternal = convertPJObjectTypeToObjectType(type, valid); + if (!valid) { + return nullptr; + } + return set_to_string_list( + factory->getAuthorityCodes(typeInternal, allow_deprecated != 0)); + + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** Free a list of NULL terminated strings. */ +void proj_free_string_list(PROJ_STRING_LIST list) { + if (list) { + for (size_t i = 0; list[i] != nullptr; i++) { + 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 + * + * The returned object must be unreferenced with proj_obj_unref() after + * use. + * It should be used by at most one thread at a time. + * + * @param crs Objet of type DerivedCRS or BoundCRSs (must not be NULL) + * @param pMethodName Pointer to a string value to store the method + * (projection) name. or NULL + * @param pMethodAuthorityName Pointer to a string value to store the method + * authority name. or NULL + * @param pMethodCode Pointer to a string value to store the method + * code. or NULL + * @return Object of type SingleOperation that must be unreferenced with + * proj_obj_unref(), or NULL in case of error. + */ +PJ_OBJ *proj_obj_crs_get_coordoperation(PJ_OBJ *crs, const char **pMethodName, + const char **pMethodAuthorityName, + const char **pMethodCode) { + assert(crs); + SingleOperationPtr co; + + auto derivedCRS = dynamic_cast<const DerivedCRS *>(crs->obj.get()); + if (derivedCRS) { + co = derivedCRS->derivingConversion().as_nullable(); + } else { + auto boundCRS = dynamic_cast<const BoundCRS *>(crs->obj.get()); + if (boundCRS) { + co = boundCRS->transformation().as_nullable(); + } else { + proj_log_error(crs->ctx, __FUNCTION__, + "Object is not a DerivedCRS or BoundCRS"); + return nullptr; + } + } + + const auto &method = co->method(); + const auto &method_ids = method->identifiers(); + if (pMethodName) { + *pMethodName = method->name()->description()->c_str(); + } + if (pMethodAuthorityName) { + if (!method_ids.empty()) { + *pMethodAuthorityName = method_ids[0]->codeSpace()->c_str(); + } else { + *pMethodAuthorityName = nullptr; + } + } + if (pMethodCode) { + if (!method_ids.empty()) { + *pMethodCode = method_ids[0]->code().c_str(); + } else { + *pMethodCode = nullptr; + } + } + return PJ_OBJ::create(crs->ctx, NN_NO_CHECK(co)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static PropertyMap createPropertyMapName(const char *name) { + return PropertyMap().set(common::IdentifiedObject::NAME_KEY, + name ? name : "unnamed"); +} + +// --------------------------------------------------------------------------- + +static UnitOfMeasure createLinearUnit(const char *name, double convFactor) { + return name == nullptr ? UnitOfMeasure::METRE + : UnitOfMeasure(name, convFactor); +} + +// --------------------------------------------------------------------------- + +static UnitOfMeasure createAngularUnit(const char *name, double convFactor) { + return name ? (ci_equal(name, "degree") + ? UnitOfMeasure::DEGREE + : ci_equal(name, "grad") + ? UnitOfMeasure::GRAD + : UnitOfMeasure(name, convFactor)) + : UnitOfMeasure::DEGREE; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Create a GeographicCRS 2D from its definition. + * + * The returned object must be unreferenced with proj_obj_unref() after + * use. + * It should be used by at most one thread at a time. + * + * @param ctx PROJ context, or NULL for default context + * @param geogName Name of the GeographicCRS. Or NULL + * @param datumName Name of the GeodeticReferenceFrame. Or NULL + * @param ellipsoidName Name of the Ellipsoid. Or NULL + * @param semiMajorMetre Ellipsoid semi-major axis, in metres. + * @param invFlattening Ellipsoid inverse flattening. Or 0 for a sphere. + * @param primeMeridianName Name of the PrimeMeridian. Or NULL + * @param primeMeridianOffset Offset of the prime meridian, expressed in the + * specified angular units. + * @param angularUnits Name of the angular units. Or NULL for Degree + * @param angularUnitsConv Conversion factor from the angular unit to radian. Or + * 0 for Degree if angularUnits == NULL. Otherwise should be not NULL + * @param latLongOrder TRUE for Latitude Longitude axis order. + * + * @return Object of type GeographicCRS that must be unreferenced with + * proj_obj_unref(), or NULL in case of error. + */ +PJ_OBJ *proj_obj_create_geographic_crs( + PJ_CONTEXT *ctx, const char *geogName, const char *datumName, + const char *ellipsoidName, double semiMajorMetre, double invFlattening, + const char *primeMeridianName, double primeMeridianOffset, + const char *angularUnits, double angularUnitsConv, int latLongOrder) { + + SANITIZE_CTX(ctx); + try { + const UnitOfMeasure angUnit( + createAngularUnit(angularUnits, angularUnitsConv)); + auto dbContext = getDBcontext(ctx); + auto body = Ellipsoid::guessBodyName(dbContext, semiMajorMetre); + auto ellpsName = createPropertyMapName(ellipsoidName); + auto ellps = + invFlattening != 0.0 + ? Ellipsoid::createFlattenedSphere(ellpsName, + Length(semiMajorMetre), + Scale(invFlattening), body) + : Ellipsoid::createSphere(ellpsName, Length(semiMajorMetre), + body); + auto pm = PrimeMeridian::create( + PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + primeMeridianName + ? primeMeridianName + : primeMeridianOffset == 0.0 + ? (ellps->celestialBody() == Ellipsoid::EARTH + ? "Greenwich" + : "Reference meridian") + : "unnamed"), + Angle(primeMeridianOffset, angUnit)); + auto datum = GeodeticReferenceFrame::create( + createPropertyMapName(datumName), ellps, + util::optional<std::string>(), pm); + auto geogCRS = GeographicCRS::create( + createPropertyMapName(geogName), datum, + latLongOrder ? cs::EllipsoidalCS::createLatitudeLongitude(angUnit) + : cs::EllipsoidalCS::createLongitudeLatitude(angUnit)); + return PJ_OBJ::create(ctx, geogCRS); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static PJ_OBJ *proj_obj_create_projected_crs(PJ_OBJ *geodetic_crs, + const char *crs_name, + const ConversionNNPtr &conv, + const UnitOfMeasure &linearUnit) { + assert(geodetic_crs); + auto geogCRS = + util::nn_dynamic_pointer_cast<GeodeticCRS>(geodetic_crs->obj); + if (!geogCRS) { + return nullptr; + } + auto crs = ProjectedCRS::create( + createPropertyMapName(crs_name), NN_NO_CHECK(geogCRS), conv, + cs::CartesianCS::createEastingNorthing(linearUnit)); + return PJ_OBJ::create(geodetic_crs->ctx, crs); +} + +//! @endcond + +/* BEGIN: Generated by scripts/create_c_api_projections.py*/ + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a Universal Transverse Mercator + * conversion. + * + * See osgeo::proj::operation::Conversion::createUTM(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_UTM(PJ_OBJ *geodetic_crs, + const char *crs_name, int zone, + int north) { + const auto &linearUnit = UnitOfMeasure::METRE; + auto conv = Conversion::createUTM(PropertyMap(), zone, north != 0); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Transverse + * Mercator projection method. + * + * See osgeo::proj::operation::Conversion::createTransverseMercator(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_TransverseMercator( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double scale, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createTransverseMercator( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Scale(scale), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Gauss + * Schreiber Transverse Mercator projection method. + * + * See + * osgeo::proj::operation::Conversion::createGaussSchreiberTransverseMercator(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_GaussSchreiberTransverseMercator( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double scale, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createGaussSchreiberTransverseMercator( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Scale(scale), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Transverse + * Mercator South Orientated projection method. + * + * See + * osgeo::proj::operation::Conversion::createTransverseMercatorSouthOriented(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_TransverseMercatorSouthOriented( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double scale, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createTransverseMercatorSouthOriented( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Scale(scale), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Two Point + * Equidistant projection method. + * + * See osgeo::proj::operation::Conversion::createTwoPointEquidistant(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_TwoPointEquidistant( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeFirstPoint, + double longitudeFirstPoint, double latitudeSecondPoint, + double longitudeSeconPoint, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createTwoPointEquidistant( + PropertyMap(), Angle(latitudeFirstPoint, angUnit), + Angle(longitudeFirstPoint, angUnit), + Angle(latitudeSecondPoint, angUnit), + Angle(longitudeSeconPoint, angUnit), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Tunisia + * Mapping Grid projection method. + * + * See osgeo::proj::operation::Conversion::createTunisiaMappingGrid(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_TunisiaMappingGrid( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createTunisiaMappingGrid( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Albers + * Conic Equal Area projection method. + * + * See osgeo::proj::operation::Conversion::createAlbersEqualArea(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_AlbersEqualArea( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeFalseOrigin, + double longitudeFalseOrigin, double latitudeFirstParallel, + double latitudeSecondParallel, double eastingFalseOrigin, + double northingFalseOrigin, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createAlbersEqualArea( + PropertyMap(), Angle(latitudeFalseOrigin, angUnit), + Angle(longitudeFalseOrigin, angUnit), + Angle(latitudeFirstParallel, angUnit), + Angle(latitudeSecondParallel, angUnit), + Length(eastingFalseOrigin, linearUnit), + Length(northingFalseOrigin, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Lambert + * Conic Conformal 1SP projection method. + * + * See osgeo::proj::operation::Conversion::createLambertConicConformal_1SP(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_LambertConicConformal_1SP( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double scale, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createLambertConicConformal_1SP( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Scale(scale), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Lambert + * Conic Conformal (2SP) projection method. + * + * See osgeo::proj::operation::Conversion::createLambertConicConformal_2SP(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_LambertConicConformal_2SP( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeFalseOrigin, + double longitudeFalseOrigin, double latitudeFirstParallel, + double latitudeSecondParallel, double eastingFalseOrigin, + double northingFalseOrigin, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createLambertConicConformal_2SP( + PropertyMap(), Angle(latitudeFalseOrigin, angUnit), + Angle(longitudeFalseOrigin, angUnit), + Angle(latitudeFirstParallel, angUnit), + Angle(latitudeSecondParallel, angUnit), + Length(eastingFalseOrigin, linearUnit), + Length(northingFalseOrigin, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Lambert + * Conic Conformal (2SP Michigan) projection method. + * + * See + * osgeo::proj::operation::Conversion::createLambertConicConformal_2SP_Michigan(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_LambertConicConformal_2SP_Michigan( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeFalseOrigin, + double longitudeFalseOrigin, double latitudeFirstParallel, + double latitudeSecondParallel, double eastingFalseOrigin, + double northingFalseOrigin, double ellipsoidScalingFactor, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createLambertConicConformal_2SP_Michigan( + PropertyMap(), Angle(latitudeFalseOrigin, angUnit), + Angle(longitudeFalseOrigin, angUnit), + Angle(latitudeFirstParallel, angUnit), + Angle(latitudeSecondParallel, angUnit), + Length(eastingFalseOrigin, linearUnit), + Length(northingFalseOrigin, linearUnit), Scale(ellipsoidScalingFactor)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Lambert + * Conic Conformal (2SP Belgium) projection method. + * + * See + * osgeo::proj::operation::Conversion::createLambertConicConformal_2SP_Belgium(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_LambertConicConformal_2SP_Belgium( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeFalseOrigin, + double longitudeFalseOrigin, double latitudeFirstParallel, + double latitudeSecondParallel, double eastingFalseOrigin, + double northingFalseOrigin, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createLambertConicConformal_2SP_Belgium( + PropertyMap(), Angle(latitudeFalseOrigin, angUnit), + Angle(longitudeFalseOrigin, angUnit), + Angle(latitudeFirstParallel, angUnit), + Angle(latitudeSecondParallel, angUnit), + Length(eastingFalseOrigin, linearUnit), + Length(northingFalseOrigin, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Modified + * Azimuthal Equidistant projection method. + * + * See osgeo::proj::operation::Conversion::createAzimuthalEquidistant(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_AzimuthalEquidistant( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeNatOrigin, + double longitudeNatOrigin, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createAzimuthalEquidistant( + PropertyMap(), Angle(latitudeNatOrigin, angUnit), + Angle(longitudeNatOrigin, angUnit), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Guam + * Projection projection method. + * + * See osgeo::proj::operation::Conversion::createGuamProjection(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_GuamProjection( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeNatOrigin, + double longitudeNatOrigin, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createGuamProjection( + PropertyMap(), Angle(latitudeNatOrigin, angUnit), + Angle(longitudeNatOrigin, angUnit), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Bonne + * projection method. + * + * See osgeo::proj::operation::Conversion::createBonne(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_Bonne( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeNatOrigin, + double longitudeNatOrigin, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createBonne( + PropertyMap(), Angle(latitudeNatOrigin, angUnit), + Angle(longitudeNatOrigin, angUnit), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Lambert + * Cylindrical Equal Area (Spherical) projection method. + * + * See + * osgeo::proj::operation::Conversion::createLambertCylindricalEqualAreaSpherical(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_LambertCylindricalEqualAreaSpherical( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeFirstParallel, + double longitudeNatOrigin, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createLambertCylindricalEqualAreaSpherical( + PropertyMap(), Angle(latitudeFirstParallel, angUnit), + Angle(longitudeNatOrigin, angUnit), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Lambert + * Cylindrical Equal Area (ellipsoidal form) projection method. + * + * See osgeo::proj::operation::Conversion::createLambertCylindricalEqualArea(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_LambertCylindricalEqualArea( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeFirstParallel, + double longitudeNatOrigin, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createLambertCylindricalEqualArea( + PropertyMap(), Angle(latitudeFirstParallel, angUnit), + Angle(longitudeNatOrigin, angUnit), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the + * Cassini-Soldner projection method. + * + * See osgeo::proj::operation::Conversion::createCassiniSoldner(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_CassiniSoldner( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createCassiniSoldner( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Equidistant + * Conic projection method. + * + * See osgeo::proj::operation::Conversion::createEquidistantConic(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_EquidistantConic( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double latitudeFirstParallel, + double latitudeSecondParallel, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createEquidistantConic( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Angle(latitudeFirstParallel, angUnit), + Angle(latitudeSecondParallel, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Eckert I + * projection method. + * + * See osgeo::proj::operation::Conversion::createEckertI(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_EckertI( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createEckertI( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Eckert II + * projection method. + * + * See osgeo::proj::operation::Conversion::createEckertII(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_EckertII( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createEckertII( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Eckert III + * projection method. + * + * See osgeo::proj::operation::Conversion::createEckertIII(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_EckertIII( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createEckertIII( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Eckert IV + * projection method. + * + * See osgeo::proj::operation::Conversion::createEckertIV(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_EckertIV( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createEckertIV( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Eckert V + * projection method. + * + * See osgeo::proj::operation::Conversion::createEckertV(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_EckertV( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createEckertV( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Eckert VI + * projection method. + * + * See osgeo::proj::operation::Conversion::createEckertVI(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_EckertVI( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createEckertVI( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Equidistant + * Cylindrical projection method. + * + * See osgeo::proj::operation::Conversion::createEquidistantCylindrical(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_EquidistantCylindrical( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeFirstParallel, + double longitudeNatOrigin, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createEquidistantCylindrical( + PropertyMap(), Angle(latitudeFirstParallel, angUnit), + Angle(longitudeNatOrigin, angUnit), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Equidistant + * Cylindrical (Spherical) projection method. + * + * See + * osgeo::proj::operation::Conversion::createEquidistantCylindricalSpherical(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_EquidistantCylindricalSpherical( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeFirstParallel, + double longitudeNatOrigin, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createEquidistantCylindricalSpherical( + PropertyMap(), Angle(latitudeFirstParallel, angUnit), + Angle(longitudeNatOrigin, angUnit), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Gall + * (Stereographic) projection method. + * + * See osgeo::proj::operation::Conversion::createGall(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_Gall( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createGall( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Goode + * Homolosine projection method. + * + * See osgeo::proj::operation::Conversion::createGoodeHomolosine(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_GoodeHomolosine( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createGoodeHomolosine( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Interrupted + * Goode Homolosine projection method. + * + * See osgeo::proj::operation::Conversion::createInterruptedGoodeHomolosine(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_InterruptedGoodeHomolosine( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createInterruptedGoodeHomolosine( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the + * Geostationary Satellite View projection method, with the sweep angle axis of + * the viewing instrument being x. + * + * See osgeo::proj::operation::Conversion::createGeostationarySatelliteSweepX(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_GeostationarySatelliteSweepX( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double height, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createGeostationarySatelliteSweepX( + PropertyMap(), Angle(centerLong, angUnit), Length(height, linearUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the + * Geostationary Satellite View projection method, with the sweep angle axis of + * the viewing instrument being y. + * + * See osgeo::proj::operation::Conversion::createGeostationarySatelliteSweepY(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_GeostationarySatelliteSweepY( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double height, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createGeostationarySatelliteSweepY( + PropertyMap(), Angle(centerLong, angUnit), Length(height, linearUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Gnomonic + * projection method. + * + * See osgeo::proj::operation::Conversion::createGnomonic(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_Gnomonic( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createGnomonic( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Hotine + * Oblique Mercator (Variant A) projection method. + * + * See + * osgeo::proj::operation::Conversion::createHotineObliqueMercatorVariantA(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_HotineObliqueMercatorVariantA( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeProjectionCentre, + double longitudeProjectionCentre, double azimuthInitialLine, + double angleFromRectifiedToSkrewGrid, double scale, double falseEasting, + double falseNorthing, const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createHotineObliqueMercatorVariantA( + PropertyMap(), Angle(latitudeProjectionCentre, angUnit), + Angle(longitudeProjectionCentre, angUnit), + Angle(azimuthInitialLine, angUnit), + Angle(angleFromRectifiedToSkrewGrid, angUnit), Scale(scale), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Hotine + * Oblique Mercator (Variant B) projection method. + * + * See + * osgeo::proj::operation::Conversion::createHotineObliqueMercatorVariantB(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_HotineObliqueMercatorVariantB( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeProjectionCentre, + double longitudeProjectionCentre, double azimuthInitialLine, + double angleFromRectifiedToSkrewGrid, double scale, + double eastingProjectionCentre, double northingProjectionCentre, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createHotineObliqueMercatorVariantB( + PropertyMap(), Angle(latitudeProjectionCentre, angUnit), + Angle(longitudeProjectionCentre, angUnit), + Angle(azimuthInitialLine, angUnit), + Angle(angleFromRectifiedToSkrewGrid, angUnit), Scale(scale), + Length(eastingProjectionCentre, linearUnit), + Length(northingProjectionCentre, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Hotine + * Oblique Mercator Two Point Natural Origin projection method. + * + * See + * osgeo::proj::operation::Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ * +proj_obj_create_projected_crs_HotineObliqueMercatorTwoPointNaturalOrigin( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeProjectionCentre, + double latitudePoint1, double longitudePoint1, double latitudePoint2, + double longitudePoint2, double scale, double eastingProjectionCentre, + double northingProjectionCentre, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin( + PropertyMap(), Angle(latitudeProjectionCentre, angUnit), + Angle(latitudePoint1, angUnit), Angle(longitudePoint1, angUnit), + Angle(latitudePoint2, angUnit), Angle(longitudePoint2, angUnit), + Scale(scale), Length(eastingProjectionCentre, linearUnit), + Length(northingProjectionCentre, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the + * International Map of the World Polyconic projection method. + * + * See + * osgeo::proj::operation::Conversion::createInternationalMapWorldPolyconic(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_InternationalMapWorldPolyconic( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double latitudeFirstParallel, double latitudeSecondParallel, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createInternationalMapWorldPolyconic( + PropertyMap(), Angle(centerLong, angUnit), + Angle(latitudeFirstParallel, angUnit), + Angle(latitudeSecondParallel, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Krovak + * (north oriented) projection method. + * + * See osgeo::proj::operation::Conversion::createKrovakNorthOriented(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_KrovakNorthOriented( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeProjectionCentre, + double longitudeOfOrigin, double colatitudeConeAxis, + double latitudePseudoStandardParallel, + double scaleFactorPseudoStandardParallel, double falseEasting, + double falseNorthing, const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createKrovakNorthOriented( + PropertyMap(), Angle(latitudeProjectionCentre, angUnit), + Angle(longitudeOfOrigin, angUnit), Angle(colatitudeConeAxis, angUnit), + Angle(latitudePseudoStandardParallel, angUnit), + Scale(scaleFactorPseudoStandardParallel), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Krovak + * projection method. + * + * See osgeo::proj::operation::Conversion::createKrovak(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_Krovak( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeProjectionCentre, + double longitudeOfOrigin, double colatitudeConeAxis, + double latitudePseudoStandardParallel, + double scaleFactorPseudoStandardParallel, double falseEasting, + double falseNorthing, const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createKrovak( + PropertyMap(), Angle(latitudeProjectionCentre, angUnit), + Angle(longitudeOfOrigin, angUnit), Angle(colatitudeConeAxis, angUnit), + Angle(latitudePseudoStandardParallel, angUnit), + Scale(scaleFactorPseudoStandardParallel), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Lambert + * Azimuthal Equal Area projection method. + * + * See osgeo::proj::operation::Conversion::createLambertAzimuthalEqualArea(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_LambertAzimuthalEqualArea( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeNatOrigin, + double longitudeNatOrigin, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createLambertAzimuthalEqualArea( + PropertyMap(), Angle(latitudeNatOrigin, angUnit), + Angle(longitudeNatOrigin, angUnit), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Miller + * Cylindrical projection method. + * + * See osgeo::proj::operation::Conversion::createMillerCylindrical(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_MillerCylindrical( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createMillerCylindrical( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Mercator + * projection method. + * + * See osgeo::proj::operation::Conversion::createMercatorVariantA(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_MercatorVariantA( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double scale, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createMercatorVariantA( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Scale(scale), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Mercator + * projection method. + * + * See osgeo::proj::operation::Conversion::createMercatorVariantB(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_MercatorVariantB( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeFirstParallel, + double centerLong, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createMercatorVariantB( + PropertyMap(), Angle(latitudeFirstParallel, angUnit), + Angle(centerLong, angUnit), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Popular + * Visualisation Pseudo Mercator projection method. + * + * See + * osgeo::proj::operation::Conversion::createPopularVisualisationPseudoMercator(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_PopularVisualisationPseudoMercator( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createPopularVisualisationPseudoMercator( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Mollweide + * projection method. + * + * See osgeo::proj::operation::Conversion::createMollweide(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_Mollweide( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createMollweide( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the New Zealand + * Map Grid projection method. + * + * See osgeo::proj::operation::Conversion::createNewZealandMappingGrid(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_NewZealandMappingGrid( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createNewZealandMappingGrid( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Oblique + * Stereographic (Alternative) projection method. + * + * See osgeo::proj::operation::Conversion::createObliqueStereographic(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_ObliqueStereographic( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double scale, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createObliqueStereographic( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Scale(scale), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the + * Orthographic projection method. + * + * See osgeo::proj::operation::Conversion::createOrthographic(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_Orthographic( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createOrthographic( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the American + * Polyconic projection method. + * + * See osgeo::proj::operation::Conversion::createAmericanPolyconic(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_AmericanPolyconic( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createAmericanPolyconic( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Polar + * Stereographic (Variant A) projection method. + * + * See osgeo::proj::operation::Conversion::createPolarStereographicVariantA(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_PolarStereographicVariantA( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double scale, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createPolarStereographicVariantA( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Scale(scale), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Polar + * Stereographic (Variant B) projection method. + * + * See osgeo::proj::operation::Conversion::createPolarStereographicVariantB(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_PolarStereographicVariantB( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeStandardParallel, + double longitudeOfOrigin, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createPolarStereographicVariantB( + PropertyMap(), Angle(latitudeStandardParallel, angUnit), + Angle(longitudeOfOrigin, angUnit), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Robinson + * projection method. + * + * See osgeo::proj::operation::Conversion::createRobinson(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_Robinson( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createRobinson( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Sinusoidal + * projection method. + * + * See osgeo::proj::operation::Conversion::createSinusoidal(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_Sinusoidal( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createSinusoidal( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the + * Stereographic projection method. + * + * See osgeo::proj::operation::Conversion::createStereographic(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_Stereographic( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double scale, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createStereographic( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Scale(scale), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Van der + * Grinten projection method. + * + * See osgeo::proj::operation::Conversion::createVanDerGrinten(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_VanDerGrinten( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createVanDerGrinten( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Wagner I + * projection method. + * + * See osgeo::proj::operation::Conversion::createWagnerI(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_WagnerI( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createWagnerI( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Wagner II + * projection method. + * + * See osgeo::proj::operation::Conversion::createWagnerII(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_WagnerII( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createWagnerII( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Wagner III + * projection method. + * + * See osgeo::proj::operation::Conversion::createWagnerIII(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_WagnerIII( + PJ_OBJ *geodetic_crs, const char *crs_name, double latitudeTrueScale, + double centerLong, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createWagnerIII( + PropertyMap(), Angle(latitudeTrueScale, angUnit), + Angle(centerLong, angUnit), Length(falseEasting, linearUnit), + Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Wagner IV + * projection method. + * + * See osgeo::proj::operation::Conversion::createWagnerIV(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_WagnerIV( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createWagnerIV( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Wagner V + * projection method. + * + * See osgeo::proj::operation::Conversion::createWagnerV(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_WagnerV( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createWagnerV( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Wagner VI + * projection method. + * + * See osgeo::proj::operation::Conversion::createWagnerVI(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_WagnerVI( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createWagnerVI( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Wagner VII + * projection method. + * + * See osgeo::proj::operation::Conversion::createWagnerVII(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_WagnerVII( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createWagnerVII( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the + * Quadrilateralized Spherical Cube projection method. + * + * See + * osgeo::proj::operation::Conversion::createQuadrilateralizedSphericalCube(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_QuadrilateralizedSphericalCube( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLat, + double centerLong, double falseEasting, double falseNorthing, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createQuadrilateralizedSphericalCube( + PropertyMap(), Angle(centerLat, angUnit), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Spherical + * Cross-Track Height projection method. + * + * See osgeo::proj::operation::Conversion::createSphericalCrossTrackHeight(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_SphericalCrossTrackHeight( + PJ_OBJ *geodetic_crs, const char *crs_name, double pegPointLat, + double pegPointLong, double pegPointHeading, double pegPointHeight, + const char *angUnitName, double angUnitConvFactor, + const char *linearUnitName, double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createSphericalCrossTrackHeight( + PropertyMap(), Angle(pegPointLat, angUnit), + Angle(pegPointLong, angUnit), Angle(pegPointHeading, angUnit), + Length(pegPointHeight, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS with a conversion based on the Equal Earth + * projection method. + * + * See osgeo::proj::operation::Conversion::createEqualEarth(). + * + * Linear parameters are expressed in (linearUnitName, linearUnitConvFactor). + * Angular parameters are expressed in (angUnitName, angUnitConvFactor). + */ +PJ_OBJ *proj_obj_create_projected_crs_EqualEarth( + PJ_OBJ *geodetic_crs, const char *crs_name, double centerLong, + double falseEasting, double falseNorthing, const char *angUnitName, + double angUnitConvFactor, const char *linearUnitName, + double linearUnitConvFactor) { + UnitOfMeasure linearUnit( + createLinearUnit(linearUnitName, linearUnitConvFactor)); + UnitOfMeasure angUnit(createAngularUnit(angUnitName, angUnitConvFactor)); + auto conv = Conversion::createEqualEarth( + PropertyMap(), Angle(centerLong, angUnit), + Length(falseEasting, linearUnit), Length(falseNorthing, linearUnit)); + return proj_obj_create_projected_crs(geodetic_crs, crs_name, conv, + linearUnit); +} +/* END: Generated by scripts/create_c_api_projections.py*/ + +// --------------------------------------------------------------------------- + +/** \brief Return whether a coordinate operation can be instanciated as + * a PROJ pipeline, checking in particular that referenced grids are + * available. + * + * @param coordoperation Objet of type CoordinateOperation or derived classes + * (must not be NULL) + * @return TRUE or FALSE. + */ + +int proj_coordoperation_is_instanciable(PJ_OBJ *coordoperation) { + assert(coordoperation); + auto op = + dynamic_cast<const CoordinateOperation *>(coordoperation->obj.get()); + if (!op) { + proj_log_error(coordoperation->ctx, __FUNCTION__, + "Object is not a CoordinateOperation"); + return 0; + } + auto dbContext = getDBcontextNoException(coordoperation->ctx, __FUNCTION__); + try { + return op->isPROJInstanciable(dbContext) ? 1 : 0; + } catch (const std::exception &) { + return 0; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Return the number of parameters of a SingleOperation + * + * @param coordoperation Objet of type SingleOperation or derived classes + * (must not be NULL) + */ + +int proj_coordoperation_get_param_count(PJ_OBJ *coordoperation) { + assert(coordoperation); + auto op = dynamic_cast<const SingleOperation *>(coordoperation->obj.get()); + if (!op) { + proj_log_error(coordoperation->ctx, __FUNCTION__, + "Object is not a SingleOperation"); + return 0; + } + return static_cast<int>(op->parameterValues().size()); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the index of a parameter of a SingleOperation + * + * @param coordoperation Objet of type SingleOperation or derived classes + * (must not be NULL) + * @param name Parameter name. Must not be NULL + * @return index (>=0), or -1 in case of error. + */ + +int proj_coordoperation_get_param_index(PJ_OBJ *coordoperation, + const char *name) { + assert(coordoperation); + assert(name); + auto op = dynamic_cast<const SingleOperation *>(coordoperation->obj.get()); + if (!op) { + proj_log_error(coordoperation->ctx, __FUNCTION__, + "Object is not a SingleOperation"); + return -1; + } + int index = 0; + for (const auto &genParam : op->method()->parameters()) { + if (Identifier::isEquivalentName(genParam->nameStr().c_str(), name)) { + return index; + } + index++; + } + return -1; +} + +// --------------------------------------------------------------------------- + +/** \brief Return a parameter of a SingleOperation + * + * @param coordoperation Objet of type SingleOperation or derived classes + * (must not be NULL) + * @param index Parameter index. + * @param pName Pointer to a string value to store the parameter name. or NULL + * @param pNameAuthorityName Pointer to a string value to store the parameter + * authority name. or NULL + * @param pNameCode Pointer to a string value to store the parameter + * code. or NULL + * @param pValue Pointer to a double value to store the parameter + * value (if numeric). or NULL + * @param pValueString Pointer to a string value to store the parameter + * value (if of type string). or NULL + * @param pValueUnitConvFactor Pointer to a double value to store the parameter + * unit conversion factor. or NULL + * @param pValueUnitName Pointer to a string value to store the parameter + * unit name. or NULL + * @return TRUE in case of success. + */ + +int proj_coordoperation_get_param(PJ_OBJ *coordoperation, int index, + const char **pName, + const char **pNameAuthorityName, + const char **pNameCode, double *pValue, + const char **pValueString, + double *pValueUnitConvFactor, + const char **pValueUnitName) { + assert(coordoperation); + auto op = dynamic_cast<const SingleOperation *>(coordoperation->obj.get()); + if (!op) { + proj_log_error(coordoperation->ctx, __FUNCTION__, + "Object is not a SingleOperation"); + return false; + } + const auto ¶meters = op->method()->parameters(); + const auto &values = op->parameterValues(); + if (static_cast<size_t>(index) >= parameters.size() || + static_cast<size_t>(index) >= values.size()) { + proj_log_error(coordoperation->ctx, __FUNCTION__, "Invalid index"); + return false; + } + + const auto ¶m = parameters[index]; + const auto ¶m_ids = param->identifiers(); + if (pName) { + *pName = param->name()->description()->c_str(); + } + if (pNameAuthorityName) { + if (!param_ids.empty()) { + *pNameAuthorityName = param_ids[0]->codeSpace()->c_str(); + } else { + *pNameAuthorityName = nullptr; + } + } + if (pNameCode) { + if (!param_ids.empty()) { + *pNameCode = param_ids[0]->code().c_str(); + } else { + *pNameCode = nullptr; + } + } + + const auto &value = values[index]; + ParameterValuePtr paramValue = nullptr; + auto opParamValue = + dynamic_cast<const OperationParameterValue *>(value.get()); + if (opParamValue) { + paramValue = opParamValue->parameterValue().as_nullable(); + } + if (pValue) { + *pValue = 0; + if (paramValue) { + if (paramValue->type() == ParameterValue::Type::MEASURE) { + *pValue = paramValue->value().value(); + } + } + } + if (pValueString) { + *pValueString = nullptr; + if (paramValue) { + if (paramValue->type() == ParameterValue::Type::FILENAME) { + *pValueString = paramValue->valueFile().c_str(); + } else if (paramValue->type() == ParameterValue::Type::STRING) { + *pValueString = paramValue->stringValue().c_str(); + } + } + } + if (pValueUnitConvFactor) { + *pValueUnitConvFactor = 0; + if (paramValue) { + if (paramValue->type() == ParameterValue::Type::MEASURE) { + *pValueUnitConvFactor = + paramValue->value().unit().conversionToSI(); + } + } + } + if (pValueUnitName) { + *pValueUnitName = nullptr; + if (paramValue) { + if (paramValue->type() == ParameterValue::Type::MEASURE) { + *pValueUnitName = paramValue->value().unit().name().c_str(); + } + } + } + + return true; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the number of grids used by a CoordinateOperation + * + * @param coordoperation Objet of type CoordinateOperation or derived classes + * (must not be NULL) + */ + +int proj_coordoperation_get_grid_used_count(PJ_OBJ *coordoperation) { + assert(coordoperation); + auto co = + dynamic_cast<const CoordinateOperation *>(coordoperation->obj.get()); + if (!co) { + proj_log_error(coordoperation->ctx, __FUNCTION__, + "Object is not a CoordinateOperation"); + return 0; + } + auto dbContext = getDBcontextNoException(coordoperation->ctx, __FUNCTION__); + try { + if (!coordoperation->gridsNeededAsked) { + coordoperation->gridsNeededAsked = true; + const auto gridsNeeded = co->gridsNeeded(dbContext); + for (const auto &gridDesc : gridsNeeded) { + coordoperation->gridsNeeded.emplace_back(gridDesc); + } + } + return static_cast<int>(coordoperation->gridsNeeded.size()); + } catch (const std::exception &e) { + proj_log_error(coordoperation->ctx, __FUNCTION__, e.what()); + return 0; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Return a parameter of a SingleOperation + * + * @param coordoperation Objet of type SingleOperation or derived classes + * (must not be NULL) + * @param index Parameter index. + * @param pShortName Pointer to a string value to store the grid short name. or + * NULL + * @param pFullName Pointer to a string value to store the grid full filename. + * or NULL + * @param pPackageName Pointer to a string value to store the package name where + * the grid might be found. or NULL + * @param pURL Pointer to a string value to store the grid URL or the + * package URL where the grid might be found. or NULL + * @param pDirectDownload Pointer to a int (boolean) value to store whether + * *pURL can be downloaded directly. or NULL + * @param pOpenLicense Pointer to a int (boolean) value to store whether + * the grid is released with an open license. or NULL + * @param pAvailable Pointer to a int (boolean) value to store whether the grid + * is available at runtime. or NULL + * @return TRUE in case of success. + */ + +int proj_coordoperation_get_grid_used(PJ_OBJ *coordoperation, int index, + const char **pShortName, + const char **pFullName, + const char **pPackageName, + const char **pURL, int *pDirectDownload, + int *pOpenLicense, int *pAvailable) { + const int count = proj_coordoperation_get_grid_used_count(coordoperation); + if (index < 0 || index >= count) { + proj_log_error(coordoperation->ctx, __FUNCTION__, "Invalid index"); + return false; + } + + const auto &gridDesc = coordoperation->gridsNeeded[index]; + if (pShortName) { + *pShortName = gridDesc.shortName.c_str(); + } + + if (pFullName) { + *pFullName = gridDesc.fullName.c_str(); + } + + if (pPackageName) { + *pPackageName = gridDesc.packageName.c_str(); + } + + if (pURL) { + *pURL = gridDesc.url.c_str(); + } + + if (pDirectDownload) { + *pDirectDownload = gridDesc.directDownload; + } + + if (pOpenLicense) { + *pOpenLicense = gridDesc.openLicense; + } + + if (pAvailable) { + *pAvailable = gridDesc.available; + } + + return true; +} + +// --------------------------------------------------------------------------- + +/** \brief Opaque object representing an operation factory context. */ +struct PJ_OPERATION_FACTORY_CONTEXT { + //! @cond Doxygen_Suppress + PJ_CONTEXT *ctx; + CoordinateOperationContextNNPtr operationContext; + + explicit PJ_OPERATION_FACTORY_CONTEXT( + PJ_CONTEXT *ctxIn, CoordinateOperationContextNNPtr &&operationContextIn) + : ctx(ctxIn), operationContext(std::move(operationContextIn)) {} + + PJ_OPERATION_FACTORY_CONTEXT(const PJ_OPERATION_FACTORY_CONTEXT &) = delete; + PJ_OPERATION_FACTORY_CONTEXT & + operator=(const PJ_OPERATION_FACTORY_CONTEXT &) = delete; + //! @endcond +}; + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a context for building coordinate operations between + * two CRS. + * + * The returned object must be unreferenced with + * proj_operation_factory_context_unref() after use. + * + * @param ctx Context, or NULL for default context. + * @param authority Name of authority to which to restrict the search of + * canidate operations. Or NULL to allow any authority. + * @return Object that must be unreferenced with + * proj_operation_factory_context_unref(), or NULL in + * case of error. + */ +PJ_OPERATION_FACTORY_CONTEXT * +proj_create_operation_factory_context(PJ_CONTEXT *ctx, const char *authority) { + SANITIZE_CTX(ctx); + auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); + try { + if (dbContext) { + auto factory = CoordinateOperationFactory::create(); + auto authFactory = AuthorityFactory::create( + NN_NO_CHECK(dbContext), + std::string(authority ? authority : "")); + auto operationContext = + CoordinateOperationContext::create(authFactory, nullptr, 0.0); + return new PJ_OPERATION_FACTORY_CONTEXT( + ctx, std::move(operationContext)); + } else { + auto operationContext = + CoordinateOperationContext::create(nullptr, nullptr, 0.0); + return new PJ_OPERATION_FACTORY_CONTEXT( + ctx, std::move(operationContext)); + } + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Drops a reference on an object. + * + * This method should be called one and exactly one for each function + * returning a PJ_OPERATION_FACTORY_CONTEXT* + * + * @param ctxt Object, or NULL. + */ +void proj_operation_factory_context_unref(PJ_OPERATION_FACTORY_CONTEXT *ctxt) { + delete ctxt; +} + +// --------------------------------------------------------------------------- + +/** \brief Set the desired accuracy of the resulting coordinate transformations. + * @param ctxt Operation factory context. must not be NULL + * @param accuracy Accuracy in meter (or 0 to disable the filter). + */ +void proj_operation_factory_context_set_desired_accuracy( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, double accuracy) { + assert(ctxt); + ctxt->operationContext->setDesiredAccuracy(accuracy); +} + +// --------------------------------------------------------------------------- + +/** \brief Set the desired area of interest for the resulting coordinate + * transformations. + * + * For an area of interest crossing the anti-meridian, west_lon will be + * greater than east_lon. + * + * @param ctxt Operation factory context. must not be NULL + * @param west_lon West longitude (in degrees). + * @param south_lat South latitude (in degrees). + * @param east_lon East longitude (in degrees). + * @param north_lat North latitude (in degrees). + */ +void proj_operation_factory_context_set_area_of_interest( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, double west_lon, double south_lat, + double east_lon, double north_lat) { + assert(ctxt); + ctxt->operationContext->setAreaOfInterest( + Extent::createFromBBOX(west_lon, south_lat, east_lon, north_lat)); +} + +// --------------------------------------------------------------------------- + +/** \brief Set how source and target CRS extent should be used + * when considering if a transformation can be used (only takes effect if + * no area of interest is explicitly defined). + * + * The default is PJ_CRS_EXTENT_SMALLEST. + * + * @param ctxt Operation factory context. must not be NULL + * @param use How source and target CRS extent should be used. + */ +void proj_operation_factory_context_set_crs_extent_use( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, PROJ_CRS_EXTENT_USE use) { + assert(ctxt); + switch (use) { + case PJ_CRS_EXTENT_NONE: + ctxt->operationContext->setSourceAndTargetCRSExtentUse( + CoordinateOperationContext::SourceTargetCRSExtentUse::NONE); + break; + + case PJ_CRS_EXTENT_BOTH: + ctxt->operationContext->setSourceAndTargetCRSExtentUse( + CoordinateOperationContext::SourceTargetCRSExtentUse::BOTH); + break; + + case PJ_CRS_EXTENT_INTERSECTION: + ctxt->operationContext->setSourceAndTargetCRSExtentUse( + CoordinateOperationContext::SourceTargetCRSExtentUse::INTERSECTION); + break; + + case PJ_CRS_EXTENT_SMALLEST: + ctxt->operationContext->setSourceAndTargetCRSExtentUse( + CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST); + break; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Set the spatial criterion to use when comparing the area of + * validity of coordinate operations with the area of interest / area of + * validity of + * source and target CRS. + * + * The default is PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT. + * + * @param ctxt Operation factory context. must not be NULL + * @param criterion patial criterion to use + */ +void PROJ_DLL proj_operation_factory_context_set_spatial_criterion( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, PROJ_SPATIAL_CRITERION criterion) { + assert(ctxt); + switch (criterion) { + case PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT: + ctxt->operationContext->setSpatialCriterion( + CoordinateOperationContext::SpatialCriterion::STRICT_CONTAINMENT); + break; + + case PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION: + ctxt->operationContext->setSpatialCriterion( + CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); + break; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Set how grid availability is used. + * + * The default is USE_FOR_SORTING. + * + * @param ctxt Operation factory context. must not be NULL + * @param use how grid availability is used. + */ +void PROJ_DLL proj_operation_factory_context_set_grid_availability_use( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, PROJ_GRID_AVAILABILITY_USE use) { + assert(ctxt); + switch (use) { + case PROJ_GRID_AVAILABILITY_USED_FOR_SORTING: + ctxt->operationContext->setGridAvailabilityUse( + CoordinateOperationContext::GridAvailabilityUse::USE_FOR_SORTING); + break; + + case PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID: + ctxt->operationContext->setGridAvailabilityUse( + CoordinateOperationContext::GridAvailabilityUse:: + DISCARD_OPERATION_IF_MISSING_GRID); + break; + + case PROJ_GRID_AVAILABILITY_IGNORED: + ctxt->operationContext->setGridAvailabilityUse( + CoordinateOperationContext::GridAvailabilityUse:: + IGNORE_GRID_AVAILABILITY); + break; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether PROJ alternative grid names should be substituted to + * the official authority names. + * + * The default is true. + * + * @param ctxt Operation factory context. must not be NULL + * @param usePROJNames whether PROJ alternative grid names should be used + */ +void proj_operation_factory_context_set_use_proj_alternative_grid_names( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, int usePROJNames) { + assert(ctxt); + ctxt->operationContext->setUsePROJAlternativeGridNames(usePROJNames != 0); +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether an intermediate pivot CRS can be used for researching + * coordinate operations between a source and target CRS. + * + * Concretely if in the database there is an operation from A to C + * (or C to A), and another one from C to B (or B to C), but no direct + * operation between A and B, setting this parameter to true, allow + * chaining both operations. + * + * The current implementation is limited to researching one intermediate + * step. + * + * By default, all potential C candidates will be used. + * proj_operation_factory_context_set_allowed_intermediate_crs() + * can be used to restrict them. + * + * The default is true. + * + * @param ctxt Operation factory context. must not be NULL + * @param allow whether intermediate CRS may be used. + */ +void proj_operation_factory_context_set_allow_use_intermediate_crs( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, int allow) { + assert(ctxt); + ctxt->operationContext->setAllowUseIntermediateCRS(allow != 0); +} + +// --------------------------------------------------------------------------- + +/** \brief Restrict the potential pivot CRSs that can be used when trying to + * build a coordinate operation between two CRS that have no direct operation. + * + * @param ctxt Operation factory context. must not be NULL + * @param list_of_auth_name_codes an array of strings NLL terminated, + * with the format { "auth_name1", "code1", "auth_name2", "code2", ... NULL } + */ +void proj_operation_factory_context_set_allowed_intermediate_crs( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, + const char *const *list_of_auth_name_codes) { + assert(ctxt); + std::vector<std::pair<std::string, std::string>> pivots; + for (auto iter = list_of_auth_name_codes; iter && iter[0] && iter[1]; + iter += 2) { + pivots.emplace_back(std::pair<std::string, std::string>( + std::string(iter[0]), std::string(iter[1]))); + } + ctxt->operationContext->setIntermediateCRS(pivots); +} + +// --------------------------------------------------------------------------- + +/** \brief Find a list of CoordinateOperation from source_crs to target_crs. + * + * The operations are sorted with the most relevant ones first: by + * descending + * area (intersection of the transformation area with the area of interest, + * or intersection of the transformation with the area of use of the CRS), + * and + * by increasing accuracy. Operations with unknown accuracy are sorted last, + * whatever their area. + * + * @param source_crs source CRS. Must not be NULL. + * @param target_crs source CRS. Must not be NULL. + * @param operationContext Search context. Must not be NULL. + * @return a result set that must be unreferenced with + * proj_obj_list_unref(), or NULL in case of error. + */ +PJ_OBJ_LIST * +proj_obj_create_operations(PJ_OBJ *source_crs, PJ_OBJ *target_crs, + PJ_OPERATION_FACTORY_CONTEXT *operationContext) { + assert(source_crs); + assert(target_crs); + assert(operationContext); + + auto sourceCRS = nn_dynamic_pointer_cast<CRS>(source_crs->obj); + if (!sourceCRS) { + proj_log_error(operationContext->ctx, __FUNCTION__, + "source_crs is not a CRS"); + return nullptr; + } + auto targetCRS = nn_dynamic_pointer_cast<CRS>(target_crs->obj); + if (!targetCRS) { + proj_log_error(operationContext->ctx, __FUNCTION__, + "target_crs is not a CRS"); + return nullptr; + } + + try { + auto factory = CoordinateOperationFactory::create(); + std::vector<IdentifiedObjectNNPtr> objects; + auto ops = factory->createOperations( + NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), + operationContext->operationContext); + for (const auto &op : ops) { + objects.emplace_back(op); + } + return new PJ_OBJ_LIST(operationContext->ctx, std::move(objects)); + } catch (const std::exception &e) { + proj_log_error(operationContext->ctx, __FUNCTION__, e.what()); + return nullptr; + } +} + +// --------------------------------------------------------------------------- + +/** \brief Return the number of objects in the result set + * + * @param result Objet of type PJ_OBJ_LIST (must not be NULL) + */ +int proj_obj_list_get_count(PJ_OBJ_LIST *result) { + assert(result); + return static_cast<int>(result->objects.size()); +} + +// --------------------------------------------------------------------------- + +/** \brief Return an object from the result set + * + * The returned object must be unreferenced with proj_obj_unref() after + * use. + * It should be used by at most one thread at a time. + * + * @param result Objet of type PJ_OBJ_LIST (must not be NULL) + * @param index Index + * @return a new object that must be unreferenced with proj_obj_unref(), + * or nullptr in case of error. + */ + +PJ_OBJ *proj_obj_list_get(PJ_OBJ_LIST *result, int index) { + assert(result); + if (index < 0 || index >= proj_obj_list_get_count(result)) { + proj_log_error(result->ctx, __FUNCTION__, "Invalid index"); + return nullptr; + } + return PJ_OBJ::create(result->ctx, result->objects[index]); +} + +// --------------------------------------------------------------------------- + +/** \brief Drops a reference on the result set. + * + * This method should be called one and exactly one for each function + * returning a PJ_OBJ_LIST* + * + * @param result Object, or NULL. + */ +void proj_obj_list_unref(PJ_OBJ_LIST *result) { delete result; } + +// --------------------------------------------------------------------------- + +/** \brief Return the accuracy (in metre) of a coordinate operation. + * + * @return the accuracy, or a negative value if unknown or in case of error. + */ +double proj_coordoperation_get_accuracy(PJ_OBJ *coordoperation) { + assert(coordoperation); + auto co = + dynamic_cast<const CoordinateOperation *>(coordoperation->obj.get()); + if (!co) { + proj_log_error(coordoperation->ctx, __FUNCTION__, + "Object is not a CoordinateOperation"); + return -1; + } + const auto &accuracies = co->coordinateOperationAccuracies(); + if (accuracies.empty()) { + return -1; + } + try { + return c_locale_stod(accuracies[0]->value()); + } catch (const std::exception &) { + } + return -1; +} diff --git a/src/common.cpp b/src/common.cpp new file mode 100644 index 00000000..94bc8678 --- /dev/null +++ b/src/common.cpp @@ -0,0 +1,1125 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/common.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include "proj.h" + +#include <cmath> // M_PI +#include <cstdlib> +#include <memory> +#include <string> +#include <vector> + +using namespace NS_PROJ::internal; +using namespace NS_PROJ::io; +using namespace NS_PROJ::metadata; +using namespace NS_PROJ::util; + +#if 0 +namespace dropbox{ namespace oxygen { +template<> nn<NS_PROJ::common::IdentifiedObjectPtr>::~nn() = default; +template<> nn<NS_PROJ::common::ObjectDomainPtr>::~nn() = default; +template<> nn<NS_PROJ::common::ObjectUsagePtr>::~nn() = default; +template<> nn<NS_PROJ::common::UnitOfMeasurePtr>::~nn() = default; +}} +#endif + +NS_PROJ_START +namespace common { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct UnitOfMeasure::Private { + std::string name_{}; + double toSI_ = 1.0; + UnitOfMeasure::Type type_{UnitOfMeasure::Type::UNKNOWN}; + std::string codeSpace_{}; + std::string code_{}; + + Private(const std::string &nameIn, double toSIIn, + UnitOfMeasure::Type typeIn, const std::string &codeSpaceIn, + const std::string &codeIn) + : name_(nameIn), toSI_(toSIIn), type_(typeIn), codeSpace_(codeSpaceIn), + code_(codeIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Creates a UnitOfMeasure. */ +UnitOfMeasure::UnitOfMeasure(const std::string &nameIn, double toSIIn, + UnitOfMeasure::Type typeIn, + const std::string &codeSpaceIn, + const std::string &codeIn) + : d(internal::make_unique<Private>(nameIn, toSIIn, typeIn, codeSpaceIn, + codeIn)) {} + +// --------------------------------------------------------------------------- + +UnitOfMeasure::UnitOfMeasure(const UnitOfMeasure &other) + : d(internal::make_unique<Private>(*(other.d))) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +UnitOfMeasure::~UnitOfMeasure() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +UnitOfMeasure &UnitOfMeasure::operator=(const UnitOfMeasure &other) { + if (this != &other) { + *d = *(other.d); + } + return *this; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +UnitOfMeasureNNPtr UnitOfMeasure::create(const UnitOfMeasure &other) { + return util::nn_make_shared<UnitOfMeasure>(other); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the name of the unit of measure. */ +const std::string &UnitOfMeasure::name() PROJ_PURE_DEFN { return d->name_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the conversion factor to the unit of the + * International System of Units of the same Type. + * + * For example, for foot, this would be 0.3048 (metre) + * + * @return the conversion factor, or 0 if no conversion exists. + */ +double UnitOfMeasure::conversionToSI() PROJ_PURE_DEFN { return d->toSI_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the type of the unit of measure. + */ +UnitOfMeasure::Type UnitOfMeasure::type() PROJ_PURE_DEFN { return d->type_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the code space of the unit of measure. + * + * For example "EPSG" + * + * @return the code space, or empty string. + */ +const std::string &UnitOfMeasure::codeSpace() PROJ_PURE_DEFN { + return d->codeSpace_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the code of the unit of measure. + * + * @return the code, or empty string. + */ +const std::string &UnitOfMeasure::code() PROJ_PURE_DEFN { return d->code_; } + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void UnitOfMeasure::_exportToWKT( + WKTFormatter *formatter, + const std::string &unitType) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2; + + if (formatter->forceUNITKeyword() && type() != Type::PARAMETRIC) { + formatter->startNode(WKTConstants::UNIT, !codeSpace().empty()); + } else if (!unitType.empty()) { + formatter->startNode(unitType, !codeSpace().empty()); + } else { + if (isWKT2 && type() == Type::LINEAR) { + formatter->startNode(WKTConstants::LENGTHUNIT, + !codeSpace().empty()); + } else if (isWKT2 && type() == Type::ANGULAR) { + formatter->startNode(WKTConstants::ANGLEUNIT, !codeSpace().empty()); + } else if (isWKT2 && type() == Type::SCALE) { + formatter->startNode(WKTConstants::SCALEUNIT, !codeSpace().empty()); + } else if (isWKT2 && type() == Type::TIME) { + formatter->startNode(WKTConstants::TIMEUNIT, !codeSpace().empty()); + } else if (isWKT2 && type() == Type::PARAMETRIC) { + formatter->startNode(WKTConstants::PARAMETRICUNIT, + !codeSpace().empty()); + } else { + formatter->startNode(WKTConstants::UNIT, !codeSpace().empty()); + } + } + + { + const auto &l_name = name(); + const bool esri = formatter->useESRIDialect(); + if (esri) { + if (ci_equal(l_name, "degree")) { + formatter->addQuotedString("Degree"); + } else if (ci_equal(l_name, "grad")) { + formatter->addQuotedString("Grad"); + } else if (ci_equal(l_name, "metre")) { + formatter->addQuotedString("Meter"); + } else { + formatter->addQuotedString(l_name); + } + } else { + formatter->addQuotedString(l_name); + } + const auto &factor = conversionToSI(); + if (!isWKT2 || factor != 0.0) { + // Some TIMEUNIT do not have a conversion factor + formatter->add(factor); + } + if (!codeSpace().empty() && formatter->outputId()) { + formatter->startNode( + isWKT2 ? WKTConstants::ID : WKTConstants::AUTHORITY, false); + formatter->addQuotedString(codeSpace()); + const auto &l_code = code(); + if (isWKT2) { + try { + (void)std::stoi(l_code); + formatter->add(l_code); + } catch (const std::exception &) { + formatter->addQuotedString(l_code); + } + } else { + formatter->addQuotedString(l_code); + } + formatter->endNode(); + } + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** Returns whether two units of measures are equal. + * + * The comparison is based on the name. + */ +bool UnitOfMeasure::operator==(const UnitOfMeasure &other) PROJ_PURE_DEFN { + return name() == other.name(); +} + +// --------------------------------------------------------------------------- + +/** Returns whether two units of measures are different. + * + * The comparison is based on the name. + */ +bool UnitOfMeasure::operator!=(const UnitOfMeasure &other) PROJ_PURE_DEFN { + return name() != other.name(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::string UnitOfMeasure::exportToPROJString() const { + if (type() == Type::LINEAR) { + auto proj_units = proj_list_units(); + for (int i = 0; proj_units[i].id != nullptr; i++) { + if (::fabs(proj_units[i].factor - conversionToSI()) < + 1e-10 * conversionToSI()) { + return proj_units[i].id; + } + } + } else if (type() == Type::ANGULAR) { + auto proj_angular_units = proj_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()) { + return proj_angular_units[i].id; + } + } + } + return std::string(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool UnitOfMeasure::_isEquivalentTo( + const UnitOfMeasure &other, util::IComparable::Criterion criterion) const { + if (criterion == util::IComparable::Criterion::STRICT) { + return operator==(other); + } + return std::fabs(conversionToSI() - other.conversionToSI()) <= + 1e-10 * std::fabs(conversionToSI()); +} + +//! @endcond +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Measure::Private { + double value_ = 0.0; + UnitOfMeasure unit_{}; + + Private(double valueIn, const UnitOfMeasure &unitIn) + : value_(valueIn), unit_(unitIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Measure. + */ +Measure::Measure(double valueIn, const UnitOfMeasure &unitIn) + : d(internal::make_unique<Private>(valueIn, unitIn)) {} + +// --------------------------------------------------------------------------- + +Measure::Measure(const Measure &other) + : d(internal::make_unique<Private>(*(other.d))) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Measure::~Measure() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the unit of the Measure. + */ +const UnitOfMeasure &Measure::unit() PROJ_CONST_DEFN { return d->unit_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the value of the Measure, after conversion to the + * corresponding + * unit of the International System. + */ +double Measure::getSIValue() PROJ_CONST_DEFN { + return d->value_ * d->unit_.conversionToSI(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the value of the measure, expressed in the unit() + */ +double Measure::value() PROJ_CONST_DEFN { return d->value_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the value of this measure expressed into the provided unit. + */ +double Measure::convertToUnit(const UnitOfMeasure &otherUnit) PROJ_CONST_DEFN { + return getSIValue() / otherUnit.conversionToSI(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether two measures are equal. + * + * The comparison is done both on the value and the unit. + */ +bool Measure::operator==(const Measure &other) PROJ_CONST_DEFN { + return d->value_ == other.d->value_ && d->unit_ == other.d->unit_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns whether an object is equivalent to another one. + * @param other other object to compare to + * @param criterion comparaison criterion. + * @return true if objects are equivalent. + */ +bool Measure::_isEquivalentTo(const Measure &other, + util::IComparable::Criterion criterion) const { + if (criterion == util::IComparable::Criterion::STRICT) { + return operator==(other); + } + return std::fabs(getSIValue() - other.getSIValue()) <= + 1e-10 * std::fabs(getSIValue()); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Scale. + * + * @param valueIn value + */ +Scale::Scale(double valueIn) : Measure(valueIn, UnitOfMeasure::SCALE_UNITY) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Scale. + * + * @param valueIn value + * @param unitIn unit. Constraint: unit.type() == UnitOfMeasure::Type::SCALE + */ +Scale::Scale(double valueIn, const UnitOfMeasure &unitIn) + : Measure(valueIn, unitIn) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Scale::Scale(const Scale &) = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Scale::~Scale() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Angle. + * + * @param valueIn value + */ +Angle::Angle(double valueIn) : Measure(valueIn, UnitOfMeasure::DEGREE) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Angle. + * + * @param valueIn value + * @param unitIn unit. Constraint: unit.type() == UnitOfMeasure::Type::ANGULAR + */ +Angle::Angle(double valueIn, const UnitOfMeasure &unitIn) + : Measure(valueIn, unitIn) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Angle::Angle(const Angle &) = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Angle::~Angle() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Length. + * + * @param valueIn value + */ +Length::Length(double valueIn) : Measure(valueIn, UnitOfMeasure::METRE) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Length. + * + * @param valueIn value + * @param unitIn unit. Constraint: unit.type() == UnitOfMeasure::Type::LINEAR + */ +Length::Length(double valueIn, const UnitOfMeasure &unitIn) + : Measure(valueIn, unitIn) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Length::Length(const Length &) = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Length::~Length() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DateTime::Private { + std::string str_{}; + + explicit Private(const std::string &str) : str_(str) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +DateTime::DateTime() : d(internal::make_unique<Private>(std::string())) {} + +// --------------------------------------------------------------------------- + +DateTime::DateTime(const std::string &str) + : d(internal::make_unique<Private>(str)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DateTime::DateTime(const DateTime &other) + : d(internal::make_unique<Private>(*(other.d))) {} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DateTime::~DateTime() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DateTime. */ +DateTime DateTime::create(const std::string &str) { return DateTime(str); } + +// --------------------------------------------------------------------------- + +/** \brief Return whether the DateTime is ISO:8601 compliant. + * + * \remark The current implementation is really simplistic, and aimed at + * detecting date-times that are not ISO:8601 compliant. + */ +bool DateTime::isISO_8601() const { + return !d->str_.empty() && d->str_[0] >= '0' && d->str_[0] <= '9' && + d->str_.find(' ') == std::string::npos; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the DateTime as a string. + */ +std::string DateTime::toString() const { return d->str_; } + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +// cppcheck-suppress copyCtorAndEqOperator +struct IdentifiedObject::Private { + IdentifierNNPtr name{Identifier::create()}; + std::vector<IdentifierNNPtr> identifiers{}; + std::vector<GenericNameNNPtr> aliases{}; + std::string remarks{}; + bool isDeprecated{}; + + void setIdentifiers(const PropertyMap &properties); + void setName(const PropertyMap &properties); + void setAliases(const PropertyMap &properties); +}; +//! @endcond + +// --------------------------------------------------------------------------- + +IdentifiedObject::IdentifiedObject() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +IdentifiedObject::IdentifiedObject(const IdentifiedObject &other) + : d(internal::make_unique<Private>(*(other.d))) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +IdentifiedObject::~IdentifiedObject() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the name of the object. + * + * Generally, the only interesting field of the name will be + * name()->description(). + */ +const IdentifierNNPtr &IdentifiedObject::name() PROJ_CONST_DEFN { + return d->name; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the name of the object. + * + * Return *(name()->description()) + */ +const std::string &IdentifiedObject::nameStr() PROJ_CONST_DEFN { + return *(d->name->description()); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the identifier(s) of the object + * + * Generally, those will have Identifier::code() and Identifier::codeSpace() + * filled. + */ +const std::vector<IdentifierNNPtr> & +IdentifiedObject::identifiers() PROJ_CONST_DEFN { + return d->identifiers; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the alias(es) of the object. + */ +const std::vector<GenericNameNNPtr> & +IdentifiedObject::aliases() PROJ_CONST_DEFN { + return d->aliases; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the (first) alias of the object as a string. + * + * Shortcut for aliases()[0]->toFullyQualifiedName()->toString() + */ +std::string IdentifiedObject::alias() PROJ_CONST_DEFN { + if (d->aliases.empty()) + return std::string(); + return d->aliases[0]->toFullyQualifiedName()->toString(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the EPSG code. + * @return code, or 0 if not found + */ +int IdentifiedObject::getEPSGCode() PROJ_CONST_DEFN { + for (const auto &id : identifiers()) { + if (ci_equal(*(id->codeSpace()), metadata::Identifier::EPSG)) { + return ::atoi(id->code().c_str()); + } + } + return 0; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the remarks. + */ +const std::string &IdentifiedObject::remarks() PROJ_CONST_DEFN { + return d->remarks; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether the object is deprecated. + * + * \remark Extension of \ref ISO_19111_2018 + */ +bool IdentifiedObject::isDeprecated() PROJ_CONST_DEFN { + return d->isDeprecated; +} + +// --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress + +void IdentifiedObject::Private::setName( + const PropertyMap &properties) // throw(InvalidValueTypeException) +{ + auto oIter = properties.find(NAME_KEY); + if (oIter == properties.end()) { + return; + } + if (auto genVal = + util::nn_dynamic_pointer_cast<BoxedValue>(oIter->second)) { + if (genVal->type() == BoxedValue::Type::STRING) { + name = Identifier::create( + std::string(), PropertyMap().set(Identifier::DESCRIPTION_KEY, + genVal->stringValue())); + } else { + throw InvalidValueTypeException("Invalid value type for " + + NAME_KEY); + } + } else { + if (auto identifier = + util::nn_dynamic_pointer_cast<Identifier>(oIter->second)) { + name = NN_NO_CHECK(identifier); + } else { + throw InvalidValueTypeException("Invalid value type for " + + NAME_KEY); + } + } +} + +// --------------------------------------------------------------------------- + +void IdentifiedObject::Private::setIdentifiers( + const PropertyMap &properties) // throw(InvalidValueTypeException) +{ + auto oIter = properties.find(IDENTIFIERS_KEY); + if (oIter == properties.end()) { + + oIter = properties.find(Identifier::CODE_KEY); + if (oIter != properties.end()) { + identifiers.push_back( + Identifier::create(std::string(), properties)); + } + return; + } + if (auto identifier = + util::nn_dynamic_pointer_cast<Identifier>(oIter->second)) { + identifiers.clear(); + identifiers.push_back(NN_NO_CHECK(identifier)); + } else { + if (auto array = util::nn_dynamic_pointer_cast<ArrayOfBaseObject>( + oIter->second)) { + identifiers.clear(); + for (const auto &val : *array) { + identifier = util::nn_dynamic_pointer_cast<Identifier>(val); + if (identifier) { + identifiers.push_back(NN_NO_CHECK(identifier)); + } else { + throw InvalidValueTypeException("Invalid value type for " + + IDENTIFIERS_KEY); + } + } + } else { + throw InvalidValueTypeException("Invalid value type for " + + IDENTIFIERS_KEY); + } + } +} + +// --------------------------------------------------------------------------- + +void IdentifiedObject::Private::setAliases( + const PropertyMap &properties) // throw(InvalidValueTypeException) +{ + auto oIter = properties.find(ALIAS_KEY); + if (oIter == properties.end()) { + return; + } + if (auto l_name = + util::nn_dynamic_pointer_cast<GenericName>(oIter->second)) { + aliases.clear(); + aliases.push_back(NN_NO_CHECK(l_name)); + } else { + if (auto array = util::nn_dynamic_pointer_cast<ArrayOfBaseObject>( + oIter->second)) { + aliases.clear(); + for (const auto &val : *array) { + l_name = util::nn_dynamic_pointer_cast<GenericName>(val); + if (l_name) { + aliases.push_back(NN_NO_CHECK(l_name)); + } else { + if (auto genVal = + util::nn_dynamic_pointer_cast<BoxedValue>(val)) { + if (genVal->type() == BoxedValue::Type::STRING) { + aliases.push_back(NameFactory::createLocalName( + nullptr, genVal->stringValue())); + } else { + throw InvalidValueTypeException( + "Invalid value type for " + ALIAS_KEY); + } + } else { + throw InvalidValueTypeException( + "Invalid value type for " + ALIAS_KEY); + } + } + } + } else { + std::string temp; + if (properties.getStringValue(ALIAS_KEY, temp)) { + aliases.clear(); + aliases.push_back(NameFactory::createLocalName(nullptr, temp)); + } else { + throw InvalidValueTypeException("Invalid value type for " + + ALIAS_KEY); + } + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +void IdentifiedObject::setProperties( + const PropertyMap &properties) // throw(InvalidValueTypeException) +{ + d->setName(properties); + d->setIdentifiers(properties); + d->setAliases(properties); + + properties.getStringValue(REMARKS_KEY, d->remarks); + + { + auto oIter = properties.find(DEPRECATED_KEY); + if (oIter != properties.end()) { + if (auto genVal = + util::nn_dynamic_pointer_cast<BoxedValue>(oIter->second)) { + if (genVal->type() == BoxedValue::Type::BOOLEAN) { + d->isDeprecated = genVal->booleanValue(); + } else { + throw InvalidValueTypeException("Invalid value type for " + + DEPRECATED_KEY); + } + } else { + throw InvalidValueTypeException("Invalid value type for " + + DEPRECATED_KEY); + } + } + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void IdentifiedObject::formatID(WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2; + for (const auto &id : identifiers()) { + id->_exportToWKT(formatter); + if (!isWKT2) { + break; + } + } +} + +// --------------------------------------------------------------------------- + +void IdentifiedObject::formatRemarks(WKTFormatter *formatter) const { + if (!remarks().empty()) { + formatter->startNode(WKTConstants::REMARK, false); + formatter->addQuotedString(remarks()); + formatter->endNode(); + } +} + +// --------------------------------------------------------------------------- + +bool IdentifiedObject::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherIdObj = dynamic_cast<const IdentifiedObject *>(other); + if (!otherIdObj) + return false; + return _isEquivalentTo(otherIdObj, criterion); +} + +// --------------------------------------------------------------------------- + +bool IdentifiedObject::_isEquivalentTo(const IdentifiedObject *otherIdObj, + util::IComparable::Criterion criterion) + PROJ_CONST_DEFN { + if (criterion == util::IComparable::Criterion::STRICT) { + if (!ci_equal(nameStr(), otherIdObj->nameStr())) { + return false; + } + // TODO test id etc + } else { + if (!metadata::Identifier::isEquivalentName( + nameStr().c_str(), otherIdObj->nameStr().c_str())) { + return false; + } + } + return true; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ObjectDomain::Private { + optional<std::string> scope_{}; + ExtentPtr domainOfValidity_{}; + + Private(const optional<std::string> &scopeIn, const ExtentPtr &extent) + : scope_(scopeIn), domainOfValidity_(extent) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ObjectDomain::ObjectDomain(const optional<std::string> &scopeIn, + const ExtentPtr &extent) + : d(internal::make_unique<Private>(scopeIn, extent)) {} +//! @endcond + +// --------------------------------------------------------------------------- + +ObjectDomain::ObjectDomain(const ObjectDomain &other) + : d(internal::make_unique<Private>(*(other.d))) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ObjectDomain::~ObjectDomain() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the scope. + * + * @return the scope, or empty. + */ +const optional<std::string> &ObjectDomain::scope() PROJ_CONST_DEFN { + return d->scope_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the domain of validity. + * + * @return the domain of validity, or nullptr. + */ +const ExtentPtr &ObjectDomain::domainOfValidity() PROJ_CONST_DEFN { + return d->domainOfValidity_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ObjectDomain. + */ +ObjectDomainNNPtr ObjectDomain::create(const optional<std::string> &scopeIn, + const ExtentPtr &extent) { + return ObjectDomain::nn_make_shared<ObjectDomain>(scopeIn, extent); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ObjectDomain::_exportToWKT(WKTFormatter *formatter) const { + if (d->scope_.has_value()) { + formatter->startNode(WKTConstants::SCOPE, false); + formatter->addQuotedString(*(d->scope_)); + formatter->endNode(); + } else if (formatter->use2018Keywords()) { + formatter->startNode(WKTConstants::SCOPE, false); + formatter->addQuotedString("unknown"); + formatter->endNode(); + } + if (d->domainOfValidity_) { + if (d->domainOfValidity_->description().has_value()) { + formatter->startNode(WKTConstants::AREA, false); + formatter->addQuotedString(*(d->domainOfValidity_->description())); + formatter->endNode(); + } + if (d->domainOfValidity_->geographicElements().size() == 1) { + auto bbox = util::nn_dynamic_pointer_cast<GeographicBoundingBox>( + d->domainOfValidity_->geographicElements()[0]); + if (bbox) { + formatter->startNode(WKTConstants::BBOX, false); + formatter->add(bbox->southBoundLatitude()); + formatter->add(bbox->westBoundLongitude()); + formatter->add(bbox->northBoundLatitude()); + formatter->add(bbox->eastBoundLongitude()); + formatter->endNode(); + } + } + if (d->domainOfValidity_->verticalElements().size() == 1) { + auto extent = d->domainOfValidity_->verticalElements()[0]; + formatter->startNode(WKTConstants::VERTICALEXTENT, false); + formatter->add(extent->minimumValue()); + formatter->add(extent->maximumValue()); + extent->unit()->_exportToWKT(formatter); + formatter->endNode(); + } + if (d->domainOfValidity_->temporalElements().size() == 1) { + auto extent = d->domainOfValidity_->temporalElements()[0]; + formatter->startNode(WKTConstants::TIMEEXTENT, false); + if (DateTime::create(extent->start()).isISO_8601()) { + formatter->add(extent->start()); + } else { + formatter->addQuotedString(extent->start()); + } + if (DateTime::create(extent->stop()).isISO_8601()) { + formatter->add(extent->stop()); + } else { + formatter->addQuotedString(extent->stop()); + } + formatter->endNode(); + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool ObjectDomain::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDomain = dynamic_cast<const ObjectDomain *>(other); + if (!otherDomain) + return false; + if (scope().has_value() != otherDomain->scope().has_value()) + return false; + if (*scope() != *otherDomain->scope()) + return false; + if ((domainOfValidity().get() != nullptr) ^ + (otherDomain->domainOfValidity().get() != nullptr)) + return false; + return domainOfValidity().get() == nullptr || + domainOfValidity()->_isEquivalentTo( + otherDomain->domainOfValidity().get(), criterion); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ObjectUsage::Private { + std::vector<ObjectDomainNNPtr> domains_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +ObjectUsage::ObjectUsage() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +ObjectUsage::ObjectUsage(const ObjectUsage &other) + : IdentifiedObject(other), d(internal::make_unique<Private>(*(other.d))) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ObjectUsage::~ObjectUsage() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the domains of the object. + */ +const std::vector<ObjectDomainNNPtr> &ObjectUsage::domains() PROJ_CONST_DEFN { + return d->domains_; +} + +// --------------------------------------------------------------------------- + +void ObjectUsage::setProperties( + const PropertyMap &properties) // throw(InvalidValueTypeException) +{ + IdentifiedObject::setProperties(properties); + + optional<std::string> scope; + { + std::string temp; + if (properties.getStringValue(SCOPE_KEY, temp)) { + scope = temp; + } + } + + ExtentPtr domainOfValidity; + { + auto oIter = properties.find(DOMAIN_OF_VALIDITY_KEY); + if (oIter != properties.end()) { + domainOfValidity = + util::nn_dynamic_pointer_cast<Extent>(oIter->second); + if (!domainOfValidity) { + throw InvalidValueTypeException("Invalid value type for " + + DOMAIN_OF_VALIDITY_KEY); + } + } + } + + if (scope.has_value() || domainOfValidity) { + d->domains_.emplace_back(ObjectDomain::create(scope, domainOfValidity)); + } + + { + auto oIter = properties.find(OBJECT_DOMAIN_KEY); + if (oIter != properties.end()) { + if (auto objectDomain = util::nn_dynamic_pointer_cast<ObjectDomain>( + oIter->second)) { + d->domains_.emplace_back(NN_NO_CHECK(objectDomain)); + } else if (auto array = + util::nn_dynamic_pointer_cast<ArrayOfBaseObject>( + oIter->second)) { + for (const auto &val : *array) { + objectDomain = + util::nn_dynamic_pointer_cast<ObjectDomain>(val); + if (objectDomain) { + d->domains_.emplace_back(NN_NO_CHECK(objectDomain)); + } else { + throw InvalidValueTypeException( + "Invalid value type for " + OBJECT_DOMAIN_KEY); + } + } + } else { + throw InvalidValueTypeException("Invalid value type for " + + OBJECT_DOMAIN_KEY); + } + } + } +} + +// --------------------------------------------------------------------------- + +void ObjectUsage::baseExportToWKT(WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2; + if (isWKT2 && formatter->outputId()) { + auto l_domains = domains(); + if (!l_domains.empty()) { + if (formatter->use2018Keywords()) { + for (const auto &domain : l_domains) { + formatter->startNode(WKTConstants::USAGE, false); + domain->_exportToWKT(formatter); + formatter->endNode(); + } + } else { + l_domains[0]->_exportToWKT(formatter); + } + } + } + if (formatter->outputId()) { + formatID(formatter); + } + if (isWKT2) { + formatRemarks(formatter); + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool ObjectUsage::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherObjUsage = dynamic_cast<const ObjectUsage *>(other); + if (!otherObjUsage) + return false; + + // TODO: incomplete + return IdentifiedObject::_isEquivalentTo(other, criterion); +} +//! @endcond + +} // namespace common +NS_PROJ_END diff --git a/src/coordinateoperation.cpp b/src/coordinateoperation.cpp new file mode 100644 index 00000000..645bd0ed --- /dev/null +++ b/src/coordinateoperation.cpp @@ -0,0 +1,10915 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif +#define FROM_COORDINATE_OPERATION_CPP + +#include "proj/coordinateoperation.hpp" +#include "proj/common.hpp" +#include "proj/crs.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include "projects.h" // M_PI + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <cstring> +#include <memory> +#include <set> +#include <string> +#include <vector> + +using namespace NS_PROJ::internal; + +#if 0 +namespace dropbox{ namespace oxygen { +template<> nn<NS_PROJ::operation::CoordinateOperationPtr>::~nn() = default; +template<> nn<NS_PROJ::operation::SingleOperationPtr>::~nn() = default; +template<> nn<NS_PROJ::operation::ConversionPtr>::~nn() = default; +template<> nn<NS_PROJ::operation::TransformationPtr>::~nn() = default; +template<> nn<NS_PROJ::operation::ConcatenatedOperationPtr>::~nn() = default; +template<> nn<NS_PROJ::operation::PointMotionOperationPtr>::~nn() = default; +template<> nn<NS_PROJ::operation::GeneralOperationParameterPtr>::~nn() = default; +template<> nn<NS_PROJ::operation::OperationParameterPtr>::~nn() = default; +template<> nn<NS_PROJ::operation::GeneralParameterValuePtr>::~nn() = default; +template<> nn<NS_PROJ::operation::ParameterValuePtr>::~nn() = default; +template<> nn<NS_PROJ::operation::OperationMethodPtr>::~nn() = default; +template<> nn<NS_PROJ::operation::OperationParameterValuePtr>::~nn() = default; +template<> nn<std::unique_ptr<NS_PROJ::operation::CoordinateOperationFactory, std::default_delete<NS_PROJ::operation::CoordinateOperationFactory> > >::~nn() = default; +template<> nn<std::unique_ptr<NS_PROJ::operation::CoordinateOperationContext, std::default_delete<NS_PROJ::operation::CoordinateOperationContext> > >::~nn() = default; +}} +#endif + +#include "proj/internal/coordinateoperation_constants.hpp" +#include "proj/internal/coordinateoperation_internal.hpp" +#include "proj/internal/esri_projection_mappings.hpp" + +#if 0 +namespace dropbox{ namespace oxygen { +template<> nn<std::shared_ptr<NS_PROJ::operation::InverseCoordinateOperation>>::~nn() = default; +template<> nn<std::shared_ptr<NS_PROJ::operation::InverseConversion>>::~nn() = default; +template<> nn<std::shared_ptr<NS_PROJ::operation::InverseTransformation>>::~nn() = default; +template<> nn<NS_PROJ::operation::PROJBasedOperationPtr>::~nn() = default; +}} +#endif + +// --------------------------------------------------------------------------- + +NS_PROJ_START +namespace operation { + +//! @cond Doxygen_Suppress + +constexpr double UTM_LATITUDE_OF_NATURAL_ORIGIN = 0.0; +constexpr double UTM_SCALE_FACTOR = 0.9996; +constexpr double UTM_FALSE_EASTING = 500000.0; +constexpr double UTM_NORTH_FALSE_NORTHING = 0.0; +constexpr double UTM_SOUTH_FALSE_NORTHING = 10000000.0; + +static const std::string INVERSE_OF = "Inverse of "; +static const char *NULL_GEOCENTRIC_TRANSLATION = "Null geocentric translation"; +static const char *NULL_GEOGRAPHIC_OFFSET = "Null geographic offset"; +//! @endcond + +//! @cond Doxygen_Suppress +static util::PropertyMap +createPropertiesForInverse(const CoordinateOperation *op, bool derivedFrom, + bool approximateInversion); +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +class InvalidOperationEmptyIntersection : public InvalidOperation { + public: + explicit InvalidOperationEmptyIntersection(const std::string &message); + InvalidOperationEmptyIntersection( + const InvalidOperationEmptyIntersection &other); + ~InvalidOperationEmptyIntersection() override; +}; + +InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection( + const std::string &message) + : InvalidOperation(message) {} + +InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection( + const InvalidOperationEmptyIntersection &) = default; + +InvalidOperationEmptyIntersection::~InvalidOperationEmptyIntersection() = + default; + +// --------------------------------------------------------------------------- + +static std::string createEntryEqParam(const std::string &a, + const std::string &b) { + return a < b ? a + b : b + a; +} + +static std::set<std::string> buildSetEquivalentParameters() { + + std::set<std::string> set; + + const char *const listOfEquivalentParameterNames[][5] = { + {"latitude_of_point_1", "Latitude_Of_1st_Point", nullptr}, + {"longitude_of_point_1", "Longitude_Of_1st_Point", nullptr}, + {"latitude_of_point_2", "Latitude_Of_2nd_Point", nullptr}, + {"longitude_of_point_2", "Longitude_Of_2nd_Point", nullptr}, + + {EPSG_NAME_PARAMETER_FALSE_EASTING, + EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, + EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, nullptr}, + + {EPSG_NAME_PARAMETER_FALSE_NORTHING, + EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, + EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, nullptr}, + + {EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, + EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, nullptr}, + + {EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, + EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, nullptr}, + }; + + for (const auto ¶mList : listOfEquivalentParameterNames) { + for (size_t i = 0; paramList[i]; i++) { + auto a = metadata::Identifier::canonicalizeName(paramList[i]); + for (size_t j = i + 1; paramList[j]; j++) { + auto b = metadata::Identifier::canonicalizeName(paramList[j]); + set.insert(createEntryEqParam(a, b)); + } + } + } + return set; +} + +static bool areEquivalentParameters(const std::string &a, + const std::string &b) { + + static const std::set<std::string> setEquivalentParameters = + buildSetEquivalentParameters(); + + auto a_can = metadata::Identifier::canonicalizeName(a); + auto b_can = metadata::Identifier::canonicalizeName(b); + return setEquivalentParameters.find(createEntryEqParam(a_can, b_can)) != + setEquivalentParameters.end(); +} + +// --------------------------------------------------------------------------- + +PROJ_NO_INLINE const MethodMapping *getMapping(int epsg_code) noexcept { + for (const auto &mapping : methodMappings) { + if (mapping.epsg_code == epsg_code) { + return &mapping; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +const MethodMapping *getMapping(const OperationMethod *method) noexcept { + const std::string &name(method->nameStr()); + const int epsg_code = method->getEPSGCode(); + for (const auto &mapping : methodMappings) { + if ((epsg_code != 0 && mapping.epsg_code == epsg_code) || + metadata::Identifier::isEquivalentName(mapping.wkt2_name, + name.c_str())) { + return &mapping; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +const MethodMapping *getMappingFromWKT1(const std::string &wkt1_name) noexcept { + // Unusual for a WKT1 projection name, but mentionned in OGC 12-063r5 C.4.2 + if (ci_starts_with(wkt1_name, "UTM zone")) { + return getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR); + } + + for (const auto &mapping : methodMappings) { + if (mapping.wkt1_name && metadata::Identifier::isEquivalentName( + mapping.wkt1_name, wkt1_name.c_str())) { + return &mapping; + } + } + return nullptr; +} +// --------------------------------------------------------------------------- + +const MethodMapping *getMapping(const char *wkt2_name) noexcept { + for (const auto &mapping : methodMappings) { + if (metadata::Identifier::isEquivalentName(mapping.wkt2_name, + wkt2_name)) { + return &mapping; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +std::vector<const MethodMapping *> +getMappingsFromPROJName(const std::string &projName) { + std::vector<const MethodMapping *> res; + for (const auto &mapping : methodMappings) { + if (mapping.proj_name_main && projName == mapping.proj_name_main) { + res.push_back(&mapping); + } + } + return res; +} + +// --------------------------------------------------------------------------- + +const ParamMapping *getMapping(const MethodMapping *mapping, + const OperationParameterValue *param) { + const auto ¶m_name = param->parameter()->name(); + const std::string &name = *(param_name->description()); + const std::string &code = param_name->code(); + const int epsg_code = !code.empty() ? ::atoi(code.c_str()) : 0; + for (int i = 0; mapping->params[i] != nullptr; ++i) { + const auto *paramMapping = mapping->params[i]; + if (metadata::Identifier::isEquivalentName(paramMapping->wkt2_name, + name.c_str()) || + (epsg_code != 0 && paramMapping->epsg_code == epsg_code) || + areEquivalentParameters(paramMapping->wkt2_name, name)) { + return paramMapping; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping, + const std::string &wkt1_name) { + for (int i = 0; mapping->params[i] != nullptr; ++i) { + const auto *paramMapping = mapping->params[i]; + if (paramMapping->wkt1_name && + (metadata::Identifier::isEquivalentName(paramMapping->wkt1_name, + wkt1_name.c_str()) || + areEquivalentParameters(paramMapping->wkt1_name, wkt1_name))) { + return paramMapping; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +std::vector<const ESRIMethodMapping *> +getMappingsFromESRI(const std::string &esri_name) { + std::vector<const ESRIMethodMapping *> res; + for (const auto &mapping : esriMappings) { + if (ci_equal(esri_name, mapping.esri_name)) { + res.push_back(&mapping); + } + } + return res; +} + +// --------------------------------------------------------------------------- + +static const ESRIMethodMapping *getESRIMapping(const std::string &wkt2_name, + int epsg_code) { + for (const auto &mapping : esriMappings) { + if ((epsg_code != 0 && mapping.epsg_code == epsg_code) || + ci_equal(wkt2_name, mapping.wkt2_name)) { + return &mapping; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +static double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops); + +// Returns the accuracy of an operation, or -1 if unknown +static double getAccuracy(const CoordinateOperationNNPtr &op) { + + if (dynamic_cast<const Conversion *>(op.get())) { + // A conversion is perfectly accurate. + return 0.0; + } + + double accuracy = -1.0; + const auto &accuracies = op->coordinateOperationAccuracies(); + if (!accuracies.empty()) { + try { + accuracy = c_locale_stod(accuracies[0]->value()); + } catch (const std::exception &) { + } + } else { + auto concatenated = + dynamic_cast<const ConcatenatedOperation *>(op.get()); + if (concatenated) { + accuracy = getAccuracy(concatenated->operations()); + } + } + return accuracy; +} + +// --------------------------------------------------------------------------- + +// Returns the accuracy of a set of concantenated operations, or -1 if unknown +static double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops) { + double accuracy = -1.0; + for (const auto &subop : ops) { + const double subops_accuracy = getAccuracy(subop); + if (subops_accuracy < 0.0) { + return -1.0; + } + if (accuracy < 0.0) { + accuracy = 0.0; + } + accuracy += subops_accuracy; + } + return accuracy; +} + +// --------------------------------------------------------------------------- + +static metadata::ExtentPtr +getExtent(const std::vector<CoordinateOperationNNPtr> &ops, + bool conversionExtentIsWorld, bool &emptyIntersection); + +static metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op, + bool conversionExtentIsWorld, + bool &emptyIntersection) { + auto conv = dynamic_cast<const Conversion *>(op.get()); + if (conv) { + emptyIntersection = false; + return metadata::Extent::WORLD; + } + const auto &domains = op->domains(); + if (!domains.empty()) { + emptyIntersection = false; + return domains[0]->domainOfValidity(); + } + auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get()); + if (!concatenated) { + emptyIntersection = false; + return nullptr; + } + return getExtent(concatenated->operations(), conversionExtentIsWorld, + emptyIntersection); +} + +// --------------------------------------------------------------------------- + +static const metadata::ExtentPtr nullExtent{}; + +static const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs) { + const auto &domains = crs->domains(); + if (!domains.empty()) { + return domains[0]->domainOfValidity(); + } + return nullExtent; +} + +// --------------------------------------------------------------------------- + +static metadata::ExtentPtr +getExtent(const std::vector<CoordinateOperationNNPtr> &ops, + bool conversionExtentIsWorld, bool &emptyIntersection) { + metadata::ExtentPtr res = nullptr; + for (const auto &subop : ops) { + + const auto &subExtent = + getExtent(subop, conversionExtentIsWorld, emptyIntersection); + if (!subExtent) { + if (emptyIntersection) { + return nullptr; + } + continue; + } + if (res == nullptr) { + res = subExtent; + } else { + res = res->intersection(NN_NO_CHECK(subExtent)); + if (!res) { + emptyIntersection = true; + return nullptr; + } + } + } + emptyIntersection = false; + return res; +} + +// --------------------------------------------------------------------------- + +static double getPseudoArea(const metadata::ExtentPtr &extent) { + if (!extent) + return 0.0; + const auto &geographicElements = extent->geographicElements(); + if (geographicElements.empty()) + return 0.0; + auto bbox = dynamic_cast<const metadata::GeographicBoundingBox *>( + geographicElements[0].get()); + if (!bbox) + return 0; + double w = bbox->westBoundLongitude(); + double s = bbox->southBoundLatitude(); + double e = bbox->eastBoundLongitude(); + double n = bbox->northBoundLatitude(); + if (w > e) { + e += 360.0; + } + // Integrate cos(lat) between south_lat and north_lat + return (e - w) * (std::sin(common::Angle(n).getSIValue()) - + std::sin(common::Angle(s).getSIValue())); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CoordinateOperation::Private { + util::optional<std::string> operationVersion_{}; + std::vector<metadata::PositionalAccuracyNNPtr> + coordinateOperationAccuracies_{}; + std::weak_ptr<crs::CRS> sourceCRSWeak_{}; + std::weak_ptr<crs::CRS> targetCRSWeak_{}; + crs::CRSPtr interpolationCRS_{}; + util::optional<common::DataEpoch> sourceCoordinateEpoch_{}; + util::optional<common::DataEpoch> targetCoordinateEpoch_{}; + + // do not set this for a ProjectedCRS.definingConversion + struct CRSStrongRef { + crs::CRSNNPtr sourceCRS_; + crs::CRSNNPtr targetCRS_; + CRSStrongRef(const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn) + : sourceCRS_(sourceCRSIn), targetCRS_(targetCRSIn) {} + }; + std::unique_ptr<CRSStrongRef> strongRef_{}; + + Private() = default; + Private(const Private &other) + : operationVersion_(other.operationVersion_), + coordinateOperationAccuracies_(other.coordinateOperationAccuracies_), + sourceCRSWeak_(other.sourceCRSWeak_), + targetCRSWeak_(other.targetCRSWeak_), + interpolationCRS_(other.interpolationCRS_), + sourceCoordinateEpoch_(other.sourceCoordinateEpoch_), + targetCoordinateEpoch_(other.targetCoordinateEpoch_), + strongRef_(other.strongRef_ ? internal::make_unique<CRSStrongRef>( + *(other.strongRef_)) + : nullptr) {} + + Private &operator=(const Private &) = delete; +}; + +// --------------------------------------------------------------------------- + +GridDescription::GridDescription() = default; + +GridDescription::~GridDescription() = default; + +GridDescription::GridDescription(const GridDescription &) = default; + +GridDescription::GridDescription(GridDescription &&) noexcept = default; + +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateOperation::CoordinateOperation() + : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +CoordinateOperation::CoordinateOperation(const CoordinateOperation &other) + : ObjectUsage(other), d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateOperation::~CoordinateOperation() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the version of the coordinate transformation (i.e. + * instantiation + * due to the stochastic nature of the parameters). + * + * Mandatory when describing a coordinate transformation or point motion + * operation, and should not be supplied for a coordinate conversion. + * + * @return version or empty. + */ +const util::optional<std::string> & +CoordinateOperation::operationVersion() const { + return d->operationVersion_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return estimate(s) of the impact of this coordinate operation on + * point accuracy. + * + * Gives position error estimates for target coordinates of this coordinate + * operation, assuming no errors in source coordinates. + * + * @return estimate(s) or empty vector. + */ +const std::vector<metadata::PositionalAccuracyNNPtr> & +CoordinateOperation::coordinateOperationAccuracies() const { + return d->coordinateOperationAccuracies_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the source CRS of this coordinate operation. + * + * This should not be null, expect for of a derivingConversion of a DerivedCRS + * when the owning DerivedCRS has been destroyed. + * + * @return source CRS, or null. + */ +const crs::CRSPtr CoordinateOperation::sourceCRS() const { + return d->sourceCRSWeak_.lock(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the target CRS of this coordinate operation. + * + * This should not be null, expect for of a derivingConversion of a DerivedCRS + * when the owning DerivedCRS has been destroyed. + * + * @return target CRS, or null. + */ +const crs::CRSPtr CoordinateOperation::targetCRS() const { + return d->targetCRSWeak_.lock(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the interpolation CRS of this coordinate operation. + * + * @return interpolation CRS, or null. + */ +const crs::CRSPtr &CoordinateOperation::interpolationCRS() const { + return d->interpolationCRS_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the source epoch of coordinates. + * + * @return source epoch of coordinates, or empty. + */ +const util::optional<common::DataEpoch> & +CoordinateOperation::sourceCoordinateEpoch() const { + return d->sourceCoordinateEpoch_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the target epoch of coordinates. + * + * @return target epoch of coordinates, or empty. + */ +const util::optional<common::DataEpoch> & +CoordinateOperation::targetCoordinateEpoch() const { + return d->targetCoordinateEpoch_; +} + +// --------------------------------------------------------------------------- + +void CoordinateOperation::setWeakSourceTargetCRS( + std::weak_ptr<crs::CRS> sourceCRSIn, std::weak_ptr<crs::CRS> targetCRSIn) { + d->sourceCRSWeak_ = sourceCRSIn; + d->targetCRSWeak_ = targetCRSIn; +} + +// --------------------------------------------------------------------------- + +void CoordinateOperation::setCRSs(const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, + const crs::CRSPtr &interpolationCRSIn) { + d->strongRef_ = + internal::make_unique<Private::CRSStrongRef>(sourceCRSIn, targetCRSIn); + d->sourceCRSWeak_ = sourceCRSIn.as_nullable(); + d->targetCRSWeak_ = targetCRSIn.as_nullable(); + d->interpolationCRS_ = interpolationCRSIn; +} +// --------------------------------------------------------------------------- + +void CoordinateOperation::setCRSs(const CoordinateOperation *in, + bool inverseSourceTarget) { + auto l_sourceCRS = in->sourceCRS(); + auto l_targetCRS = in->targetCRS(); + if (l_sourceCRS && l_targetCRS) { + auto nn_sourceCRS = NN_NO_CHECK(l_sourceCRS); + auto nn_targetCRS = NN_NO_CHECK(l_targetCRS); + if (inverseSourceTarget) { + setCRSs(nn_targetCRS, nn_sourceCRS, in->interpolationCRS()); + } else { + setCRSs(nn_sourceCRS, nn_targetCRS, in->interpolationCRS()); + } + } +} + +// --------------------------------------------------------------------------- + +void CoordinateOperation::setAccuracies( + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + d->coordinateOperationAccuracies_ = accuracies; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether a coordinate operation can be instanciated as + * a PROJ pipeline, checking in particular that referenced grids are + * available. + */ +bool CoordinateOperation::isPROJInstanciable( + const io::DatabaseContextPtr &databaseContext) const { + try { + exportToPROJString(io::PROJStringFormatter::create().get()); + } catch (const std::exception &) { + return false; + } + for (const auto &gridDesc : gridsNeeded(databaseContext)) { + if (!gridDesc.available) { + return false; + } + } + return true; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct OperationMethod::Private { + util::optional<std::string> formula_{}; + util::optional<metadata::Citation> formulaCitation_{}; + std::vector<GeneralOperationParameterNNPtr> parameters_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +OperationMethod::OperationMethod() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +OperationMethod::OperationMethod(const OperationMethod &other) + : IdentifiedObject(other), d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +OperationMethod::~OperationMethod() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the formula(s) or procedure used by this coordinate operation + * method. + * + * This may be a reference to a publication (in which case use + * formulaCitation()). + * + * Note that the operation method may not be analytic, in which case this + * attribute references or contains the procedure, not an analytic formula. + * + * @return the formula, or empty. + */ +const util::optional<std::string> &OperationMethod::formula() PROJ_CONST_DEFN { + return d->formula_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return a reference to a publication giving the formula(s) or + * procedure + * used by the coordinate operation method. + * + * @return the formula citation, or empty. + */ +const util::optional<metadata::Citation> & +OperationMethod::formulaCitation() PROJ_CONST_DEFN { + return d->formulaCitation_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the parameters of this operation method. + * + * @return the parameters. + */ +const std::vector<GeneralOperationParameterNNPtr> & +OperationMethod::parameters() PROJ_CONST_DEFN { + return d->parameters_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a operation method from a vector of + * GeneralOperationParameter. + * + * @param properties See \ref general_properties. At minimum the name should be + * defined. + * @param parameters Vector of GeneralOperationParameterNNPtr. + * @return a new OperationMethod. + */ +OperationMethodNNPtr OperationMethod::create( + const util::PropertyMap &properties, + const std::vector<GeneralOperationParameterNNPtr> ¶meters) { + OperationMethodNNPtr method( + OperationMethod::nn_make_shared<OperationMethod>()); + method->assignSelf(method); + method->setProperties(properties); + method->d->parameters_ = parameters; + return method; +} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a operation method from a vector of OperationParameter. + * + * @param properties See \ref general_properties. At minimum the name should be + * defined. + * @param parameters Vector of OperationParameterNNPtr. + * @return a new OperationMethod. + */ +OperationMethodNNPtr OperationMethod::create( + const util::PropertyMap &properties, + const std::vector<OperationParameterNNPtr> ¶meters) { + std::vector<GeneralOperationParameterNNPtr> parametersGeneral; + parametersGeneral.reserve(parameters.size()); + for (const auto &p : parameters) { + parametersGeneral.push_back(p); + } + return create(properties, parametersGeneral); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the EPSG code, either directly, or through the name + * @return code, or 0 if not found + */ +int OperationMethod::getEPSGCode() PROJ_CONST_DEFN { + int epsg_code = IdentifiedObject::getEPSGCode(); + if (epsg_code == 0) { + const auto &l_name = nameStr(); + for (const auto &tuple : methodNameCodes) { + if (metadata::Identifier::isEquivalentName(l_name.c_str(), + tuple.name)) { + return tuple.epsg_code; + } + } + } + return epsg_code; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void OperationMethod::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::METHOD + : io::WKTConstants::PROJECTION, + !identifiers().empty()); + std::string l_name(nameStr()); + if (!isWKT2) { + const MethodMapping *mapping = getMapping(this); + if (mapping == nullptr) { + l_name = replaceAll(l_name, " ", "_"); + } else { + if (mapping->wkt1_name == nullptr) { + throw io::FormattingException( + std::string("Unsupported conversion method: ") + + mapping->wkt2_name); + } + l_name = mapping->wkt1_name; + } + } + formatter->addQuotedString(l_name); + if (formatter->outputId()) { + formatID(formatter); + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool OperationMethod::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherOM = dynamic_cast<const OperationMethod *>(other); + if (otherOM == nullptr || + !IdentifiedObject::_isEquivalentTo(other, criterion)) { + return false; + } + // TODO test formula and formulaCitation + const auto ¶ms = parameters(); + const auto &otherParams = otherOM->parameters(); + const auto paramsSize = params.size(); + if (paramsSize != otherParams.size()) { + return false; + } + if (criterion == util::IComparable::Criterion::STRICT) { + for (size_t i = 0; i < paramsSize; i++) { + if (!params[i]->_isEquivalentTo(otherParams[i].get(), criterion)) { + return false; + } + } + } else { + std::vector<bool> candidateIndices(paramsSize, true); + for (size_t i = 0; i < paramsSize; i++) { + bool found = false; + for (size_t j = 0; j < paramsSize; j++) { + if (candidateIndices[j] && + params[i]->_isEquivalentTo(otherParams[j].get(), + criterion)) { + candidateIndices[j] = false; + found = true; + break; + } + } + if (!found) { + return false; + } + } + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeneralParameterValue::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeneralParameterValue::GeneralParameterValue() : d(nullptr) {} + +// --------------------------------------------------------------------------- + +GeneralParameterValue::GeneralParameterValue(const GeneralParameterValue &) + : d(nullptr) {} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeneralParameterValue::~GeneralParameterValue() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct OperationParameterValue::Private { + OperationParameterNNPtr parameter; + ParameterValueNNPtr parameterValue; + + Private(const OperationParameterNNPtr ¶meterIn, + const ParameterValueNNPtr &valueIn) + : parameter(parameterIn), parameterValue(valueIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +OperationParameterValue::OperationParameterValue( + const OperationParameterValue &other) + : GeneralParameterValue(other), + d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +OperationParameterValue::OperationParameterValue( + const OperationParameterNNPtr ¶meterIn, + const ParameterValueNNPtr &valueIn) + : GeneralParameterValue(), + d(internal::make_unique<Private>(parameterIn, valueIn)) {} + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a OperationParameterValue. + * + * @param parameterIn Parameter (definition). + * @param valueIn Parameter value. + * @return a new OperationParameterValue. + */ +OperationParameterValueNNPtr +OperationParameterValue::create(const OperationParameterNNPtr ¶meterIn, + const ParameterValueNNPtr &valueIn) { + return OperationParameterValue::nn_make_shared<OperationParameterValue>( + parameterIn, valueIn); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +OperationParameterValue::~OperationParameterValue() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the parameter (definition) + * + * @return the parameter (definition). + */ +const OperationParameterNNPtr & +OperationParameterValue::parameter() PROJ_CONST_DEFN { + return d->parameter; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the parameter value. + * + * @return the parameter value. + */ +const ParameterValueNNPtr & +OperationParameterValue::parameterValue() PROJ_CONST_DEFN { + return d->parameterValue; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void OperationParameterValue::_exportToWKT( + // cppcheck-suppress passedByValue + io::WKTFormatter *formatter) const { + _exportToWKT(formatter, nullptr); +} + +void OperationParameterValue::_exportToWKT(io::WKTFormatter *formatter, + const MethodMapping *mapping) const { + const ParamMapping *paramMapping = + mapping ? getMapping(mapping, this) : nullptr; + if (paramMapping && paramMapping->wkt1_name == nullptr) { + return; + } + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (isWKT2 && parameterValue()->type() == ParameterValue::Type::FILENAME) { + formatter->startNode(io::WKTConstants::PARAMETERFILE, + !parameter()->identifiers().empty()); + } else { + formatter->startNode(io::WKTConstants::PARAMETER, + !parameter()->identifiers().empty()); + } + if (paramMapping) { + formatter->addQuotedString(paramMapping->wkt1_name); + } else { + formatter->addQuotedString(parameter()->nameStr()); + } + parameterValue()->_exportToWKT(formatter); + if (formatter->outputId()) { + parameter()->formatID(formatter); + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +/** Utility method used on WKT2 import to convert from abridged transformation + * to "normal" transformation parameters. + */ +bool OperationParameterValue::convertFromAbridged( + const std::string ¶mName, double &val, + const common::UnitOfMeasure *&unit, int ¶mEPSGCode) { + if (metadata::Identifier::isEquivalentName( + paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION) || + paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) { + unit = &common::UnitOfMeasure::METRE; + paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION; + return true; + } else if (metadata::Identifier::isEquivalentName( + paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION) || + paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) { + unit = &common::UnitOfMeasure::METRE; + paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION; + return true; + } else if (metadata::Identifier::isEquivalentName( + paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION) || + paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) { + unit = &common::UnitOfMeasure::METRE; + paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION; + return true; + } else if (metadata::Identifier::isEquivalentName( + paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_ROTATION) || + paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_ROTATION) { + unit = &common::UnitOfMeasure::ARC_SECOND; + paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_ROTATION; + return true; + } else if (metadata::Identifier::isEquivalentName( + paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_ROTATION) || + paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) { + unit = &common::UnitOfMeasure::ARC_SECOND; + paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_ROTATION; + return true; + + } else if (metadata::Identifier::isEquivalentName( + paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_ROTATION) || + paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) { + unit = &common::UnitOfMeasure::ARC_SECOND; + paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_ROTATION; + return true; + + } else if (metadata::Identifier::isEquivalentName( + paramName.c_str(), EPSG_NAME_PARAMETER_SCALE_DIFFERENCE) || + paramEPSGCode == EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) { + val = (val - 1.0) * 1e6; + unit = &common::UnitOfMeasure::PARTS_PER_MILLION; + paramEPSGCode = EPSG_CODE_PARAMETER_SCALE_DIFFERENCE; + return true; + } + return false; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool OperationParameterValue::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherOPV = dynamic_cast<const OperationParameterValue *>(other); + if (otherOPV == nullptr) { + return false; + } + return d->parameter->_isEquivalentTo(otherOPV->d->parameter.get(), + criterion) && + d->parameterValue->_isEquivalentTo(otherOPV->d->parameterValue.get(), + criterion); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeneralOperationParameter::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +GeneralOperationParameter::GeneralOperationParameter() : d(nullptr) {} + +// --------------------------------------------------------------------------- + +GeneralOperationParameter::GeneralOperationParameter( + const GeneralOperationParameter &other) + : IdentifiedObject(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeneralOperationParameter::~GeneralOperationParameter() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct OperationParameter::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +OperationParameter::OperationParameter() : d(nullptr) {} + +// --------------------------------------------------------------------------- + +OperationParameter::OperationParameter(const OperationParameter &other) + : GeneralOperationParameter(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +OperationParameter::~OperationParameter() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instantiate a OperationParameter. + * + * @param properties See \ref general_properties. At minimum the name should be + * defined. + * @return a new OperationParameter. + */ +OperationParameterNNPtr +OperationParameter::create(const util::PropertyMap &properties) { + OperationParameterNNPtr op( + OperationParameter::nn_make_shared<OperationParameter>()); + op->assignSelf(op); + op->setProperties(properties); + return op; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool OperationParameter::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherOP = dynamic_cast<const OperationParameter *>(other); + return otherOP != nullptr && + IdentifiedObject::_isEquivalentTo(other, criterion); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void OperationParameter::_exportToWKT(io::WKTFormatter *) const {} + +// --------------------------------------------------------------------------- + +/** \brief Return the name of a parameter designed by its EPSG code + * @return name, or nullptr if not found + */ +const char *OperationParameter::getNameForEPSGCode(int epsg_code) noexcept { + for (const auto &tuple : paramNameCodes) { + if (tuple.epsg_code == epsg_code) { + return tuple.name; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the EPSG code, either directly, or through the name + * @return code, or 0 if not found + */ +int OperationParameter::getEPSGCode() PROJ_CONST_DEFN { + int epsg_code = IdentifiedObject::getEPSGCode(); + if (epsg_code == 0) { + const auto &l_name = nameStr(); + for (const auto &tuple : paramNameCodes) { + if (metadata::Identifier::isEquivalentName(l_name.c_str(), + tuple.name)) { + return tuple.epsg_code; + } + } + } + return epsg_code; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct SingleOperation::Private { + std::vector<GeneralParameterValueNNPtr> parameterValues_{}; + OperationMethodNNPtr method_; + + explicit Private(const OperationMethodNNPtr &methodIn) + : method_(methodIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +SingleOperation::SingleOperation(const OperationMethodNNPtr &methodIn) + : d(internal::make_unique<Private>(methodIn)) {} + +// --------------------------------------------------------------------------- + +SingleOperation::SingleOperation(const SingleOperation &other) + : +#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) + CoordinateOperation(other), +#endif + d(internal::make_unique<Private>(*other.d)) { +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +SingleOperation::~SingleOperation() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the parameter values. + * + * @return the parameter values. + */ +const std::vector<GeneralParameterValueNNPtr> & +SingleOperation::parameterValues() PROJ_CONST_DEFN { + return d->parameterValues_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the operation method associated to the operation. + * + * @return the operation method. + */ +const OperationMethodNNPtr &SingleOperation::method() PROJ_CONST_DEFN { + return d->method_; +} + +// --------------------------------------------------------------------------- + +void SingleOperation::setParameterValues( + const std::vector<GeneralParameterValueNNPtr> &values) { + d->parameterValues_ = values; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const ParameterValuePtr nullParameterValue; +//! @endcond + +/** \brief Return the parameter value corresponding to a parameter name or + * EPSG code + * + * @param paramName the parameter name (or empty, in which case epsg_code + * should be non zero) + * @param epsg_code the parameter EPSG code (possibly zero) + * @return the value, or nullptr if not found. + */ +const ParameterValuePtr & +SingleOperation::parameterValue(const std::string ¶mName, + int epsg_code) const noexcept { + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = opParamvalue->parameter(); + if ((epsg_code != 0 && parameter->getEPSGCode() == epsg_code) || + ci_equal(paramName, parameter->nameStr())) { + return opParamvalue->parameterValue(); + } + } + } + return nullParameterValue; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the parameter value corresponding to a EPSG code + * + * @param epsg_code the parameter EPSG code + * @return the value, or nullptr if not found. + */ +const ParameterValuePtr &SingleOperation::parameterValue(int epsg_code) const + noexcept { + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = opParamvalue->parameter(); + if (parameter->getEPSGCode() == epsg_code) { + return opParamvalue->parameterValue(); + } + } + } + return nullParameterValue; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const common::Measure nullMeasure; +//! @endcond + +/** \brief Return the parameter value, as a measure, corresponding to a + * parameter name or EPSG code + * + * @param paramName the parameter name (or empty, in which case epsg_code + * should be non zero) + * @param epsg_code the parameter EPSG code (possibly zero) + * @return the measure, or the empty Measure() object if not found. + */ +const common::Measure & +SingleOperation::parameterValueMeasure(const std::string ¶mName, + int epsg_code) const noexcept { + const auto &val = parameterValue(paramName, epsg_code); + if (val && val->type() == ParameterValue::Type::MEASURE) { + return val->value(); + } + return nullMeasure; +} + +/** \brief Return the parameter value, as a measure, corresponding to a + * EPSG code + * + * @param epsg_code the parameter EPSG code + * @return the measure, or the empty Measure() object if not found. + */ +const common::Measure & +SingleOperation::parameterValueMeasure(int epsg_code) const noexcept { + const auto &val = parameterValue(epsg_code); + if (val && val->type() == ParameterValue::Type::MEASURE) { + return val->value(); + } + return nullMeasure; +} + +//! @cond Doxygen_Suppress + +double SingleOperation::parameterValueNumericAsSI(int epsg_code) const + noexcept { + const auto &val = parameterValue(epsg_code); + if (val && val->type() == ParameterValue::Type::MEASURE) { + return val->value().getSIValue(); + } + return 0.0; +} + +double SingleOperation::parameterValueNumeric( + int epsg_code, const common::UnitOfMeasure &targetUnit) const noexcept { + const auto &val = parameterValue(epsg_code); + if (val && val->type() == ParameterValue::Type::MEASURE) { + return val->value().convertToUnit(targetUnit); + } + return 0.0; +} + +//! @endcond +// --------------------------------------------------------------------------- + +/** \brief Instanciate a PROJ-based single operation. + * + * \note The operation might internally be a pipeline chaining several + * operations. + * The use of the SingleOperation modeling here is mostly to be able to get + * the PROJ string as a parameter. + * + * @param properties Properties + * @param PROJString the PROJ string. + * @param sourceCRS source CRS (might be null). + * @param targetCRS target CRS (might be null). + * @param accuracies Vector of positional accuracy (might be empty). + * @return the new instance + */ +SingleOperationNNPtr SingleOperation::createPROJBased( + const util::PropertyMap &properties, const std::string &PROJString, + const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return util::nn_static_pointer_cast<SingleOperation>( + PROJBasedOperation::create(properties, PROJString, sourceCRS, targetCRS, + accuracies)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static SingleOperationNNPtr createPROJBased( + const util::PropertyMap &properties, + const io::IPROJStringExportableNNPtr &projExportable, + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies = + std::vector<metadata::PositionalAccuracyNNPtr>()) { + return util::nn_static_pointer_cast<SingleOperation>( + PROJBasedOperation::create(properties, projExportable, false, sourceCRS, + targetCRS, accuracies)); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool SingleOperation::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherSO = dynamic_cast<const SingleOperation *>(other); + if (otherSO == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !ObjectUsage::_isEquivalentTo(other, criterion))) { + return false; + } + + const int methodEPSGCode = d->method_->getEPSGCode(); + if (!d->method_->_isEquivalentTo(otherSO->d->method_.get(), criterion)) { + if (criterion == util::IComparable::Criterion::EQUIVALENT) { + // _1SP methods can sometimes be equivalent to _2SP ones + // Check it by using convertToOtherMethod() + + const int otherMethodEPSGCode = otherSO->d->method_->getEPSGCode(); + if (methodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP && + otherMethodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { + // Convert from 2SP to 1SP as the other direction has more + // degree of liberties. + return otherSO->_isEquivalentTo(this, criterion); + } else if ((methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A && + otherMethodEPSGCode == + EPSG_CODE_METHOD_MERCATOR_VARIANT_B) || + (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && + otherMethodEPSGCode == + EPSG_CODE_METHOD_MERCATOR_VARIANT_A) || + (methodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP && + otherMethodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)) { + auto conv = dynamic_cast<const Conversion *>(this); + if (conv) { + auto eqConv = + conv->convertToOtherMethod(otherMethodEPSGCode); + if (eqConv) { + return eqConv->_isEquivalentTo(other, criterion); + } + } + } + } + + return false; + } + + const auto &values = d->parameterValues_; + const auto &otherValues = otherSO->d->parameterValues_; + const auto valuesSize = values.size(); + if (valuesSize != otherValues.size()) { + return false; + } + if (criterion == util::IComparable::Criterion::STRICT) { + for (size_t i = 0; i < valuesSize; i++) { + if (!values[i]->_isEquivalentTo(otherValues[i].get(), criterion)) { + return false; + } + } + return true; + } + + std::vector<bool> candidateIndices(valuesSize, true); + bool equivalent = true; + for (size_t i = 0; i < valuesSize; i++) { + bool found = false; + for (size_t j = 0; j < valuesSize; j++) { + if (candidateIndices[j] && + values[i]->_isEquivalentTo(otherValues[j].get(), criterion)) { + candidateIndices[j] = false; + found = true; + break; + } + } + if (!found) { + equivalent = false; + if (methodEPSGCode == + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { + // For LCC_2SP, the standard parallels can be switched and + // this will result in the same result. + auto opParamvalue = + dynamic_cast<const OperationParameterValue *>( + values[i].get()); + if (opParamvalue) { + const int paramEPSGCode = + opParamvalue->parameter()->getEPSGCode(); + if (paramEPSGCode == + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL || + paramEPSGCode == + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) { + auto value_1st = parameterValue( + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL); + auto value_2nd = parameterValue( + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL); + if (value_1st && value_2nd) { + equivalent = + value_1st->_isEquivalentTo( + otherSO + ->parameterValue( + EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) + .get(), + criterion) && + value_2nd->_isEquivalentTo( + otherSO + ->parameterValue( + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) + .get(), + criterion); + } + } + } + } + if (!equivalent) { + break; + } + } + } + + // Equivalent formulations of 2SP can have different parameters + // Then convert to 1SP and compare. + if (!equivalent && + methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { + auto conv = dynamic_cast<const Conversion *>(this); + auto otherConv = dynamic_cast<const Conversion *>(other); + if (conv && otherConv) { + auto thisAs1SP = conv->convertToOtherMethod( + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); + auto otherAs1SP = otherConv->convertToOtherMethod( + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); + if (thisAs1SP && otherAs1SP) { + equivalent = + thisAs1SP->_isEquivalentTo(otherAs1SP.get(), criterion); + } + } + } + return equivalent; +} +//! @endcond + +// --------------------------------------------------------------------------- + +std::set<GridDescription> SingleOperation::gridsNeeded( + const io::DatabaseContextPtr &databaseContext) const { + std::set<GridDescription> res; + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto &value = opParamvalue->parameterValue(); + if (value->type() == ParameterValue::Type::FILENAME) { + GridDescription desc; + desc.shortName = value->valueFile(); + if (databaseContext) { + databaseContext->lookForGridInfo( + desc.shortName, desc.fullName, desc.packageName, + desc.url, desc.directDownload, desc.openLicense, + desc.available); + } + res.insert(desc); + } + } + } + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ParameterValue::Private { + ParameterValue::Type type_{ParameterValue::Type::STRING}; + std::unique_ptr<common::Measure> measure_{}; + std::unique_ptr<std::string> stringValue_{}; + int integerValue_{}; + bool booleanValue_{}; + + explicit Private(const common::Measure &valueIn) + : type_(ParameterValue::Type::MEASURE), + measure_(internal::make_unique<common::Measure>(valueIn)) {} + + Private(const std::string &stringValueIn, ParameterValue::Type typeIn) + : type_(typeIn), + stringValue_(internal::make_unique<std::string>(stringValueIn)) {} + + explicit Private(int integerValueIn) + : type_(ParameterValue::Type::INTEGER), integerValue_(integerValueIn) {} + + explicit Private(bool booleanValueIn) + : type_(ParameterValue::Type::BOOLEAN), booleanValue_(booleanValueIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ParameterValue::~ParameterValue() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +ParameterValue::ParameterValue(const common::Measure &measureIn) + : d(internal::make_unique<Private>(measureIn)) {} + +// --------------------------------------------------------------------------- + +ParameterValue::ParameterValue(const std::string &stringValueIn, + ParameterValue::Type typeIn) + : d(internal::make_unique<Private>(stringValueIn, typeIn)) {} + +// --------------------------------------------------------------------------- + +ParameterValue::ParameterValue(int integerValueIn) + : d(internal::make_unique<Private>(integerValueIn)) {} + +// --------------------------------------------------------------------------- + +ParameterValue::ParameterValue(bool booleanValueIn) + : d(internal::make_unique<Private>(booleanValueIn)) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ParameterValue from a Measure (i.e. a value associated + * with a + * unit) + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr ParameterValue::create(const common::Measure &measureIn) { + return ParameterValue::nn_make_shared<ParameterValue>(measureIn); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ParameterValue from a string value. + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr ParameterValue::create(const char *stringValueIn) { + return ParameterValue::nn_make_shared<ParameterValue>( + std::string(stringValueIn), ParameterValue::Type::STRING); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ParameterValue from a string value. + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr ParameterValue::create(const std::string &stringValueIn) { + return ParameterValue::nn_make_shared<ParameterValue>( + stringValueIn, ParameterValue::Type::STRING); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ParameterValue from a filename. + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr +ParameterValue::createFilename(const std::string &stringValueIn) { + return ParameterValue::nn_make_shared<ParameterValue>( + stringValueIn, ParameterValue::Type::FILENAME); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ParameterValue from a integer value. + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr ParameterValue::create(int integerValueIn) { + return ParameterValue::nn_make_shared<ParameterValue>(integerValueIn); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ParameterValue from a boolean value. + * + * @return a new ParameterValue. + */ +ParameterValueNNPtr ParameterValue::create(bool booleanValueIn) { + return ParameterValue::nn_make_shared<ParameterValue>(booleanValueIn); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the type of a parameter value. + * + * @return the type. + */ +const ParameterValue::Type &ParameterValue::type() PROJ_CONST_DEFN { + return d->type_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the value as a Measure (assumes type() == Type::MEASURE) + * @return the value as a Measure. + */ +const common::Measure &ParameterValue::value() PROJ_CONST_DEFN { + return *d->measure_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the value as a string (assumes type() == Type::STRING) + * @return the value as a string. + */ +const std::string &ParameterValue::stringValue() PROJ_CONST_DEFN { + return *d->stringValue_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the value as a filename (assumes type() == Type::FILENAME) + * @return the value as a filename. + */ +const std::string &ParameterValue::valueFile() PROJ_CONST_DEFN { + return *d->stringValue_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the value as a integer (assumes type() == Type::INTEGER) + * @return the value as a integer. + */ +int ParameterValue::integerValue() PROJ_CONST_DEFN { return d->integerValue_; } + +// --------------------------------------------------------------------------- + +/** \brief Returns the value as a boolean (assumes type() == Type::BOOLEAN) + * @return the value as a boolean. + */ +bool ParameterValue::booleanValue() PROJ_CONST_DEFN { return d->booleanValue_; } + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ParameterValue::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + + const auto &l_type = type(); + const auto &l_value = value(); + if (formatter->abridgedTransformation() && l_type == Type::MEASURE) { + const auto &unit = l_value.unit(); + const auto &unitType = unit.type(); + if (unitType == common::UnitOfMeasure::Type::LINEAR) { + formatter->add(l_value.getSIValue()); + } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) { + formatter->add( + l_value.convertToUnit(common::UnitOfMeasure::ARC_SECOND)); + } else if (unit == common::UnitOfMeasure::PARTS_PER_MILLION) { + formatter->add(1.0 + l_value.value() * 1e-6); + } else { + formatter->add(l_value.value()); + } + } else if (l_type == Type::MEASURE) { + const auto &unit = l_value.unit(); + if (isWKT2) { + formatter->add(l_value.value()); + } else { + // In WKT1, as we don't output the natural unit, output to the + // registered linear / angular unit. + const auto &unitType = unit.type(); + if (unitType == common::UnitOfMeasure::Type::LINEAR) { + formatter->add( + l_value.convertToUnit(*(formatter->axisLinearUnit()))); + } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) { + formatter->add( + l_value.convertToUnit(*(formatter->axisAngularUnit()))); + } else { + formatter->add(l_value.getSIValue()); + } + } + if (isWKT2 && unit != common::UnitOfMeasure::NONE) { + if (!formatter->primeMeridianOrParameterUnitOmittedIfSameAsAxis() || + (unit != common::UnitOfMeasure::SCALE_UNITY && + unit != *(formatter->axisLinearUnit()) && + unit != *(formatter->axisAngularUnit()))) { + unit._exportToWKT(formatter); + } + } + } else if (l_type == Type::STRING || l_type == Type::FILENAME) { + formatter->addQuotedString(stringValue()); + } else if (l_type == Type::INTEGER) { + formatter->add(integerValue()); + } else { + throw io::FormattingException("boolean parameter value not handled"); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool ParameterValue::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherPV = dynamic_cast<const ParameterValue *>(other); + if (otherPV == nullptr) { + return false; + } + if (type() != otherPV->type()) { + return false; + } + switch (type()) { + case Type::MEASURE: { + return value()._isEquivalentTo(otherPV->value(), criterion); + } + + case Type::STRING: + case Type::FILENAME: { + return stringValue() == otherPV->stringValue(); + } + + case Type::INTEGER: { + return integerValue() == otherPV->integerValue(); + } + + case Type::BOOLEAN: { + return booleanValue() == otherPV->booleanValue(); + } + + default: { + assert(false); + break; + } + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Conversion::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +Conversion::Conversion(const OperationMethodNNPtr &methodIn, + const std::vector<GeneralParameterValueNNPtr> &values) + : SingleOperation(methodIn), d(nullptr) { + setParameterValues(values); +} + +// --------------------------------------------------------------------------- + +Conversion::Conversion(const Conversion &other) + : CoordinateOperation(other), SingleOperation(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Conversion::~Conversion() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ConversionNNPtr Conversion::shallowClone() const { + auto conv = Conversion::nn_make_shared<Conversion>(*this); + conv->assignSelf(conv); + conv->setCRSs(this, false); + return conv; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Conversion from a vector of GeneralParameterValue. + * + * @param properties See \ref general_properties. At minimum the name should be + * defined. + * @param methodIn the operation method. + * @param values the values. + * @return a new Conversion. + * @throws InvalidOperation + */ +ConversionNNPtr Conversion::create(const util::PropertyMap &properties, + const OperationMethodNNPtr &methodIn, + const std::vector<GeneralParameterValueNNPtr> + &values) // throw InvalidOperation +{ + if (methodIn->parameters().size() != values.size()) { + throw InvalidOperation( + "Inconsistent number of parameters and parameter values"); + } + auto conv = Conversion::nn_make_shared<Conversion>(methodIn, values); + conv->assignSelf(conv); + conv->setProperties(properties); + return conv; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Conversion and its OperationMethod + * + * @param propertiesConversion See \ref general_properties of the conversion. + * At minimum the name should be defined. + * @param propertiesOperationMethod See \ref general_properties of the operation + * method. At minimum the name should be defined. + * @param parameters the operation parameters. + * @param values the operation values. Constraint: + * values.size() == parameters.size() + * @return a new Conversion. + * @throws InvalidOperation + */ +ConversionNNPtr Conversion::create( + const util::PropertyMap &propertiesConversion, + const util::PropertyMap &propertiesOperationMethod, + const std::vector<OperationParameterNNPtr> ¶meters, + const std::vector<ParameterValueNNPtr> &values) // throw InvalidOperation +{ + OperationMethodNNPtr op( + OperationMethod::create(propertiesOperationMethod, parameters)); + + if (parameters.size() != values.size()) { + throw InvalidOperation( + "Inconsistent number of parameters and parameter values"); + } + std::vector<GeneralParameterValueNNPtr> generalParameterValues; + generalParameterValues.reserve(values.size()); + for (size_t i = 0; i < values.size(); i++) { + generalParameterValues.push_back( + OperationParameterValue::create(parameters[i], values[i])); + } + return create(propertiesConversion, op, generalParameterValues); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +static util::PropertyMap createMapNameEPSGCode(const std::string &name, + int code) { + return util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, name) + .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, code); +} + +// --------------------------------------------------------------------------- + +static util::PropertyMap createMapNameEPSGCode(const char *name, int code) { + return util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, name) + .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, code); +} + +// --------------------------------------------------------------------------- + +static util::PropertyMap createMethodMapNameEPSGCode(int code) { + const char *name = nullptr; + for (const auto &tuple : methodNameCodes) { + if (tuple.epsg_code == code) { + name = tuple.name; + break; + } + } + assert(name); + return createMapNameEPSGCode(name, code); +} + +// --------------------------------------------------------------------------- + +static util::PropertyMap +getUTMConversionProperty(const util::PropertyMap &properties, int zone, + bool north) { + if (properties.find(common::IdentifiedObject::NAME_KEY) == + properties.end()) { + std::string conversionName("UTM zone "); + conversionName += toString(zone); + conversionName += (north ? 'N' : 'S'); + + return createMapNameEPSGCode(conversionName, + (north ? 16000 : 17000) + zone); + } else { + return properties; + } +} + +// --------------------------------------------------------------------------- + +static util::PropertyMap +addDefaultNameIfNeeded(const util::PropertyMap &properties, + const std::string &defaultName) { + if (properties.find(common::IdentifiedObject::NAME_KEY) == + properties.end()) { + return util::PropertyMap(properties) + .set(common::IdentifiedObject::NAME_KEY, defaultName); + } else { + return properties; + } +} + +// --------------------------------------------------------------------------- + +static ConversionNNPtr +createConversion(const util::PropertyMap &properties, + const MethodMapping *mapping, + const std::vector<ParameterValueNNPtr> &values) { + + std::vector<OperationParameterNNPtr> parameters; + for (int i = 0; mapping->params[i] != nullptr; i++) { + const auto *param = mapping->params[i]; + auto paramProperties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, param->wkt2_name); + if (param->epsg_code != 0) { + paramProperties + .set(metadata::Identifier::CODESPACE_KEY, + metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, param->epsg_code); + } + auto parameter = OperationParameter::create(paramProperties); + parameters.push_back(parameter); + } + + auto methodProperties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, mapping->wkt2_name); + if (mapping->epsg_code != 0) { + methodProperties + .set(metadata::Identifier::CODESPACE_KEY, + metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, mapping->epsg_code); + } + return Conversion::create( + addDefaultNameIfNeeded(properties, mapping->wkt2_name), + methodProperties, parameters, values); +} +//! @endcond + +// --------------------------------------------------------------------------- + +ConversionNNPtr +Conversion::create(const util::PropertyMap &properties, int method_epsg_code, + const std::vector<ParameterValueNNPtr> &values) { + const MethodMapping *mapping = getMapping(method_epsg_code); + assert(mapping); + return createConversion(properties, mapping, values); +} + +// --------------------------------------------------------------------------- + +ConversionNNPtr +Conversion::create(const util::PropertyMap &properties, + const char *method_wkt2_name, + const std::vector<ParameterValueNNPtr> &values) { + const MethodMapping *mapping = getMapping(method_wkt2_name); + assert(mapping); + return createConversion(properties, mapping, values); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +struct VectorOfParameters : public std::vector<OperationParameterNNPtr> { + VectorOfParameters() : std::vector<OperationParameterNNPtr>() {} + explicit VectorOfParameters( + std::initializer_list<OperationParameterNNPtr> list) + : std::vector<OperationParameterNNPtr>(list) {} + VectorOfParameters(const VectorOfParameters &) = delete; + + ~VectorOfParameters(); +}; + +// This way, we disable inlining of destruction, and save a lot of space +VectorOfParameters::~VectorOfParameters() = default; + +struct VectorOfValues : public std::vector<ParameterValueNNPtr> { + VectorOfValues() : std::vector<ParameterValueNNPtr>() {} + explicit VectorOfValues(std::initializer_list<ParameterValueNNPtr> list) + : std::vector<ParameterValueNNPtr>(list) {} + + explicit VectorOfValues(std::initializer_list<common::Measure> list); + VectorOfValues(const VectorOfValues &) = delete; + VectorOfValues(VectorOfValues &&) = default; + + ~VectorOfValues(); +}; + +static std::vector<ParameterValueNNPtr> buildParameterValueFromMeasure( + const std::initializer_list<common::Measure> &list) { + std::vector<ParameterValueNNPtr> res; + for (const auto &v : list) { + res.emplace_back(ParameterValue::create(v)); + } + return res; +} + +VectorOfValues::VectorOfValues(std::initializer_list<common::Measure> list) + : std::vector<ParameterValueNNPtr>(buildParameterValueFromMeasure(list)) {} + +// This way, we disable inlining of destruction, and save a lot of space +VectorOfValues::~VectorOfValues() = default; + +PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1, + const common::Measure &m2, + const common::Measure &m3) { + return VectorOfValues{ParameterValue::create(m1), + ParameterValue::create(m2), + ParameterValue::create(m3)}; +} + +PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1, + const common::Measure &m2, + const common::Measure &m3, + const common::Measure &m4) { + return VectorOfValues{ + ParameterValue::create(m1), ParameterValue::create(m2), + ParameterValue::create(m3), ParameterValue::create(m4)}; +} + +PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1, + const common::Measure &m2, + const common::Measure &m3, + const common::Measure &m4, + const common::Measure &m5) { + return VectorOfValues{ + ParameterValue::create(m1), ParameterValue::create(m2), + ParameterValue::create(m3), ParameterValue::create(m4), + ParameterValue::create(m5), + }; +} + +PROJ_NO_INLINE static VectorOfValues +createParams(const common::Measure &m1, const common::Measure &m2, + const common::Measure &m3, const common::Measure &m4, + const common::Measure &m5, const common::Measure &m6) { + return VectorOfValues{ + ParameterValue::create(m1), ParameterValue::create(m2), + ParameterValue::create(m3), ParameterValue::create(m4), + ParameterValue::create(m5), ParameterValue::create(m6), + }; +} + +PROJ_NO_INLINE static VectorOfValues +createParams(const common::Measure &m1, const common::Measure &m2, + const common::Measure &m3, const common::Measure &m4, + const common::Measure &m5, const common::Measure &m6, + const common::Measure &m7) { + return VectorOfValues{ + ParameterValue::create(m1), ParameterValue::create(m2), + ParameterValue::create(m3), ParameterValue::create(m4), + ParameterValue::create(m5), ParameterValue::create(m6), + ParameterValue::create(m7), + }; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a [Universal Transverse Mercator] + *(https://proj4.org/operations/projections/utm.html) conversion. + * + * UTM is a family of conversions, of EPSG codes from 16001 to 16060 for the + * northern hemisphere, and 17001 t 17060 for the southern hemisphere, + * based on the Transverse Mercator projection method. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param zone UTM zone number between 1 and 60. + * @param north true for UTM northern hemisphere, false for UTM southern + * hemisphere. + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createUTM(const util::PropertyMap &properties, + int zone, bool north) { + return create( + getUTMConversionProperty(properties, zone, north), + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, + createParams(common::Angle(UTM_LATITUDE_OF_NATURAL_ORIGIN), + common::Angle(zone * 6.0 - 183.0), + common::Scale(UTM_SCALE_FACTOR), + common::Length(UTM_FALSE_EASTING), + common::Length(north ? UTM_NORTH_FALSE_NORTHING + : UTM_SOUTH_FALSE_NORTHING))); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Transverse Mercator] + *(https://proj4.org/operations/projections/tmerc.html) projection method. + * + * This method is defined as [EPSG:9807] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9807) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createTransverseMercator( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Gauss Schreiber Transverse + *Mercator] + *(https://proj4.org/operations/projections/gstmerc.html) projection method. + * + * This method is also known as Gauss-Laborde Reunion. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createGaussSchreiberTransverseMercator( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, + PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Transverse Mercator South + *Orientated] + *(https://proj4.org/operations/projections/tmerc.html) projection method. + * + * This method is defined as [EPSG:9808] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9808) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createTransverseMercatorSouthOriented( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Two Point Equidistant] + *(https://proj4.org/operations/projections/tpeqd.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFirstPoint Latitude of first point. + * @param longitudeFirstPoint Longitude of first point. + * @param latitudeSecondPoint Latitude of second point. + * @param longitudeSeconPoint Longitude of second point. + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr +Conversion::createTwoPointEquidistant(const util::PropertyMap &properties, + const common::Angle &latitudeFirstPoint, + const common::Angle &longitudeFirstPoint, + const common::Angle &latitudeSecondPoint, + const common::Angle &longitudeSeconPoint, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, + createParams(latitudeFirstPoint, longitudeFirstPoint, + latitudeSecondPoint, longitudeSeconPoint, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the Tunisia Mapping Grid projection + * method. + * + * This method is defined as [EPSG:9816] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9816) + * + * \note There is currently no implementation of the method formulas in PROJ. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createTunisiaMappingGrid( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, EPSG_CODE_METHOD_TUNISIA_MAPPING_GRID, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Albers Conic Equal Area] + *(https://proj4.org/operations/projections/aea.html) projection method. + * + * This method is defined as [EPSG:9822] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9822) + * + * @note the order of arguments is conformant with the corresponding EPSG + * mode and different than OGRSpatialReference::setACEA() of GDAL <= 2.3 + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFalseOrigin See \ref latitude_false_origin + * @param longitudeFalseOrigin See \ref longitude_false_origin + * @param latitudeFirstParallel See \ref latitude_first_std_parallel + * @param latitudeSecondParallel See \ref latitude_second_std_parallel + * @param eastingFalseOrigin See \ref easting_false_origin + * @param northingFalseOrigin See \ref northing_false_origin + * @return a new Conversion. + */ +ConversionNNPtr +Conversion::createAlbersEqualArea(const util::PropertyMap &properties, + const common::Angle &latitudeFalseOrigin, + const common::Angle &longitudeFalseOrigin, + const common::Angle &latitudeFirstParallel, + const common::Angle &latitudeSecondParallel, + const common::Length &eastingFalseOrigin, + const common::Length &northingFalseOrigin) { + return create(properties, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA, + createParams(latitudeFalseOrigin, longitudeFalseOrigin, + latitudeFirstParallel, latitudeSecondParallel, + eastingFalseOrigin, northingFalseOrigin)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Lambert Conic Conformal 1SP] + *(https://proj4.org/operations/projections/lcc.html) projection method. + * + * This method is defined as [EPSG:9801] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9801) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLambertConicConformal_1SP( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Lambert Conic Conformal (2SP)] + *(https://proj4.org/operations/projections/lcc.html) projection method. + * + * This method is defined as [EPSG:9802] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9802) + * + * @note the order of arguments is conformant with the corresponding EPSG + * mode and different than OGRSpatialReference::setLCC() of GDAL <= 2.3 + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFalseOrigin See \ref latitude_false_origin + * @param longitudeFalseOrigin See \ref longitude_false_origin + * @param latitudeFirstParallel See \ref latitude_first_std_parallel + * @param latitudeSecondParallel See \ref latitude_second_std_parallel + * @param eastingFalseOrigin See \ref easting_false_origin + * @param northingFalseOrigin See \ref northing_false_origin + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLambertConicConformal_2SP( + const util::PropertyMap &properties, + const common::Angle &latitudeFalseOrigin, + const common::Angle &longitudeFalseOrigin, + const common::Angle &latitudeFirstParallel, + const common::Angle &latitudeSecondParallel, + const common::Length &eastingFalseOrigin, + const common::Length &northingFalseOrigin) { + return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, + createParams(latitudeFalseOrigin, longitudeFalseOrigin, + latitudeFirstParallel, latitudeSecondParallel, + eastingFalseOrigin, northingFalseOrigin)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Lambert Conic Conformal (2SP + *Michigan)] + *(https://proj4.org/operations/projections/lcc.html) projection method. + * + * This method is defined as [EPSG:1051] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1051) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFalseOrigin See \ref latitude_false_origin + * @param longitudeFalseOrigin See \ref longitude_false_origin + * @param latitudeFirstParallel See \ref latitude_first_std_parallel + * @param latitudeSecondParallel See \ref latitude_second_std_parallel + * @param eastingFalseOrigin See \ref easting_false_origin + * @param northingFalseOrigin See \ref northing_false_origin + * @param ellipsoidScalingFactor Ellipsoid scaling factor. + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLambertConicConformal_2SP_Michigan( + const util::PropertyMap &properties, + const common::Angle &latitudeFalseOrigin, + const common::Angle &longitudeFalseOrigin, + const common::Angle &latitudeFirstParallel, + const common::Angle &latitudeSecondParallel, + const common::Length &eastingFalseOrigin, + const common::Length &northingFalseOrigin, + const common::Scale &ellipsoidScalingFactor) { + return create(properties, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, + createParams(latitudeFalseOrigin, longitudeFalseOrigin, + latitudeFirstParallel, latitudeSecondParallel, + eastingFalseOrigin, northingFalseOrigin, + ellipsoidScalingFactor)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Lambert Conic Conformal (2SP + *Belgium)] + *(https://proj4.org/operations/projections/lcc.html) projection method. + * + * This method is defined as [EPSG:9803] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9803) + * + * \warning The formulas used currently in PROJ are, incorrectly, the ones of + * the regular LCC_2SP method. + * + * @note the order of arguments is conformant with the corresponding EPSG + * mode and different than OGRSpatialReference::setLCCB() of GDAL <= 2.3 + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFalseOrigin See \ref latitude_false_origin + * @param longitudeFalseOrigin See \ref longitude_false_origin + * @param latitudeFirstParallel See \ref latitude_first_std_parallel + * @param latitudeSecondParallel See \ref latitude_second_std_parallel + * @param eastingFalseOrigin See \ref easting_false_origin + * @param northingFalseOrigin See \ref northing_false_origin + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLambertConicConformal_2SP_Belgium( + const util::PropertyMap &properties, + const common::Angle &latitudeFalseOrigin, + const common::Angle &longitudeFalseOrigin, + const common::Angle &latitudeFirstParallel, + const common::Angle &latitudeSecondParallel, + const common::Length &eastingFalseOrigin, + const common::Length &northingFalseOrigin) { + + return create(properties, + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM, + createParams(latitudeFalseOrigin, longitudeFalseOrigin, + latitudeFirstParallel, latitudeSecondParallel, + eastingFalseOrigin, northingFalseOrigin)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Modified Azimuthal + *Equidistant] + *(https://proj4.org/operations/projections/aeqd.html) projection method. + * + * This method is defined as [EPSG:9832] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9832) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeNatOrigin See \ref center_latitude + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createAzimuthalEquidistant( + const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, + const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, + createParams(latitudeNatOrigin, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Guam Projection] + *(https://proj4.org/operations/projections/aeqd.html) projection method. + * + * This method is defined as [EPSG:9831] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9831) + * + * @param properties See \ref general_properties of the conversion. If the name + *is + * not provided, it is automatically set. + * @param latitudeNatOrigin See \ref center_latitude + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createGuamProjection( + const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, + const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_GUAM_PROJECTION, + createParams(latitudeNatOrigin, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Bonne] + *(https://proj4.org/operations/projections/bonne.html) projection method. + * + * This method is defined as [EPSG:9827] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9827) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeNatOrigin See \ref center_latitude . PROJ calls its the + * standard parallel 1. + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createBonne(const util::PropertyMap &properties, + const common::Angle &latitudeNatOrigin, + const common::Angle &longitudeNatOrigin, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_BONNE, + createParams(latitudeNatOrigin, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Lambert Cylindrical Equal Area + *(Spherical)] + *(https://proj4.org/operations/projections/cea.html) projection method. + * + * This method is defined as [EPSG:9834] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9834) + * + * \warning The PROJ cea computation code would select the ellipsoidal form if + * a non-spherical ellipsoid is used for the base GeographicalCRS. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFirstParallel See \ref latitude_first_std_parallel. + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLambertCylindricalEqualAreaSpherical( + const util::PropertyMap &properties, + const common::Angle &latitudeFirstParallel, + const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, + EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL, + createParams(latitudeFirstParallel, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Lambert Cylindrical Equal Area + *(ellipsoidal form)] + *(https://proj4.org/operations/projections/cea.html) projection method. + * + * This method is defined as [EPSG:9835] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9835) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFirstParallel See \ref latitude_first_std_parallel. + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLambertCylindricalEqualArea( + const util::PropertyMap &properties, + const common::Angle &latitudeFirstParallel, + const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, + createParams(latitudeFirstParallel, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Cassini-Soldner] + * (https://proj4.org/operations/projections/cass.html) projection method. + * + * This method is defined as [EPSG:9806] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9806) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createCassiniSoldner( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, EPSG_CODE_METHOD_CASSINI_SOLDNER, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Equidistant Conic] + *(https://proj4.org/operations/projections/eqdc.html) projection method. + * + * There is no equivalent in EPSG. + * + * @note Although not found in EPSG, the order of arguments is conformant with + * the "spirit" of EPSG and different than OGRSpatialReference::setEC() of GDAL + *<= 2.3 * @param properties See \ref general_properties of the conversion. + *If the name + * is not provided, it is automatically set. + * + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param latitudeFirstParallel See \ref latitude_first_std_parallel + * @param latitudeSecondParallel See \ref latitude_second_std_parallel + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEquidistantConic( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Angle &latitudeFirstParallel, + const common::Angle &latitudeSecondParallel, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC, + createParams(centerLat, centerLong, latitudeFirstParallel, + latitudeSecondParallel, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Eckert I] + * (https://proj4.org/operations/projections/eck1.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEckertI(const util::PropertyMap &properties, + const common::Angle ¢erLong, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_I, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Eckert II] + * (https://proj4.org/operations/projections/eck2.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEckertII( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_II, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Eckert III] + * (https://proj4.org/operations/projections/eck3.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEckertIII( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_III, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Eckert IV] + * (https://proj4.org/operations/projections/eck4.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEckertIV( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_IV, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Eckert V] + * (https://proj4.org/operations/projections/eck5.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEckertV(const util::PropertyMap &properties, + const common::Angle ¢erLong, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_V, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Eckert VI] + * (https://proj4.org/operations/projections/eck6.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEckertVI( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_VI, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Equidistant Cylindrical] + *(https://proj4.org/operations/projections/eqc.html) projection method. + * + * This is also known as the Equirectangular method, and in the particular case + * where the latitude of first parallel is 0. + * + * This method is defined as [EPSG:1028] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1028) + * + * @note This is the equivalent OGRSpatialReference::SetEquirectangular2( + * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL <= 2.3, + * where the lat_0 / center_latitude parameter is forced to 0. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFirstParallel See \ref latitude_first_std_parallel. + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEquidistantCylindrical( + const util::PropertyMap &properties, + const common::Angle &latitudeFirstParallel, + const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, + createParams(latitudeFirstParallel, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Equidistant Cylindrical + *(Spherical)] + *(https://proj4.org/operations/projections/eqc.html) projection method. + * + * This is also known as the Equirectangular method, and in the particular case + * where the latitude of first parallel is 0. + * + * This method is defined as [EPSG:1029] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1029) + * + * @note This is the equivalent OGRSpatialReference::SetEquirectangular2( + * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL <= 2.3, + * where the lat_0 / center_latitude parameter is forced to 0. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFirstParallel See \ref latitude_first_std_parallel. + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEquidistantCylindricalSpherical( + const util::PropertyMap &properties, + const common::Angle &latitudeFirstParallel, + const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, + createParams(latitudeFirstParallel, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Gall (Stereographic)] + * (https://proj4.org/operations/projections/gall.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createGall(const util::PropertyMap &properties, + const common::Angle ¢erLong, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Goode Homolosine] + * (https://proj4.org/operations/projections/goode.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createGoodeHomolosine( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Interrupted Goode Homolosine] + * (https://proj4.org/operations/projections/igh.html) projection method. + * + * There is no equivalent in EPSG. + * + * @note OGRSpatialReference::SetIGH() of GDAL <= 2.3 assumes the 3 + * projection + * parameters to be zero and this is the nominal case. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createInterruptedGoodeHomolosine( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, + PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Geostationary Satellite View] + * (https://proj4.org/operations/projections/geos.html) projection method, + * with the sweep angle axis of the viewing instrument being x + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param height Height of the view point above the Earth. + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createGeostationarySatelliteSweepX( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &height, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X, + createParams(centerLong, height, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Geostationary Satellite View] + * (https://proj4.org/operations/projections/geos.html) projection method, + * with the sweep angle axis of the viewing instrument being y. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param height Height of the view point above the Earth. + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createGeostationarySatelliteSweepY( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &height, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y, + createParams(centerLong, height, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Gnomonic] + *(https://proj4.org/operations/projections/gnom.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createGnomonic( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, PROJ_WKT2_NAME_METHOD_GNOMONIC, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Hotine Oblique Mercator + *(Variant A)] + *(https://proj4.org/operations/projections/omerc.html) projection method + * + * This is the variant with the no_uoff parameter, which corresponds to + * GDAL >=2.3 Hotine_Oblique_Mercator projection. + * In this variant, the false grid coordinates are + * defined at the intersection of the initial line and the aposphere (the + * equator on one of the intermediate surfaces inherent in the method), that is + * at the natural origin of the coordinate system). + * + * This method is defined as [EPSG:9812] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9812) + * + * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid = + *90deg, + * this maps to the [Swiss Oblique Mercator] + *(https://proj4.org/operations/projections/somerc.html) formulas. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeProjectionCentre See \ref latitude_projection_centre + * @param longitudeProjectionCentre See \ref longitude_projection_centre + * @param azimuthInitialLine See \ref azimuth_initial_line + * @param angleFromRectifiedToSkrewGrid See + * \ref angle_from_recitfied_to_skrew_grid + * @param scale See \ref scale_factor_initial_line + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createHotineObliqueMercatorVariantA( + const util::PropertyMap &properties, + const common::Angle &latitudeProjectionCentre, + const common::Angle &longitudeProjectionCentre, + const common::Angle &azimuthInitialLine, + const common::Angle &angleFromRectifiedToSkrewGrid, + const common::Scale &scale, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, + createParams(latitudeProjectionCentre, longitudeProjectionCentre, + azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Hotine Oblique Mercator + *(Variant B)] + *(https://proj4.org/operations/projections/omerc.html) projection method + * + * This is the variant without the no_uoff parameter, which corresponds to + * GDAL >=2.3 Hotine_Oblique_Mercator_Azimuth_Center projection. + * In this variant, the false grid coordinates are defined at the projection + *centre. + * + * This method is defined as [EPSG:9815] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9815) + * + * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid = + *90deg, + * this maps to the [Swiss Oblique Mercator] + *(https://proj4.org/operations/projections/somerc.html) formulas. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeProjectionCentre See \ref latitude_projection_centre + * @param longitudeProjectionCentre See \ref longitude_projection_centre + * @param azimuthInitialLine See \ref azimuth_initial_line + * @param angleFromRectifiedToSkrewGrid See + * \ref angle_from_recitfied_to_skrew_grid + * @param scale See \ref scale_factor_initial_line + * @param eastingProjectionCentre See \ref easting_projection_centre + * @param northingProjectionCentre See \ref northing_projection_centre + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createHotineObliqueMercatorVariantB( + const util::PropertyMap &properties, + const common::Angle &latitudeProjectionCentre, + const common::Angle &longitudeProjectionCentre, + const common::Angle &azimuthInitialLine, + const common::Angle &angleFromRectifiedToSkrewGrid, + const common::Scale &scale, const common::Length &eastingProjectionCentre, + const common::Length &northingProjectionCentre) { + return create( + properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, + createParams(latitudeProjectionCentre, longitudeProjectionCentre, + azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale, + eastingProjectionCentre, northingProjectionCentre)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Hotine Oblique Mercator Two + *Point Natural Origin] + *(https://proj4.org/operations/projections/omerc.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeProjectionCentre See \ref latitude_projection_centre + * @param latitudePoint1 Latitude of point 1. + * @param longitudePoint1 Latitude of point 1. + * @param latitudePoint2 Latitude of point 2. + * @param longitudePoint2 Longitude of point 2. + * @param scale See \ref scale_factor_initial_line + * @param eastingProjectionCentre See \ref easting_projection_centre + * @param northingProjectionCentre See \ref northing_projection_centre + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin( + const util::PropertyMap &properties, + const common::Angle &latitudeProjectionCentre, + const common::Angle &latitudePoint1, const common::Angle &longitudePoint1, + const common::Angle &latitudePoint2, const common::Angle &longitudePoint2, + const common::Scale &scale, const common::Length &eastingProjectionCentre, + const common::Length &northingProjectionCentre) { + return create( + properties, + PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, + { + ParameterValue::create(latitudeProjectionCentre), + ParameterValue::create(latitudePoint1), + ParameterValue::create(longitudePoint1), + ParameterValue::create(latitudePoint2), + ParameterValue::create(longitudePoint2), + ParameterValue::create(scale), + ParameterValue::create(eastingProjectionCentre), + ParameterValue::create(northingProjectionCentre), + }); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [International Map of the World + *Polyconic] + *(https://proj4.org/operations/projections/imw_p.html) projection method. + * + * There is no equivalent in EPSG. + * + * @note the order of arguments is conformant with the corresponding EPSG + * mode and different than OGRSpatialReference::SetIWMPolyconic() of GDAL <= + *2.3 + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param latitudeFirstParallel See \ref latitude_first_std_parallel + * @param latitudeSecondParallel See \ref latitude_second_std_parallel + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createInternationalMapWorldPolyconic( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Angle &latitudeFirstParallel, + const common::Angle &latitudeSecondParallel, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC, + createParams(centerLong, latitudeFirstParallel, + latitudeSecondParallel, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Krovak (north oriented)] + *(https://proj4.org/operations/projections/krovak.html) projection method. + * + * This method is defined as [EPSG:1041] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1041) + * + * The coordinates are returned in the "GIS friendly" order: easting, northing. + * This method is similar to createKrovak(), except that the later returns + * projected values as southing, westing, where + * southing(Krovak) = -northing(Krovak_North) and + * westing(Krovak) = -easting(Krovak_North). + * + * @note The current PROJ implementation of Krovak hard-codes + * colatitudeConeAxis = 30deg17'17.30311" + * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for + * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221). + * It also hard-codes the parameters of the Bessel ellipsoid typically used for + * Krovak. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeProjectionCentre See \ref latitude_projection_centre + * @param longitudeOfOrigin See \ref longitude_of_origin + * @param colatitudeConeAxis See \ref colatitude_cone_axis + * @param latitudePseudoStandardParallel See \ref + *latitude_pseudo_standard_parallel + * @param scaleFactorPseudoStandardParallel See \ref + *scale_factor_pseudo_standard_parallel + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createKrovakNorthOriented( + const util::PropertyMap &properties, + const common::Angle &latitudeProjectionCentre, + const common::Angle &longitudeOfOrigin, + const common::Angle &colatitudeConeAxis, + const common::Angle &latitudePseudoStandardParallel, + const common::Scale &scaleFactorPseudoStandardParallel, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED, + createParams(latitudeProjectionCentre, longitudeOfOrigin, + colatitudeConeAxis, + latitudePseudoStandardParallel, + scaleFactorPseudoStandardParallel, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Krovak] + *(https://proj4.org/operations/projections/krovak.html) projection method. + * + * This method is defined as [EPSG:9819] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9819) + * + * The coordinates are returned in the historical order: southing, westing + * This method is similar to createKrovakNorthOriented(), except that the later + *returns + * projected values as easting, northing, where + * easting(Krovak_North) = -westing(Krovak) and + * northing(Krovak_North) = -southing(Krovak). + * + * @note The current PROJ implementation of Krovak hard-codes + * colatitudeConeAxis = 30deg17'17.30311" + * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for + * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221). + * It also hard-codes the parameters of the Bessel ellipsoid typically used for + * Krovak. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeProjectionCentre See \ref latitude_projection_centre + * @param longitudeOfOrigin See \ref longitude_of_origin + * @param colatitudeConeAxis See \ref colatitude_cone_axis + * @param latitudePseudoStandardParallel See \ref + *latitude_pseudo_standard_parallel + * @param scaleFactorPseudoStandardParallel See \ref + *scale_factor_pseudo_standard_parallel + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr +Conversion::createKrovak(const util::PropertyMap &properties, + const common::Angle &latitudeProjectionCentre, + const common::Angle &longitudeOfOrigin, + const common::Angle &colatitudeConeAxis, + const common::Angle &latitudePseudoStandardParallel, + const common::Scale &scaleFactorPseudoStandardParallel, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_KROVAK, + createParams(latitudeProjectionCentre, longitudeOfOrigin, + colatitudeConeAxis, + latitudePseudoStandardParallel, + scaleFactorPseudoStandardParallel, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Lambert Azimuthal Equal Area] + *(https://proj4.org/operations/projections/laea.html) projection method. + * + * This method is defined as [EPSG:9820] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9820) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeNatOrigin See \ref center_latitude + * @param longitudeNatOrigin See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createLambertAzimuthalEqualArea( + const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, + const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, + createParams(latitudeNatOrigin, longitudeNatOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Miller Cylindrical] + *(https://proj4.org/operations/projections/mill.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createMillerCylindrical( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Mercator] + *(https://proj4.org/operations/projections/merc.html) projection method. + * + * This is the variant, also known as Mercator (1SP), defined with the scale + * factor. Note that latitude of natural origin (centerLat) is a parameter, + * but unused in the transformation formulas. + * + * This method is defined as [EPSG:9804] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9804) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude . Should be 0. + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createMercatorVariantA( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_A, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Mercator] + *(https://proj4.org/operations/projections/merc.html) projection method. + * + * This is the variant, also known as Mercator (2SP), defined with the latitude + * of the first standard parallel (the second standard parallel is implicitly + * the opposite value). The latitude of natural origin is fixed to zero. + * + * This method is defined as [EPSG:9805] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9805) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeFirstParallel See \ref latitude_first_std_parallel + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createMercatorVariantB( + const util::PropertyMap &properties, + const common::Angle &latitudeFirstParallel, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_B, + createParams(latitudeFirstParallel, centerLong, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Popular Visualisation Pseudo + *Mercator] + *(https://proj4.org/operations/projections/webmerc.html) projection method. + * + * Also known as WebMercator. Mostly/only used for Projected CRS EPSG:3857 + * (WGS 84 / Pseudo-Mercator) + * + * This method is defined as [EPSG:1024] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1024) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude . Usually 0 + * @param centerLong See \ref center_longitude . Usually 0 + * @param falseEasting See \ref false_easting . Usually 0 + * @param falseNorthing See \ref false_northing . Usually 0 + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createPopularVisualisationPseudoMercator( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Mollweide] + * (https://proj4.org/operations/projections/moll.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createMollweide( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_MOLLWEIDE, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [New Zealand Map Grid] + * (https://proj4.org/operations/projections/nzmg.html) projection method. + * + * This method is defined as [EPSG:9811] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9811) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createNewZealandMappingGrid( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, EPSG_CODE_METHOD_NZMG, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Oblique Stereographic + *(Alternative)] + *(https://proj4.org/operations/projections/sterea.html) projection method. + * + * This method is defined as [EPSG:9809] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9809) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createObliqueStereographic( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Orthographic] + *(https://proj4.org/operations/projections/ortho.html) projection method. + * + * This method is defined as [EPSG:9840] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9840) + * + * \note At the time of writing, PROJ only implements the spherical formulation + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createOrthographic( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, EPSG_CODE_METHOD_ORTHOGRAPHIC, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [American Polyconic] + *(https://proj4.org/operations/projections/poly.html) projection method. + * + * This method is defined as [EPSG:9818] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9818) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createAmericanPolyconic( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, EPSG_CODE_METHOD_AMERICAN_POLYCONIC, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Polar Stereographic (Variant + *A)] + *(https://proj4.org/operations/projections/stere.html) projection method. + * + * This method is defined as [EPSG:9810] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9810) + * + * This is the variant of polar stereographic defined with a scale factor. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude . Should be 90 deg ou -90 deg. + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createPolarStereographicVariantA( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Polar Stereographic (Variant + *B)] + *(https://proj4.org/operations/projections/stere.html) projection method. + * + * This method is defined as [EPSG:9829] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9829) + * + * This is the variant of polar stereographic defined with a latitude of + * standard parallel. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeStandardParallel See \ref latitude_std_parallel + * @param longitudeOfOrigin See \ref longitude_of_origin + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createPolarStereographicVariantB( + const util::PropertyMap &properties, + const common::Angle &latitudeStandardParallel, + const common::Angle &longitudeOfOrigin, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, + createParams(latitudeStandardParallel, longitudeOfOrigin, + falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Robinson] + * (https://proj4.org/operations/projections/robin.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createRobinson( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_ROBINSON, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Sinusoidal] + * (https://proj4.org/operations/projections/sinu.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createSinusoidal( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_SINUSOIDAL, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Stereographic] + *(https://proj4.org/operations/projections/stere.html) projection method. + * + * There is no equivalent in EPSG. This method implements the original "Oblique + * Stereographic" method described in "Snyder's Map Projections - A Working + *manual", + * which is different from the "Oblique Stereographic (alternative") method + * implemented in createObliqueStereographic(). + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param scale See \ref scale + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createStereographic( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Scale &scale, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC, + createParams(centerLat, centerLong, scale, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Van der Grinten] + * (https://proj4.org/operations/projections/vandg.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createVanDerGrinten( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Wagner I] + * (https://proj4.org/operations/projections/wag1.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createWagnerI(const util::PropertyMap &properties, + const common::Angle ¢erLong, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_I, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Wagner II] + * (https://proj4.org/operations/projections/wag2.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createWagnerII( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_II, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Wagner III] + * (https://proj4.org/operations/projections/wag3.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param latitudeTrueScale Latitude of true scale. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createWagnerIII( + const util::PropertyMap &properties, const common::Angle &latitudeTrueScale, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_III, + createParams(latitudeTrueScale, centerLong, falseEasting, + falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Wagner IV] + * (https://proj4.org/operations/projections/wag4.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createWagnerIV( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_IV, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Wagner V] + * (https://proj4.org/operations/projections/wag5.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createWagnerV(const util::PropertyMap &properties, + const common::Angle ¢erLong, + const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_V, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Wagner VI] + * (https://proj4.org/operations/projections/wag6.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createWagnerVI( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VI, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Wagner VII] + * (https://proj4.org/operations/projections/wag7.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createWagnerVII( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VII, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Quadrilateralized Spherical + *Cube] + *(https://proj4.org/operations/projections/qsc.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLat See \ref center_latitude + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createQuadrilateralizedSphericalCube( + const util::PropertyMap &properties, const common::Angle ¢erLat, + const common::Angle ¢erLong, const common::Length &falseEasting, + const common::Length &falseNorthing) { + return create( + properties, PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE, + createParams(centerLat, centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Spherical Cross-Track Height] + *(https://proj4.org/operations/projections/sch.html) projection method. + * + * There is no equivalent in EPSG. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param pegPointLat Peg point latitude. + * @param pegPointLong Peg point longitude. + * @param pegPointHeading Peg point heading. + * @param pegPointHeight Peg point height. + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createSphericalCrossTrackHeight( + const util::PropertyMap &properties, const common::Angle &pegPointLat, + const common::Angle &pegPointLong, const common::Angle &pegPointHeading, + const common::Length &pegPointHeight) { + return create(properties, + PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT, + createParams(pegPointLat, pegPointLong, pegPointHeading, + pegPointHeight)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the [Equal Earth] + * (https://proj4.org/operations/projections/eqearth.html) projection method. + * + * This method is defined as [EPSG:1078] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1078) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param centerLong See \ref center_longitude + * @param falseEasting See \ref false_easting + * @param falseNorthing See \ref false_northing + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createEqualEarth( + const util::PropertyMap &properties, const common::Angle ¢erLong, + const common::Length &falseEasting, const common::Length &falseNorthing) { + return create(properties, EPSG_CODE_METHOD_EQUAL_EARTH, + createParams(centerLong, falseEasting, falseNorthing)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static OperationParameterNNPtr createOpParamNameEPSGCode(int code) { + const char *name = OperationParameter::getNameForEPSGCode(code); + assert(name); + return OperationParameter::create(createMapNameEPSGCode(name, code)); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the Change of Vertical Unit + * method. + * + * This method is defined as [EPSG:1069] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1069) + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param factor Conversion factor + * @return a new Conversion. + */ +ConversionNNPtr +Conversion::createChangeVerticalUnit(const util::PropertyMap &properties, + const common::Scale &factor) { + return create(properties, createMethodMapNameEPSGCode( + EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT), + VectorOfParameters{ + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR), + }, + VectorOfValues{ + factor, + }); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the Axis order reversal method + * + * This swaps the longitude, latitude axis. + * + * This method is defined as [EPSG:9843] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9843), + * or for 3D as [EPSG:9844] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9844) + * + * @param is3D Whether this should apply on 3D geographicCRS + * @return a new Conversion. + */ +ConversionNNPtr Conversion::createAxisOrderReversal(bool is3D) { + if (is3D) { + return create(createMapNameEPSGCode( + "axis order change (geographic3D horizontal)", 15499), + createMethodMapNameEPSGCode( + EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D), + {}, {}); + } else { + return create(createMapNameEPSGCode("axis order change (2D)", 15498), + createMethodMapNameEPSGCode( + EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D), + {}, {}); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a conversion based on the Geographic/Geocentric method. + * + * This method is defined as [EPSG:9602] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9602), + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @return a new Conversion. + */ +ConversionNNPtr +Conversion::createGeographicGeocentric(const util::PropertyMap &properties) { + return create(properties, createMethodMapNameEPSGCode( + EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC), + {}, {}); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static util::PropertyMap &addDomains(util::PropertyMap &map, + const common::ObjectUsage *obj) { + + auto ar = util::ArrayOfBaseObject::create(); + for (const auto &domain : obj->domains()) { + ar->add(domain); + } + if (!ar->empty()) { + map.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, ar); + } + return map; +} + +// --------------------------------------------------------------------------- + +static void addModifiedIdentifier(util::PropertyMap &map, + const common::IdentifiedObject *obj, + bool inverse, bool derivedFrom) { + // If original operation is AUTH:CODE, then assign INVERSE(AUTH):CODE + // as identifier. + + auto ar = util::ArrayOfBaseObject::create(); + for (const auto &idSrc : obj->identifiers()) { + auto authName = *(idSrc->codeSpace()); + const auto &srcCode = idSrc->code(); + if (derivedFrom) { + authName = concat("DERIVED_FROM(", authName, ")"); + } + if (inverse) { + if (starts_with(authName, "INVERSE(") && authName.back() == ')') { + authName = authName.substr(strlen("INVERSE(")); + authName.resize(authName.size() - 1); + } else { + authName = concat("INVERSE(", authName, ")"); + } + } + auto idsProp = util::PropertyMap().set( + metadata::Identifier::CODESPACE_KEY, authName); + ar->add(metadata::Identifier::create(srcCode, idsProp)); + } + if (!ar->empty()) { + map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar); + } +} + +// --------------------------------------------------------------------------- + +static util::PropertyMap +createPropertiesForInverse(const OperationMethodNNPtr &method) { + util::PropertyMap map; + + const std::string &forwardName = method->nameStr(); + if (!forwardName.empty()) { + if (starts_with(forwardName, INVERSE_OF)) { + map.set(common::IdentifiedObject::NAME_KEY, + forwardName.substr(INVERSE_OF.size())); + } else { + map.set(common::IdentifiedObject::NAME_KEY, + INVERSE_OF + forwardName); + } + } + + addModifiedIdentifier(map, method.get(), true, false); + + return map; +} + +// --------------------------------------------------------------------------- + +InverseConversion::InverseConversion(const ConversionNNPtr &forward) + : Conversion( + OperationMethod::create(createPropertiesForInverse(forward->method()), + forward->method()->parameters()), + forward->parameterValues()), + InverseCoordinateOperation(forward, true) { + setPropertiesFromForward(); +} + +// --------------------------------------------------------------------------- + +InverseConversion::~InverseConversion() = default; + +// --------------------------------------------------------------------------- + +ConversionNNPtr InverseConversion::inverseAsConversion() const { + return NN_NO_CHECK( + util::nn_dynamic_pointer_cast<Conversion>(forwardOperation_)); +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr +InverseConversion::create(const ConversionNNPtr &forward) { + auto conv = util::nn_make_shared<InverseConversion>(forward); + conv->assignSelf(conv); + return conv; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static bool isAxisOrderReversal2D(int methodEPSGCode) { + return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D; +} + +static bool isAxisOrderReversal3D(int methodEPSGCode) { + return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D; +} + +bool isAxisOrderReversal(int methodEPSGCode) { + return isAxisOrderReversal2D(methodEPSGCode) || + isAxisOrderReversal3D(methodEPSGCode); +} +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr Conversion::inverse() const { + const int methodEPSGCode = method()->getEPSGCode(); + + if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) { + const double convFactor = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); + auto conv = createChangeVerticalUnit( + createPropertiesForInverse(this, false, false), + common::Scale(1.0 / convFactor)); + conv->setCRSs(this, true); + return conv; + } + + const bool l_isAxisOrderReversal2D = isAxisOrderReversal2D(methodEPSGCode); + const bool l_isAxisOrderReversal3D = isAxisOrderReversal3D(methodEPSGCode); + if (l_isAxisOrderReversal2D || l_isAxisOrderReversal3D) { + auto conv = createAxisOrderReversal(l_isAxisOrderReversal3D); + conv->setCRSs(this, true); + return conv; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) { + + auto conv = createGeographicGeocentric( + createPropertiesForInverse(this, false, false)); + conv->setCRSs(this, true); + return conv; + } + + return InverseConversion::create(NN_NO_CHECK( + util::nn_dynamic_pointer_cast<Conversion>(shared_from_this()))); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static double msfn(double phi, double e2) { + const double sinphi = std::sin(phi); + const double cosphi = std::cos(phi); + return pj_msfn(sinphi, cosphi, e2); +} + +// --------------------------------------------------------------------------- + +static double tsfn(double phi, double ec) { + const double sinphi = std::sin(phi); + return pj_tsfn(phi, sinphi, ec); +} + +// --------------------------------------------------------------------------- + +// Function whose zeroes are the sin of the standard parallels of LCC_2SP +static double lcc_1sp_to_2sp_f(double sinphi, double K, double ec, double n) { + const double x = sinphi; + const double ecx = ec * x; + return (1 - x * x) / (1 - ecx * ecx) - + K * K * std::pow((1.0 - x) / (1.0 + x) * + std::pow((1.0 + ecx) / (1.0 - ecx), ec), + n); +} + +// --------------------------------------------------------------------------- + +// Find the sin of the standard parallels of LCC_2SP +static double find_zero_lcc_1sp_to_2sp_f(double sinphi0, bool bNorth, double K, + double ec) { + double a, b; + double f_a; + if (bNorth) { + // Look for zero above phi0 + a = sinphi0; + b = 1.0; // sin(North pole) + f_a = 1.0; // some positive value, but we only care about the sign + } else { + // Look for zero below phi0 + a = -1.0; // sin(South pole) + b = sinphi0; + f_a = -1.0; // minus infinity in fact, but we only care about the sign + } + // We use dichotomy search. lcc_1sp_to_2sp_f() is positive at sinphi_init, + // has a zero in ]-1,sinphi0[ and ]sinphi0,1[ ranges + for (int N = 0; N < 100; N++) { + double c = (a + b) / 2; + double f_c = lcc_1sp_to_2sp_f(c, K, ec, sinphi0); + if (f_c == 0.0 || (b - a) < 1e-18) { + return c; + } + if ((f_c > 0 && f_a > 0) || (f_c < 0 && f_a < 0)) { + a = c; + f_a = f_c; + } else { + b = c; + } + } + return (a + b) / 2; +} + +static inline double DegToRad(double x) { return x / 180.0 * M_PI; } +static inline double RadToDeg(double x) { return x / M_PI * 180.0; } + +//! @endcond + +// --------------------------------------------------------------------------- + +/** + * \brief Return an equivalent projection. + * + * Currently implemented: + * <ul> + * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP) to + * EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP)</li> + * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP) to + * EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP)</li> + * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP to + * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP</li> + * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP to + * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP</li> + * </ul> + * + * @param targetEPSGCode EPSG code of the target method. + * @return new conversion, or nullptr + */ +ConversionPtr Conversion::convertToOtherMethod(int targetEPSGCode) const { + const int current_epsg_code = method()->getEPSGCode(); + if (current_epsg_code == targetEPSGCode) { + return util::nn_dynamic_pointer_cast<Conversion>(shared_from_this()); + } + + auto geogCRS = dynamic_cast<crs::GeodeticCRS *>(sourceCRS().get()); + if (!geogCRS) { + return nullptr; + } + + const double e2 = geogCRS->ellipsoid()->squaredEccentricity(); + if (e2 < 0) { + return nullptr; + } + + if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_A && + targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && + parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) { + const double k0 = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN); + if (!(k0 > 0 && k0 <= 1.0 + 1e-10)) + return nullptr; + const double dfStdP1Lat = + (k0 >= 1.0) + ? 0.0 + : std::acos(std::sqrt((1.0 - e2) / ((1.0 / (k0 * k0)) - e2))); + auto latitudeFirstParallel = common::Angle( + common::Angle(dfStdP1Lat, common::UnitOfMeasure::RADIAN) + .convertToUnit(common::UnitOfMeasure::DEGREE), + common::UnitOfMeasure::DEGREE); + auto conv = createMercatorVariantB( + util::PropertyMap(), latitudeFirstParallel, + common::Angle(parameterValueMeasure( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), + common::Length( + parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), + common::Length( + parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING))); + conv->setCRSs(this, false); + return conv; + } + + if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && + targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) { + const double phi1 = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL); + if (!(std::fabs(phi1) < M_PI / 2)) + return nullptr; + const double k0 = msfn(phi1, e2); + auto conv = createMercatorVariantA( + util::PropertyMap(), + common::Angle(0.0, common::UnitOfMeasure::DEGREE), + common::Angle(parameterValueMeasure( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), + common::Scale(k0, common::UnitOfMeasure::SCALE_UNITY), + common::Length( + parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), + common::Length( + parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING))); + conv->setCRSs(this, false); + return conv; + } + + if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP && + targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { + // Notations m0, t0, n, m1, t1, F are those of the EPSG guidance + // "1.3.1.1 Lambert Conic Conformal (2SP)" and + // "1.3.1.2 Lambert Conic Conformal (1SP)" and + // or Snyder pages 106-109 + auto latitudeOfOrigin = common::Angle(parameterValueMeasure( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN)); + const double phi0 = latitudeOfOrigin.getSIValue(); + const double k0 = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN); + if (!(std::fabs(phi0) < M_PI / 2)) + return nullptr; + if (!(k0 > 0 && k0 <= 1.0 + 1e-10)) + return nullptr; + const double ec = std::sqrt(e2); + const double m0 = msfn(phi0, e2); + const double t0 = tsfn(phi0, ec); + const double n = sin(phi0); + if (std::fabs(n) < 1e-10) + return nullptr; + if (fabs(k0 - 1.0) <= 1e-10) { + auto conv = createLambertConicConformal_2SP( + util::PropertyMap(), latitudeOfOrigin, + common::Angle(parameterValueMeasure( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), + latitudeOfOrigin, latitudeOfOrigin, + common::Length( + parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), + common::Length( + parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING))); + conv->setCRSs(this, false); + return conv; + } else { + const double K = k0 * m0 / std::pow(t0, n); + const double phi1 = + std::asin(find_zero_lcc_1sp_to_2sp_f(n, true, K, ec)); + const double phi2 = + std::asin(find_zero_lcc_1sp_to_2sp_f(n, false, K, ec)); + double phi1Deg = RadToDeg(phi1); + double phi2Deg = RadToDeg(phi2); + + // Try to round to hundreth of degree if very close to it + if (std::fabs(phi1Deg * 1000 - std::floor(phi1Deg * 1000 + 0.5)) < + 1e-8) + phi1Deg = floor(phi1Deg * 1000 + 0.5) / 1000; + if (std::fabs(phi2Deg * 1000 - std::floor(phi2Deg * 1000 + 0.5)) < + 1e-8) + phi2Deg = std::floor(phi2Deg * 1000 + 0.5) / 1000; + + // The following improvement is too turn the LCC1SP equivalent of + // EPSG:2154 to the real LCC2SP + // If the computed latitude of origin is close to .0 or .5 degrees + // then check if rounding it to it will get a false northing + // close to an integer + const double FN = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING); + const double latitudeOfOriginDeg = + latitudeOfOrigin.convertToUnit(common::UnitOfMeasure::DEGREE); + if (std::fabs(latitudeOfOriginDeg * 2 - + std::floor(latitudeOfOriginDeg * 2 + 0.5)) < 0.2) { + const double dfRoundedLatOfOrig = + std::floor(latitudeOfOriginDeg * 2 + 0.5) / 2; + const double m1 = msfn(phi1, e2); + const double t1 = tsfn(phi1, ec); + const double F = m1 / (n * std::pow(t1, n)); + const double a = + geogCRS->ellipsoid()->semiMajorAxis().getSIValue(); + const double tRoundedLatOfOrig = + tsfn(DegToRad(dfRoundedLatOfOrig), ec); + const double FN_correction = + a * F * (std::pow(tRoundedLatOfOrig, n) - std::pow(t0, n)); + const double FN_corrected = FN - FN_correction; + const double FN_corrected_rounded = + std::floor(FN_corrected + 0.5); + if (std::fabs(FN_corrected - FN_corrected_rounded) < 1e-8) { + auto conv = createLambertConicConformal_2SP( + util::PropertyMap(), + common::Angle(dfRoundedLatOfOrig, + common::UnitOfMeasure::DEGREE), + common::Angle(parameterValueMeasure( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), + common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE), + common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE), + common::Length(parameterValueMeasure( + EPSG_CODE_PARAMETER_FALSE_EASTING)), + common::Length(FN_corrected_rounded)); + conv->setCRSs(this, false); + return conv; + } + } + + auto conv = createLambertConicConformal_2SP( + util::PropertyMap(), latitudeOfOrigin, + common::Angle(parameterValueMeasure( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), + common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE), + common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE), + common::Length( + parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), + common::Length(FN)); + conv->setCRSs(this, false); + return conv; + } + } + + if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP && + targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP) { + // Notations m0, t0, m1, t1, m2, t2 n, F are those of the EPSG guidance + // "1.3.1.1 Lambert Conic Conformal (2SP)" and + // "1.3.1.2 Lambert Conic Conformal (1SP)" and + // or Snyder pages 106-109 + const double phiF = + parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN) + .getSIValue(); + const double phi1 = + parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) + .getSIValue(); + const double phi2 = + parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) + .getSIValue(); + if (!(std::fabs(phiF) < M_PI / 2)) + return nullptr; + if (!(std::fabs(phi1) < M_PI / 2)) + return nullptr; + if (!(std::fabs(phi2) < M_PI / 2)) + return nullptr; + const double ec = std::sqrt(e2); + const double m1 = msfn(phi1, e2); + const double m2 = msfn(phi2, e2); + const double t1 = tsfn(phi1, ec); + const double t2 = tsfn(phi2, ec); + const double n_denom = std::log(t1) - std::log(t2); + const double n = (std::fabs(n_denom) < 1e-10) + ? std::sin(phi1) + : (std::log(m1) - std::log(m2)) / n_denom; + if (std::fabs(n) < 1e-10) + return nullptr; + const double F = m1 / (n * std::pow(t1, n)); + const double phi0 = std::asin(n); + const double m0 = msfn(phi0, e2); + const double t0 = tsfn(phi0, ec); + const double F0 = m0 / (n * std::pow(t0, n)); + const double k0 = F / F0; + const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue(); + const double tF = tsfn(phiF, ec); + const double FN_correction = + a * F * (std::pow(tF, n) - std::pow(t0, n)); + + double phi0Deg = RadToDeg(phi0); + // Try to round to thousandth of degree if very close to it + if (std::fabs(phi0Deg * 1000 - std::floor(phi0Deg * 1000 + 0.5)) < 1e-8) + phi0Deg = std::floor(phi0Deg * 1000 + 0.5) / 1000; + + auto conv = createLambertConicConformal_1SP( + util::PropertyMap(), + common::Angle(phi0Deg, common::UnitOfMeasure::DEGREE), + common::Angle(parameterValueMeasure( + EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN)), + common::Scale(k0), common::Length(parameterValueMeasure( + EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN)), + common::Length( + parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN) + + (std::fabs(FN_correction) > 1e-8 ? FN_correction : 0))); + conv->setCRSs(this, false); + return conv; + } + + return nullptr; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static void getESRIMethodNameAndParams(const Conversion *conv, + const std::string &methodName, + int methodEPSGCode, + const char *&esriMethodName, + const ESRIParamMapping *&esriParams) { + esriParams = nullptr; + esriMethodName = nullptr; + const auto *esriMapping = getESRIMapping(methodName, methodEPSGCode); + const auto l_targetCRS = conv->targetCRS(); + if (esriMapping) { + esriParams = esriMapping->params; + esriMethodName = esriMapping->esri_name; + if (esriMapping->epsg_code == + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL || + esriMapping->epsg_code == + EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) { + if (l_targetCRS && + ci_find(l_targetCRS->nameStr(), "Plate Carree") != + std::string::npos && + conv->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) { + esriParams = paramsESRI_Plate_Carree; + esriMethodName = "Plate_Carree"; + } else { + esriParams = paramsESRI_Equidistant_Cylindrical; + esriMethodName = "Equidistant_Cylindrical"; + } + } else if (esriMapping->epsg_code == + EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { + if (l_targetCRS && + (ci_find(l_targetCRS->nameStr(), "Gauss") != + std::string::npos || + ci_find(l_targetCRS->nameStr(), "GK_") != std::string::npos)) { + esriParams = paramsESRI_Gauss_Kruger; + esriMethodName = "Gauss_Kruger"; + } else { + esriParams = paramsESRI_Transverse_Mercator; + esriMethodName = "Transverse_Mercator"; + } + } else if (esriMapping->epsg_code == + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) { + if (conv->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) == + conv->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) { + esriParams = + paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin; + esriMethodName = + "Hotine_Oblique_Mercator_Azimuth_Natural_Origin"; + } else { + esriParams = + paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin; + esriMethodName = "Rectified_Skew_Orthomorphic_Natural_Origin"; + } + } else if (esriMapping->epsg_code == + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) { + if (conv->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) == + conv->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) { + esriParams = paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center; + esriMethodName = "Hotine_Oblique_Mercator_Azimuth_Center"; + } else { + esriParams = paramsESRI_Rectified_Skew_Orthomorphic_Center; + esriMethodName = "Rectified_Skew_Orthomorphic_Center"; + } + } else if (esriMapping->epsg_code == + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) { + if (conv->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL) > 0) { + esriMethodName = "Stereographic_North_Pole"; + } else { + esriMethodName = "Stereographic_South_Pole"; + } + } + } +} + +// --------------------------------------------------------------------------- + +const char *Conversion::getESRIMethodName() const { + const auto &l_method = method(); + const auto &methodName = l_method->nameStr(); + const auto methodEPSGCode = l_method->getEPSGCode(); + const ESRIParamMapping *esriParams = nullptr; + const char *esriMethodName = nullptr; + getESRIMethodNameAndParams(this, methodName, methodEPSGCode, esriMethodName, + esriParams); + return esriMethodName; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const char *Conversion::getWKT1GDALMethodName() const { + const auto &l_method = method(); + const auto methodEPSGCode = l_method->getEPSGCode(); + if (methodEPSGCode == + EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) { + return "Mercator_1SP"; + } + const MethodMapping *mapping = getMapping(l_method.get()); + return mapping ? mapping->wkt1_name : nullptr; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Conversion::_exportToWKT(io::WKTFormatter *formatter) const { + const auto &l_method = method(); + const auto &methodName = l_method->nameStr(); + const auto methodEPSGCode = l_method->getEPSGCode(); + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + + if (!isWKT2 && formatter->useESRIDialect()) { + if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) { + auto eqConv = + convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B); + if (eqConv) { + eqConv->_exportToWKT(formatter); + return; + } + } + } + + if (isWKT2) { + formatter->startNode(formatter->useDerivingConversion() + ? io::WKTConstants::DERIVINGCONVERSION + : io::WKTConstants::CONVERSION, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + } else { + formatter->enter(); + formatter->pushOutputUnit(false); + formatter->pushOutputId(false); + } + + bool bAlreadyWritten = false; + if (!isWKT2 && formatter->useESRIDialect()) { + const ESRIParamMapping *esriParams = nullptr; + const char *esriMethodName = nullptr; + getESRIMethodNameAndParams(this, methodName, methodEPSGCode, + esriMethodName, esriParams); + if (esriMethodName && esriParams) { + formatter->startNode(io::WKTConstants::PROJECTION, false); + formatter->addQuotedString(esriMethodName); + formatter->endNode(); + + for (int i = 0; esriParams[i].esri_name != nullptr; i++) { + const auto &esriParam = esriParams[i]; + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString(esriParam.esri_name); + if (esriParam.wkt2_name) { + const auto &pv = parameterValue(esriParam.wkt2_name, + esriParam.epsg_code); + if (pv && pv->type() == ParameterValue::Type::MEASURE) { + const auto &v = pv->value(); + // as we don't output the natural unit, output + // to the registered linear / angular unit. + const auto &unitType = v.unit().type(); + if (unitType == common::UnitOfMeasure::Type::LINEAR) { + formatter->add(v.convertToUnit( + *(formatter->axisLinearUnit()))); + } else if (unitType == + common::UnitOfMeasure::Type::ANGULAR) { + const auto &angUnit = + *(formatter->axisAngularUnit()); + double val = v.convertToUnit(angUnit); + if (angUnit == common::UnitOfMeasure::DEGREE) { + if (val > 180.0) { + val -= 360.0; + } else if (val < -180.0) { + val += 360.0; + } + } + formatter->add(val); + } else { + formatter->add(v.getSIValue()); + } + } else if (ci_find(esriParam.esri_name, "scale") != + std::string::npos) { + formatter->add(1.0); + } else { + formatter->add(0.0); + } + } else { + formatter->add(esriParam.fixed_value); + } + formatter->endNode(); + } + bAlreadyWritten = true; + } + } else if (!isWKT2) { + if (methodEPSGCode == + EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) { + const double latitudeOrigin = parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + common::UnitOfMeasure::DEGREE); + if (latitudeOrigin != 0) { + throw io::FormattingException( + std::string("Unsupported value for ") + + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); + } + + bAlreadyWritten = true; + formatter->startNode(io::WKTConstants::PROJECTION, false); + formatter->addQuotedString("Mercator_1SP"); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("central_meridian"); + const double centralMeridian = parameterValueNumeric( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + common::UnitOfMeasure::DEGREE); + formatter->add(centralMeridian); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("scale_factor"); + formatter->add(1.0); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("false_easting"); + const double falseEasting = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING); + formatter->add(falseEasting); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("false_northing"); + const double falseNorthing = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING); + formatter->add(falseNorthing); + formatter->endNode(); + } else if (starts_with(methodName, "PROJ ")) { + bAlreadyWritten = true; + formatter->startNode(io::WKTConstants::PROJECTION, false); + formatter->addQuotedString("custom_proj4"); + formatter->endNode(); + } + } + + if (!bAlreadyWritten) { + l_method->_exportToWKT(formatter); + + const MethodMapping *mapping = + !isWKT2 ? getMapping(l_method.get()) : nullptr; + for (const auto ¶mValue : parameterValues()) { + paramValue->_exportToWKT(formatter, mapping); + } + } + + if (isWKT2) { + if (formatter->outputId()) { + formatID(formatter); + } + formatter->endNode(); + } else { + formatter->popOutputUnit(); + formatter->popOutputId(); + formatter->leave(); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static bool createPROJ4WebMercator(const Conversion *conv, + io::PROJStringFormatter *formatter) { + const double centralMeridian = conv->parameterValueNumeric( + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, + common::UnitOfMeasure::DEGREE); + + const double falseEasting = + conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING); + + const double falseNorthing = + conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING); + + auto sourceCRS = conv->sourceCRS(); + auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); + if (!geogCRS) { + return false; + } + + formatter->addStep("merc"); + const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue(); + formatter->addParam("a", a); + formatter->addParam("b", a); + formatter->addParam("lat_ts", 0.0); + formatter->addParam("lon_0", centralMeridian); + formatter->addParam("x_0", falseEasting); + formatter->addParam("y_0", falseNorthing); + formatter->addParam("k", 1.0); + formatter->addParam("units", "m"); + formatter->addParam("nadgrids", "@null"); + formatter->addParam("wktext"); + formatter->addParam("no_defs"); + return true; +} + +// --------------------------------------------------------------------------- + +static bool +createPROJExtensionFromCustomProj(const Conversion *conv, + io::PROJStringFormatter *formatter, + bool forExtensionNode) { + const auto &methodName = conv->method()->nameStr(); + assert(starts_with(methodName, "PROJ ")); + auto tokens = split(methodName, ' '); + + formatter->addStep(tokens[1]); + + if (forExtensionNode) { + auto sourceCRS = conv->sourceCRS(); + auto geogCRS = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); + if (!geogCRS) { + return false; + } + geogCRS->addDatumInfoToPROJString(formatter); + } + + for (size_t i = 2; i < tokens.size(); i++) { + auto kv = split(tokens[i], '='); + if (kv.size() == 2) { + formatter->addParam(kv[0], kv[1]); + } else { + formatter->addParam(tokens[i]); + } + } + + for (const auto &genOpParamvalue : conv->parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶mValue = opParamvalue->parameterValue(); + if (paramValue->type() == ParameterValue::Type::MEASURE) { + const auto &measure = paramValue->value(); + const auto unitType = measure.unit().type(); + if (unitType == common::UnitOfMeasure::Type::LINEAR) { + formatter->addParam(paramName, measure.getSIValue()); + } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) { + formatter->addParam( + paramName, + measure.convertToUnit(common::UnitOfMeasure::DEGREE)); + } else { + formatter->addParam(paramName, measure.value()); + } + } + } + } + + if (forExtensionNode) { + formatter->addParam("wktext"); + formatter->addParam("no_defs"); + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +void Conversion::addWKTExtensionNode(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + const auto &methodName = method()->nameStr(); + const int methodEPSGCode = method()->getEPSGCode(); + if (methodEPSGCode == + EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR || + nameStr() == "Popular Visualisation Mercator") { + + auto projFormatter = io::PROJStringFormatter::create( + io::PROJStringFormatter::Convention::PROJ_4); + if (createPROJ4WebMercator(this, projFormatter.get())) { + formatter->startNode(io::WKTConstants::EXTENSION, false); + formatter->addQuotedString("PROJ4"); + formatter->addQuotedString(projFormatter->toString()); + formatter->endNode(); + } + } else if (starts_with(methodName, "PROJ ")) { + auto projFormatter = io::PROJStringFormatter::create( + io::PROJStringFormatter::Convention::PROJ_4); + if (createPROJExtensionFromCustomProj(this, projFormatter.get(), + true)) { + formatter->startNode(io::WKTConstants::EXTENSION, false); + formatter->addQuotedString("PROJ4"); + formatter->addQuotedString(projFormatter->toString()); + formatter->endNode(); + } + } + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Conversion::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(FormattingException) +{ + const auto &l_method = method(); + const auto &methodName = l_method->nameStr(); + const int methodEPSGCode = l_method->getEPSGCode(); + const bool isZUnitConversion = + methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT; + const bool isAffineParametric = + methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION; + const bool isGeographicGeocentric = + methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC; + const bool applySourceCRSModifiers = + !isZUnitConversion && !isAffineParametric && + !isAxisOrderReversal(methodEPSGCode) && !isGeographicGeocentric; + const bool applyTargetCRSModifiers = applySourceCRSModifiers; + + auto l_sourceCRS = sourceCRS(); + if (l_sourceCRS && applySourceCRSModifiers && + formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + auto geogCRS = + dynamic_cast<const crs::GeographicCRS *>(l_sourceCRS.get()); + if (geogCRS) { + formatter->setOmitProjLongLatIfPossible(true); + formatter->startInversion(); + geogCRS->_exportToPROJString(formatter); + formatter->stopInversion(); + formatter->setOmitProjLongLatIfPossible(false); + } + + auto projCRS = + dynamic_cast<const crs::ProjectedCRS *>(l_sourceCRS.get()); + if (projCRS) { + formatter->startInversion(); + projCRS->addUnitConvertAndAxisSwap(formatter, false); + formatter->stopInversion(); + } + } + + const auto &convName = nameStr(); + bool bConversionDone = false; + bool bEllipsoidParametersDone = false; + bool useETMerc = false; + if (methodEPSGCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { + // Check for UTM + int zone = 0; + bool north = true; + if (isUTM(zone, north)) { + bConversionDone = true; + formatter->addStep("utm"); + formatter->addParam("zone", zone); + if (!north) { + formatter->addParam("south"); + } + } else { + useETMerc = formatter->getUseETMercForTMerc(); + } + } else if (methodEPSGCode == + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) { + const double azimuth = + parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, + common::UnitOfMeasure::DEGREE); + const double angleRectifiedToSkewGrid = parameterValueNumeric( + EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, + common::UnitOfMeasure::DEGREE); + // Map to Swiss Oblique Mercator / somerc + if (std::fabs(azimuth - 90) < 1e-4 && + std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) { + bConversionDone = true; + formatter->addStep("somerc"); + formatter->addParam( + "lat_0", parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, + common::UnitOfMeasure::DEGREE)); + formatter->addParam( + "lon_0", parameterValueNumeric( + EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + common::UnitOfMeasure::DEGREE)); + formatter->addParam( + "k_0", parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE)); + formatter->addParam("x_0", parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_FALSE_EASTING)); + formatter->addParam("y_0", parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_FALSE_NORTHING)); + } + } else if (methodEPSGCode == + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) { + const double azimuth = + parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, + common::UnitOfMeasure::DEGREE); + const double angleRectifiedToSkewGrid = parameterValueNumeric( + EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, + common::UnitOfMeasure::DEGREE); + // Map to Swiss Oblique Mercator / somerc + if (std::fabs(azimuth - 90) < 1e-4 && + std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) { + bConversionDone = true; + formatter->addStep("somerc"); + formatter->addParam( + "lat_0", parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, + common::UnitOfMeasure::DEGREE)); + formatter->addParam( + "lon_0", parameterValueNumeric( + EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, + common::UnitOfMeasure::DEGREE)); + formatter->addParam( + "k_0", parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE)); + formatter->addParam( + "x_0", parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE)); + formatter->addParam( + "y_0", parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE)); + } + } else if (methodEPSGCode == EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED) { + double colatitude = + parameterValueNumeric(EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, + common::UnitOfMeasure::DEGREE); + double latitudePseudoStandardParallel = parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, + common::UnitOfMeasure::DEGREE); + if (std::fabs(colatitude - 30.28813972222222) > 1e-8) { + throw io::FormattingException( + std::string("Unsupported value for ") + + EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS); + } + if (std::fabs(latitudePseudoStandardParallel - 78.5) > 1e-8) { + throw io::FormattingException( + std::string("Unsupported value for ") + + EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL); + } + } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) { + double latitudeOrigin = parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + common::UnitOfMeasure::DEGREE); + if (latitudeOrigin != 0) { + throw io::FormattingException( + std::string("Unsupported value for ") + + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); + } + // PROJ.4 specific hack for webmercator + } else if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4 && + methodEPSGCode == + EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) { + if (!createPROJ4WebMercator(this, formatter)) { + throw io::FormattingException( + std::string("Cannot export ") + + EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR + + " as PROJ.4 string outside of a ProjectedCRS context"); + } + bConversionDone = true; + bEllipsoidParametersDone = true; + } else if (ci_equal(convName, "Popular Visualisation Mercator")) { + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + if (!createPROJ4WebMercator(this, formatter)) { + throw io::FormattingException(concat( + "Cannot export ", convName, + " as PROJ.4 string outside of a ProjectedCRS context")); + } + } else { + formatter->addStep("webmerc"); + if (l_sourceCRS) { + datum::Ellipsoid::WGS84->_exportToPROJString(formatter); + } + } + bConversionDone = true; + bEllipsoidParametersDone = true; + } else if (starts_with(methodName, "PROJ ")) { + bConversionDone = true; + createPROJExtensionFromCustomProj(this, formatter, false); + } else if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5 && + isZUnitConversion) { + double convFactor = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); + auto uom = common::UnitOfMeasure(std::string(), convFactor, + common::UnitOfMeasure::Type::LINEAR) + .exportToPROJString(); + auto reverse_uom = + common::UnitOfMeasure(std::string(), 1.0 / convFactor, + common::UnitOfMeasure::Type::LINEAR) + .exportToPROJString(); + if (!uom.empty()) { + formatter->addStep("unitconvert"); + formatter->addParam("z_in", uom); + formatter->addParam("z_out", "m"); + } else if (!reverse_uom.empty()) { + formatter->addStep("unitconvert"); + formatter->addParam("z_in", "m"); + formatter->addParam("z_out", reverse_uom); + } else { + formatter->addStep("affine"); + formatter->addParam("s33", convFactor); + } + bConversionDone = true; + bEllipsoidParametersDone = true; + } + + bool bAxisSpecFound = false; + if (!bConversionDone) { + const MethodMapping *mapping = getMapping(l_method.get()); + if (mapping && mapping->proj_name_main) { + formatter->addStep(useETMerc ? "etmerc" : mapping->proj_name_main); + if (mapping->proj_name_aux) { + if (internal::starts_with(mapping->proj_name_aux, "axis=")) { + bAxisSpecFound = true; + } + auto kv = split(mapping->proj_name_aux, '='); + if (kv.size() == 2) { + formatter->addParam(kv[0], kv[1]); + } else { + formatter->addParam(mapping->proj_name_aux); + } + } + + if (mapping->epsg_code == + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) { + double latitudeStdParallel = parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, + common::UnitOfMeasure::DEGREE); + formatter->addParam("lat_0", + (latitudeStdParallel >= 0) ? 90.0 : -90.0); + } + + for (int i = 0; mapping->params[i] != nullptr; i++) { + const auto *param = mapping->params[i]; + if (!param->proj_name) { + continue; + } + auto value = + parameterValueMeasure(param->wkt2_name, param->epsg_code); + if (mapping->epsg_code == + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP && + strcmp(param->proj_name, "lat_1") == 0) { + formatter->addParam( + param->proj_name, + value.convertToUnit(common::UnitOfMeasure::DEGREE)); + formatter->addParam( + "lat_0", + value.convertToUnit(common::UnitOfMeasure::DEGREE)); + } else if (param->unit_type == + common::UnitOfMeasure::Type::ANGULAR) { + formatter->addParam( + param->proj_name, + value.convertToUnit(common::UnitOfMeasure::DEGREE)); + } else { + formatter->addParam(param->proj_name, value.getSIValue()); + } + } + + } else { + if (!exportToPROJStringGeneric(formatter)) { + throw io::FormattingException( + concat("Unsupported conversion method: ", methodName)); + } + } + } + + auto l_targetCRS = targetCRS(); + if (l_targetCRS && applyTargetCRSModifiers) { + if (!bEllipsoidParametersDone) { + auto targetGeogCRS = l_targetCRS->extractGeographicCRS(); + if (targetGeogCRS) { + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + targetGeogCRS->addDatumInfoToPROJString(formatter); + } else { + targetGeogCRS->ellipsoid()->_exportToPROJString(formatter); + targetGeogCRS->primeMeridian()->_exportToPROJString( + formatter); + } + } + } + + auto projCRS = + dynamic_cast<const crs::ProjectedCRS *>(l_targetCRS.get()); + if (projCRS) { + projCRS->addUnitConvertAndAxisSwap(formatter, bAxisSpecFound); + } + + auto derivedGeographicCRS = + dynamic_cast<const crs::DerivedGeographicCRS *>(l_targetCRS.get()); + if (derivedGeographicCRS) { + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + auto geogCRS = derivedGeographicCRS->baseCRS(); + formatter->setOmitProjLongLatIfPossible(true); + geogCRS->_exportToPROJString(formatter); + formatter->setOmitProjLongLatIfPossible(false); + } + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return whether a conversion is a [Universal Transverse Mercator] + * (https://proj4.org/operations/projections/utm.html) conversion. + * + * @param[out] zone UTM zone number between 1 and 60. + * @param[out] north true for UTM northern hemisphere, false for UTM southern + * hemisphere. + * @return true if it is a UTM conversion. + */ +bool Conversion::isUTM(int &zone, bool &north) const { + zone = 0; + north = true; + + if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { + // Check for UTM + + bool bLatitudeNatOriginUTM = false; + bool bScaleFactorUTM = false; + bool bFalseEastingUTM = false; + bool bFalseNorthingUTM = false; + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto epsg_code = opParamvalue->parameter()->getEPSGCode(); + const auto &l_parameterValue = opParamvalue->parameterValue(); + if (l_parameterValue->type() == ParameterValue::Type::MEASURE) { + const auto &measure = l_parameterValue->value(); + if (epsg_code == + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN && + measure.value() == UTM_LATITUDE_OF_NATURAL_ORIGIN) { + bLatitudeNatOriginUTM = true; + } else if ( + epsg_code == + EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN && + measure.unit() == common::UnitOfMeasure::DEGREE) { + double dfZone = (measure.value() + 183.0) / 6.0; + if (dfZone > 0.9 && dfZone < 60.1 && + std::abs(dfZone - std::round(dfZone)) < 1e-10) { + zone = static_cast<int>(std::lround(dfZone)); + } + } else if ( + epsg_code == + EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN && + measure.value() == UTM_SCALE_FACTOR) { + bScaleFactorUTM = true; + } else if (epsg_code == EPSG_CODE_PARAMETER_FALSE_EASTING && + measure.value() == UTM_FALSE_EASTING && + measure.unit() == common::UnitOfMeasure::METRE) { + bFalseEastingUTM = true; + } else if (epsg_code == + EPSG_CODE_PARAMETER_FALSE_NORTHING && + measure.unit() == common::UnitOfMeasure::METRE) { + if (measure.value() == UTM_NORTH_FALSE_NORTHING) { + bFalseNorthingUTM = true; + north = true; + } else if (measure.value() == + UTM_SOUTH_FALSE_NORTHING) { + bFalseNorthingUTM = true; + north = false; + } + } + } + } + } + if (bLatitudeNatOriginUTM && zone > 0 && bScaleFactorUTM && + bFalseEastingUTM && bFalseNorthingUTM) { + return true; + } + } + return false; +} + +// --------------------------------------------------------------------------- + +/** \brief Return a Conversion object where some parameters are better + * identified. + * + * @return a new Conversion. + */ +ConversionNNPtr Conversion::identify() const { + auto newConversion = Conversion::nn_make_shared<Conversion>(*this); + newConversion->assignSelf(newConversion); + + if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { + // Check for UTM + int zone = 0; + bool north = true; + if (isUTM(zone, north)) { + newConversion->setProperties( + getUTMConversionProperty(util::PropertyMap(), zone, north)); + } + } + + return newConversion; +} + +//! @cond Doxygen_Suppress +// --------------------------------------------------------------------------- + +InvalidOperation::InvalidOperation(const char *message) : Exception(message) {} + +// --------------------------------------------------------------------------- + +InvalidOperation::InvalidOperation(const std::string &message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +InvalidOperation::InvalidOperation(const InvalidOperation &) = default; + +// --------------------------------------------------------------------------- + +InvalidOperation::~InvalidOperation() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Transformation::Private { + + TransformationPtr forwardOperation_{}; + + TransformationNNPtr registerInv(util::BaseObjectNNPtr thisIn, + TransformationNNPtr invTransform); +}; +//! @endcond + +// --------------------------------------------------------------------------- + +Transformation::Transformation( + const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, + const crs::CRSPtr &interpolationCRSIn, const OperationMethodNNPtr &methodIn, + const std::vector<GeneralParameterValueNNPtr> &values, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) + : SingleOperation(methodIn), d(internal::make_unique<Private>()) { + setParameterValues(values); + setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn); + setAccuracies(accuracies); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Transformation::~Transformation() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the source crs::CRS of the transformation. + * + * @return the source CRS. + */ +const crs::CRSNNPtr &Transformation::sourceCRS() PROJ_CONST_DEFN { + return CoordinateOperation::getPrivate()->strongRef_->sourceCRS_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the target crs::CRS of the transformation. + * + * @return the target CRS. + */ +const crs::CRSNNPtr &Transformation::targetCRS() PROJ_CONST_DEFN { + return CoordinateOperation::getPrivate()->strongRef_->targetCRS_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +/** \brief Return the TOWGS84 parameters of the transformation. + * + * If this transformation uses Coordinate Frame Rotation, Position Vector + * transformation or Geocentric translations, a vector of 7 double values + * using the Position Vector convention (EPSG:9606) is returned. Those values + * can be used as the value of the WKT1 TOWGS84 parameter or + * PROJ +towgs84 parameter. + * + * @return a vector of 7 values if valid, otherwise a io::FormattingException + * is thrown. + * @throws io::FormattingException + */ +std::vector<double> +Transformation::getTOWGS84Parameters() const // throw(io::FormattingException) +{ + // GDAL WKT1 assumes EPSG:9606 / Position Vector convention + + bool sevenParamsTransform = false; + bool threeParamsTransform = false; + bool invertRotSigns = false; + const auto &l_method = method(); + const auto &methodName = l_method->nameStr(); + const int methodEPSGCode = l_method->getEPSGCode(); + const auto paramCount = parameterValues().size(); + if ((paramCount == 7 && + ci_find(methodName, "Coordinate Frame") != std::string::npos) || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) { + sevenParamsTransform = true; + invertRotSigns = true; + } else if ((paramCount == 7 && + ci_find(methodName, "Position Vector") != std::string::npos) || + methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) { + sevenParamsTransform = true; + invertRotSigns = false; + } else if ((paramCount == 3 && + ci_find(methodName, "Geocentric translations") != + std::string::npos) || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) { + threeParamsTransform = true; + } + + if (threeParamsTransform || sevenParamsTransform) { + std::vector<double> params(7, 0.0); + bool foundX = false; + bool foundY = false; + bool foundZ = false; + bool foundRotX = false; + bool foundRotY = false; + bool foundRotZ = false; + bool foundScale = false; + const double rotSign = invertRotSigns ? -1.0 : 1.0; + for (const auto &genOpParamvalue : parameterValues()) { + auto opParamvalue = dynamic_cast<const OperationParameterValue *>( + genOpParamvalue.get()); + if (opParamvalue) { + const auto ¶meter = opParamvalue->parameter(); + const auto epsg_code = parameter->getEPSGCode(); + const auto &l_parameterValue = opParamvalue->parameterValue(); + if (l_parameterValue->type() == ParameterValue::Type::MEASURE) { + const auto &measure = l_parameterValue->value(); + if (epsg_code == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) { + params[0] = measure.getSIValue(); + foundX = true; + } else if (epsg_code == + EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) { + params[1] = measure.getSIValue(); + foundY = true; + } else if (epsg_code == + EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) { + params[2] = measure.getSIValue(); + foundZ = true; + } else if (epsg_code == + EPSG_CODE_PARAMETER_X_AXIS_ROTATION) { + params[3] = rotSign * + measure.convertToUnit( + common::UnitOfMeasure::ARC_SECOND); + foundRotX = true; + } else if (epsg_code == + EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) { + params[4] = rotSign * + measure.convertToUnit( + common::UnitOfMeasure::ARC_SECOND); + foundRotY = true; + } else if (epsg_code == + EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) { + params[5] = rotSign * + measure.convertToUnit( + common::UnitOfMeasure::ARC_SECOND); + foundRotZ = true; + } else if (epsg_code == + EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) { + params[6] = measure.convertToUnit( + common::UnitOfMeasure::PARTS_PER_MILLION); + foundScale = true; + } + } + } + } + if (foundX && foundY && foundZ && + (threeParamsTransform || + (foundRotX && foundRotY && foundRotZ && foundScale))) { + return params; + } else { + throw io::FormattingException( + "Missing required parameter values in transformation"); + } + } + +#if 0 + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS || + methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) { + auto offsetLat = + parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); + auto offsetLong = + parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); + + auto offsetHeight = + parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); + + if (offsetLat.getSIValue() == 0.0 && offsetLong.getSIValue() == 0.0 && + offsetHeight.getSIValue() == 0.0) { + std::vector<double> params(7, 0.0); + return params; + } + } +#endif + + throw io::FormattingException( + "Transformation cannot be formatted as WKT1 TOWGS84 parameters"); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation from a vector of GeneralParameterValue. + * + * @param properties See \ref general_properties. At minimum the name should be + * defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param interpolationCRSIn Interpolation CRS (might be null) + * @param methodIn Operation method. + * @param values Vector of GeneralOperationParameterNNPtr. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + * @throws InvalidOperation + */ +TransformationNNPtr Transformation::create( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn, + const OperationMethodNNPtr &methodIn, + const std::vector<GeneralParameterValueNNPtr> &values, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + if (methodIn->parameters().size() != values.size()) { + throw InvalidOperation( + "Inconsistent number of parameters and parameter values"); + } + auto conv = Transformation::nn_make_shared<Transformation>( + sourceCRSIn, targetCRSIn, interpolationCRSIn, methodIn, values, + accuracies); + conv->assignSelf(conv); + conv->setProperties(properties); + return conv; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation ands its OperationMethod. + * + * @param propertiesTransformation The \ref general_properties of the + * Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param interpolationCRSIn Interpolation CRS (might be null) + * @param propertiesOperationMethod The \ref general_properties of the + * OperationMethod. + * At minimum the name should be defined. + * @param parameters Vector of parameters of the operation method. + * @param values Vector of ParameterValueNNPtr. Constraint: + * values.size() == parameters.size() + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + * @throws InvalidOperation + */ +TransformationNNPtr +Transformation::create(const util::PropertyMap &propertiesTransformation, + const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, + const crs::CRSPtr &interpolationCRSIn, + const util::PropertyMap &propertiesOperationMethod, + const std::vector<OperationParameterNNPtr> ¶meters, + const std::vector<ParameterValueNNPtr> &values, + const std::vector<metadata::PositionalAccuracyNNPtr> + &accuracies) // throw InvalidOperation +{ + OperationMethodNNPtr op( + OperationMethod::create(propertiesOperationMethod, parameters)); + + if (parameters.size() != values.size()) { + throw InvalidOperation( + "Inconsistent number of parameters and parameter values"); + } + std::vector<GeneralParameterValueNNPtr> generalParameterValues; + generalParameterValues.reserve(values.size()); + for (size_t i = 0; i < values.size(); i++) { + generalParameterValues.push_back( + OperationParameterValue::create(parameters[i], values[i])); + } + return create(propertiesTransformation, sourceCRSIn, targetCRSIn, + interpolationCRSIn, op, generalParameterValues, accuracies); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +static TransformationNNPtr createSevenParamsTransform( + const util::PropertyMap &properties, + const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double rotationXArcSecond, double rotationYArcSecond, + double rotationZArcSecond, double scaleDifferencePPM, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return Transformation::create( + properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties, + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE), + }, + createParams(common::Length(translationXMetre), + common::Length(translationYMetre), + common::Length(translationZMetre), + common::Angle(rotationXArcSecond, + common::UnitOfMeasure::ARC_SECOND), + common::Angle(rotationYArcSecond, + common::UnitOfMeasure::ARC_SECOND), + common::Angle(rotationZArcSecond, + common::UnitOfMeasure::ARC_SECOND), + common::Scale(scaleDifferencePPM, + common::UnitOfMeasure::PARTS_PER_MILLION)), + accuracies); +} + +// --------------------------------------------------------------------------- + +static void getTransformationType(const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, + bool &isGeocentric, bool &isGeog2D, + bool &isGeog3D) { + auto sourceCRSGeod = + dynamic_cast<const crs::GeodeticCRS *>(sourceCRSIn.get()); + auto targetCRSGeod = + dynamic_cast<const crs::GeodeticCRS *>(targetCRSIn.get()); + isGeocentric = sourceCRSGeod && sourceCRSGeod->isGeocentric() && + targetCRSGeod && targetCRSGeod->isGeocentric(); + if (isGeocentric) { + isGeog2D = false; + isGeog3D = false; + return; + } + isGeocentric = false; + + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRSIn.get()); + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRSIn.get()); + if (!sourceCRSGeog || !targetCRSGeog) { + throw InvalidOperation("Inconsistent CRS type"); + } + const auto nSrcAxisCount = + sourceCRSGeog->coordinateSystem()->axisList().size(); + const auto nTargetAxisCount = + targetCRSGeog->coordinateSystem()->axisList().size(); + isGeog2D = nSrcAxisCount == 2 && nTargetAxisCount == 2; + isGeog3D = !isGeog2D && nSrcAxisCount >= 2 && nTargetAxisCount >= 2; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation with Geocentric Translations method. + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param translationXMetre Value of the Translation_X parameter (in metre). + * @param translationYMetre Value of the Translation_Y parameter (in metre). + * @param translationZMetre Value of the Translation_Z parameter (in metre). + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createGeocentricTranslations( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + bool isGeocentric; + bool isGeog2D; + bool isGeog3D; + getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, + isGeog3D); + return create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode( + isGeocentric + ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC + : isGeog2D + ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D + : EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D), + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), + }, + createParams(common::Length(translationXMetre), + common::Length(translationYMetre), + common::Length(translationZMetre)), + accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation with Position vector transformation + * method. + * + * This is similar to createCoordinateFrameRotation(), except that the sign of + * the rotation terms is inverted. + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param translationXMetre Value of the Translation_X parameter (in metre). + * @param translationYMetre Value of the Translation_Y parameter (in metre). + * @param translationZMetre Value of the Translation_Z parameter (in metre). + * @param rotationXArcSecond Value of the Rotation_X parameter (in + * arc-second). + * @param rotationYArcSecond Value of the Rotation_Y parameter (in + * arc-second). + * @param rotationZArcSecond Value of the Rotation_Z parameter (in + * arc-second). + * @param scaleDifferencePPM Value of the Scale_Difference parameter (in + * parts-per-million). + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createPositionVector( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double rotationXArcSecond, double rotationYArcSecond, + double rotationZArcSecond, double scaleDifferencePPM, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + + bool isGeocentric; + bool isGeog2D; + bool isGeog3D; + getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, + isGeog3D); + return createSevenParamsTransform( + properties, + createMethodMapNameEPSGCode( + isGeocentric + ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC + : isGeog2D ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D + : EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D), + sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, + translationZMetre, rotationXArcSecond, rotationYArcSecond, + rotationZArcSecond, scaleDifferencePPM, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation with Coordinate Frame Rotation method. + * + * This is similar to createPositionVector(), except that the sign of + * the rotation terms is inverted. + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param translationXMetre Value of the Translation_X parameter (in metre). + * @param translationYMetre Value of the Translation_Y parameter (in metre). + * @param translationZMetre Value of the Translation_Z parameter (in metre). + * @param rotationXArcSecond Value of the Rotation_X parameter (in + * arc-second). + * @param rotationYArcSecond Value of the Rotation_Y parameter (in + * arc-second). + * @param rotationZArcSecond Value of the Rotation_Z parameter (in + * arc-second). + * @param scaleDifferencePPM Value of the Scale_Difference parameter (in + * parts-per-million). + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createCoordinateFrameRotation( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double rotationXArcSecond, double rotationYArcSecond, + double rotationZArcSecond, double scaleDifferencePPM, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + bool isGeocentric; + bool isGeog2D; + bool isGeog3D; + getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, + isGeog3D); + return createSevenParamsTransform( + properties, + createMethodMapNameEPSGCode( + isGeocentric + ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC + : isGeog2D ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D + : EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D), + sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, + translationZMetre, rotationXArcSecond, rotationYArcSecond, + rotationZArcSecond, scaleDifferencePPM, accuracies); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static TransformationNNPtr createFifteenParamsTransform( + const util::PropertyMap &properties, + const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double rotationXArcSecond, double rotationYArcSecond, + double rotationZArcSecond, double scaleDifferencePPM, + double rateTranslationX, double rateTranslationY, double rateTranslationZ, + double rateRotationX, double rateRotationY, double rateRotationZ, + double rateScaleDifference, double referenceEpochYear, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return Transformation::create( + properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties, + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE), + + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION), + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION), + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION), + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE), + + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_REFERENCE_EPOCH), + }, + VectorOfValues{ + common::Length(translationXMetre), + common::Length(translationYMetre), + common::Length(translationZMetre), + common::Angle(rotationXArcSecond, + common::UnitOfMeasure::ARC_SECOND), + common::Angle(rotationYArcSecond, + common::UnitOfMeasure::ARC_SECOND), + common::Angle(rotationZArcSecond, + common::UnitOfMeasure::ARC_SECOND), + common::Scale(scaleDifferencePPM, + common::UnitOfMeasure::PARTS_PER_MILLION), + common::Measure(rateTranslationX, + common::UnitOfMeasure::METRE_PER_YEAR), + common::Measure(rateTranslationY, + common::UnitOfMeasure::METRE_PER_YEAR), + common::Measure(rateTranslationZ, + common::UnitOfMeasure::METRE_PER_YEAR), + common::Measure(rateRotationX, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR), + common::Measure(rateRotationY, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR), + common::Measure(rateRotationZ, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR), + common::Measure(rateScaleDifference, + common::UnitOfMeasure::PPM_PER_YEAR), + common::Measure(referenceEpochYear, common::UnitOfMeasure::YEAR), + }, + accuracies); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation with Time Dependent position vector + * transformation method. + * + * This is similar to createTimeDependentCoordinateFrameRotation(), except that + * the sign of + * the rotation terms is inverted. + * + * This method is defined as [EPSG:1053] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1053) + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param translationXMetre Value of the Translation_X parameter (in metre). + * @param translationYMetre Value of the Translation_Y parameter (in metre). + * @param translationZMetre Value of the Translation_Z parameter (in metre). + * @param rotationXArcSecond Value of the Rotation_X parameter (in + * arc-second). + * @param rotationYArcSecond Value of the Rotation_Y parameter (in + * arc-second). + * @param rotationZArcSecond Value of the Rotation_Z parameter (in + * arc-second). + * @param scaleDifferencePPM Value of the Scale_Difference parameter (in + * parts-per-million). + * @param rateTranslationX Value of the rate of change of X-axis translation (in + * metre/year) + * @param rateTranslationY Value of the rate of change of Y-axis translation (in + * metre/year) + * @param rateTranslationZ Value of the rate of change of Z-axis translation (in + * metre/year) + * @param rateRotationX Value of the rate of change of X-axis rotation (in + * arc-second/year) + * @param rateRotationY Value of the rate of change of Y-axis rotation (in + * arc-second/year) + * @param rateRotationZ Value of the rate of change of Z-axis rotation (in + * arc-second/year) + * @param rateScaleDifference Value of the rate of change of scale difference + * (in PPM/year) + * @param referenceEpochYear Parameter reference epoch (in decimal year) + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createTimeDependentPositionVector( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double rotationXArcSecond, double rotationYArcSecond, + double rotationZArcSecond, double scaleDifferencePPM, + double rateTranslationX, double rateTranslationY, double rateTranslationZ, + double rateRotationX, double rateRotationY, double rateRotationZ, + double rateScaleDifference, double referenceEpochYear, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + bool isGeocentric; + bool isGeog2D; + bool isGeog3D; + getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, + isGeog3D); + return createFifteenParamsTransform( + properties, + createMethodMapNameEPSGCode( + isGeocentric + ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC + : isGeog2D + ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D + : EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D), + sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, + translationZMetre, rotationXArcSecond, rotationYArcSecond, + rotationZArcSecond, scaleDifferencePPM, rateTranslationX, + rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY, + rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation with Time Dependent Position coordinate + * frame rotation transformation method. + * + * This is similar to createTimeDependentPositionVector(), except that the sign + * of + * the rotation terms is inverted. + * + * This method is defined as [EPSG:1056] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1056) + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param translationXMetre Value of the Translation_X parameter (in metre). + * @param translationYMetre Value of the Translation_Y parameter (in metre). + * @param translationZMetre Value of the Translation_Z parameter (in metre). + * @param rotationXArcSecond Value of the Rotation_X parameter (in + * arc-second). + * @param rotationYArcSecond Value of the Rotation_Y parameter (in + * arc-second). + * @param rotationZArcSecond Value of the Rotation_Z parameter (in + * arc-second). + * @param scaleDifferencePPM Value of the Scale_Difference parameter (in + * parts-per-million). + * @param rateTranslationX Value of the rate of change of X-axis translation (in + * metre/year) + * @param rateTranslationY Value of the rate of change of Y-axis translation (in + * metre/year) + * @param rateTranslationZ Value of the rate of change of Z-axis translation (in + * metre/year) + * @param rateRotationX Value of the rate of change of X-axis rotation (in + * arc-second/year) + * @param rateRotationY Value of the rate of change of Y-axis rotation (in + * arc-second/year) + * @param rateRotationZ Value of the rate of change of Z-axis rotation (in + * arc-second/year) + * @param rateScaleDifference Value of the rate of change of scale difference + * (in PPM/year) + * @param referenceEpochYear Parameter reference epoch (in decimal year) + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + * @throws InvalidOperation + */ +TransformationNNPtr Transformation::createTimeDependentCoordinateFrameRotation( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double rotationXArcSecond, double rotationYArcSecond, + double rotationZArcSecond, double scaleDifferencePPM, + double rateTranslationX, double rateTranslationY, double rateTranslationZ, + double rateRotationX, double rateRotationY, double rateRotationZ, + double rateScaleDifference, double referenceEpochYear, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + + bool isGeocentric; + bool isGeog2D; + bool isGeog3D; + getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, + isGeog3D); + return createFifteenParamsTransform( + properties, + createMethodMapNameEPSGCode( + isGeocentric + ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC + : isGeog2D + ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D + : EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D), + sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, + translationZMetre, rotationXArcSecond, rotationYArcSecond, + rotationZArcSecond, scaleDifferencePPM, rateTranslationX, + rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY, + rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static TransformationNNPtr _createMolodensky( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, int methodEPSGCode, + double translationXMetre, double translationYMetre, + double translationZMetre, double semiMajorAxisDifferenceMetre, + double flattingDifference, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return Transformation::create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(methodEPSGCode), + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE), + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE), + }, + createParams( + common::Length(translationXMetre), + common::Length(translationYMetre), + common::Length(translationZMetre), + common::Length(semiMajorAxisDifferenceMetre), + common::Measure(flattingDifference, common::UnitOfMeasure::NONE)), + accuracies); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation with Molodensky method. + * + * @see createAbridgedMolodensky() for a related method. + * + * This method is defined as [EPSG:9604] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9604) + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param translationXMetre Value of the Translation_X parameter (in metre). + * @param translationYMetre Value of the Translation_Y parameter (in metre). + * @param translationZMetre Value of the Translation_Z parameter (in metre). + * @param semiMajorAxisDifferenceMetre The difference between the semi-major + * axis values of the ellipsoids used in the target and source CRS (in metre). + * @param flattingDifference The difference between the flattening values of + * the ellipsoids used in the target and source CRS. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + * @throws InvalidOperation + */ +TransformationNNPtr Transformation::createMolodensky( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double semiMajorAxisDifferenceMetre, double flattingDifference, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return _createMolodensky( + properties, sourceCRSIn, targetCRSIn, EPSG_CODE_METHOD_MOLODENSKY, + translationXMetre, translationYMetre, translationZMetre, + semiMajorAxisDifferenceMetre, flattingDifference, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation with Abridged Molodensky method. + * + * @see createdMolodensky() for a related method. + * + * This method is defined as [EPSG:9605] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9605) + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param translationXMetre Value of the Translation_X parameter (in metre). + * @param translationYMetre Value of the Translation_Y parameter (in metre). + * @param translationZMetre Value of the Translation_Z parameter (in metre). + * @param semiMajorAxisDifferenceMetre The difference between the semi-major + * axis values of the ellipsoids used in the target and source CRS (in metre). + * @param flattingDifference The difference between the flattening values of + * the ellipsoids used in the target and source CRS. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + * @throws InvalidOperation + */ +TransformationNNPtr Transformation::createAbridgedMolodensky( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, double translationXMetre, + double translationYMetre, double translationZMetre, + double semiMajorAxisDifferenceMetre, double flattingDifference, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return _createMolodensky(properties, sourceCRSIn, targetCRSIn, + EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY, + translationXMetre, translationYMetre, + translationZMetre, semiMajorAxisDifferenceMetre, + flattingDifference, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation from TOWGS84 parameters. + * + * This is a helper of createPositionVector() with the source CRS being the + * GeographicCRS of sourceCRSIn, and the target CRS being EPSG:4326 + * + * @param sourceCRSIn Source CRS. + * @param TOWGS84Parameters The vector of 3 double values (Translation_X,_Y,_Z) + * or 7 double values (Translation_X,_Y,_Z, Rotation_X,_Y,_Z, Scale_Difference) + * passed to createPositionVector() + * @return new Transformation. + * @throws InvalidOperation + */ +TransformationNNPtr Transformation::createTOWGS84( + const crs::CRSNNPtr &sourceCRSIn, + const std::vector<double> &TOWGS84Parameters) // throw InvalidOperation +{ + if (TOWGS84Parameters.size() != 3 && TOWGS84Parameters.size() != 7) { + throw InvalidOperation( + "Invalid number of elements in TOWGS84Parameters"); + } + + crs::CRSPtr transformSourceCRS = sourceCRSIn->extractGeographicCRS(); + if (!transformSourceCRS) { + throw InvalidOperation( + "Cannot find GeographicCRS in sourceCRS of TOWGS84 transformation"); + } + + if (TOWGS84Parameters.size() == 3) { + return createGeocentricTranslations( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + concat("Transformation from ", + transformSourceCRS->nameStr(), + " to WGS84")), + NN_NO_CHECK(transformSourceCRS), crs::GeographicCRS::EPSG_4326, + TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2], + {}); + } + + return createPositionVector( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + concat("Transformation from ", + transformSourceCRS->nameStr(), + " to WGS84")), + NN_NO_CHECK(transformSourceCRS), crs::GeographicCRS::EPSG_4326, + TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2], + TOWGS84Parameters[3], TOWGS84Parameters[4], TOWGS84Parameters[5], + TOWGS84Parameters[6], {}); +} + +// --------------------------------------------------------------------------- +/** \brief Instanciate a transformation with NTv2 method. + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param filename NTv2 filename. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createNTv2( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const std::string &filename, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + + return create(properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV2), + VectorOfParameters{createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}, + VectorOfValues{ParameterValue::createFilename(filename)}, + accuracies); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static TransformationNNPtr _createGravityRelatedHeightToGeographic3D( + const util::PropertyMap &properties, bool inverse, + const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, + const std::string &filename, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + + return Transformation::create( + properties, sourceCRSIn, targetCRSIn, nullptr, + util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + inverse ? INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D + : PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D), + VectorOfParameters{createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)}, + VectorOfValues{ParameterValue::createFilename(filename)}, accuracies); +} +//! @endcond + +// --------------------------------------------------------------------------- +/** \brief Instanciate a transformation from GravityRelatedHeight to + * Geographic3D + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param filename GRID filename. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createGravityRelatedHeightToGeographic3D( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const std::string &filename, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + + return _createGravityRelatedHeightToGeographic3D( + properties, false, sourceCRSIn, targetCRSIn, filename, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation with method VERTCON + * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param filename GRID filename. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createVERTCON( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const std::string &filename, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + + return create(properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTCON), + VectorOfParameters{createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)}, + VectorOfValues{ParameterValue::createFilename(filename)}, + accuracies); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static inline std::vector<metadata::PositionalAccuracyNNPtr> +buildAccuracyZero() { + return std::vector<metadata::PositionalAccuracyNNPtr>{ + metadata::PositionalAccuracy::create("0")}; +} + +// --------------------------------------------------------------------------- + +//! @endcond + +/** \brief Instanciate a transformation with method Longitude rotation + * + * This method is defined as [EPSG:9601] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9601) + * * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param offset Longitude offset to add. + * @return new Transformation. + */ +TransformationNNPtr Transformation::createLongitudeRotation( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const common::Angle &offset) { + + return create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_LONGITUDE_ROTATION), + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)}, + VectorOfValues{ParameterValue::create(offset)}, buildAccuracyZero()); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool Transformation::isLongitudeRotation() const { + return method()->getEPSGCode() == EPSG_CODE_METHOD_LONGITUDE_ROTATION; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation with method Geographic 2D offsets + * + * This method is defined as [EPSG:9619] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9619) + * * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param offsetLat Latitude offset to add. + * @param offsetLon Longitude offset to add. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createGeographic2DOffsets( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, + const common::Angle &offsetLon, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS), + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)}, + VectorOfValues{offsetLat, offsetLon}, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation with method Geographic 3D offsets + * + * This method is defined as [EPSG:9660] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9660) + * * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param offsetLat Latitude offset to add. + * @param offsetLon Longitude offset to add. + * @param offsetHeight Height offset to add. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createGeographic3DOffsets( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, + const common::Angle &offsetLon, const common::Length &offsetHeight, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS), + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_VERTICAL_OFFSET)}, + VectorOfValues{offsetLat, offsetLon, offsetHeight}, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation with method Geographic 2D with + * height + * offsets + * + * This method is defined as [EPSG:9618] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9618) + * * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param offsetLat Latitude offset to add. + * @param offsetLon Longitude offset to add. + * @param offsetHeight Geoid undulation to add. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createGeographic2DWithHeightOffsets( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, + const common::Angle &offsetLon, const common::Length &offsetHeight, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode( + EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS), + VectorOfParameters{ + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET), + createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_GEOID_UNDULATION)}, + VectorOfValues{offsetLat, offsetLon, offsetHeight}, accuracies); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a transformation with method Vertical Offset. + * + * This method is defined as [EPSG:9616] + * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9616) + * * + * @param properties See \ref general_properties of the Transformation. + * At minimum the name should be defined. + * @param sourceCRSIn Source CRS. + * @param targetCRSIn Target CRS. + * @param offsetHeight Geoid undulation to add. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + */ +TransformationNNPtr Transformation::createVerticalOffset( + const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, const common::Length &offsetHeight, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return create(properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTICAL_OFFSET), + VectorOfParameters{createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_VERTICAL_OFFSET)}, + VectorOfValues{offsetHeight}, accuracies); +} + +// --------------------------------------------------------------------------- + +static const char *getCRSQualifierStr(const crs::CRSPtr &crs) { + auto geod = dynamic_cast<crs::GeodeticCRS *>(crs.get()); + if (geod) { + if (geod->isGeocentric()) { + return " (geocentric)"; + } + auto geog = dynamic_cast<crs::GeographicCRS *>(geod); + if (geog) { + if (geog->coordinateSystem()->axisList().size() == 2) { + return " (geog2D)"; + } else { + return " (geog3D)"; + } + } + } + return ""; +} + +// --------------------------------------------------------------------------- + +static std::string buildOpName(const char *opType, const crs::CRSPtr &source, + const crs::CRSPtr &target) { + std::string res(opType); + const auto &srcName = source->nameStr(); + const auto &targetName = target->nameStr(); + const char *srcQualifier = ""; + const char *targetQualifier = ""; + if (srcName == targetName) { + srcQualifier = getCRSQualifierStr(source); + targetQualifier = getCRSQualifierStr(target); + if (strcmp(srcQualifier, targetQualifier) == 0) { + srcQualifier = ""; + targetQualifier = ""; + } + } + res += " from "; + res += srcName; + res += srcQualifier; + res += " to "; + res += targetName; + res += targetQualifier; + return res; +} + +// --------------------------------------------------------------------------- + +static util::PropertyMap +createPropertiesForInverse(const CoordinateOperation *op, bool derivedFrom, + bool approximateInversion) { + assert(op); + util::PropertyMap map; + + // The domain(s) are unchanged by the inverse operation + addDomains(map, op); + + const std::string &forwardName = op->nameStr(); + + // Forge a name for the inverse, either from the forward name, or + // from the source and target CRS names + const char *opType; + if (starts_with(forwardName, NULL_GEOCENTRIC_TRANSLATION)) { + opType = NULL_GEOCENTRIC_TRANSLATION; + } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) { + opType = NULL_GEOGRAPHIC_OFFSET; + } else if (dynamic_cast<const Transformation *>(op) || + starts_with(forwardName, "Transformation from ")) { + opType = "Transformation"; + } else if (dynamic_cast<const Conversion *>(op)) { + opType = "Conversion"; + } else { + opType = "Operation"; + } + + auto sourceCRS = op->sourceCRS(); + auto targetCRS = op->targetCRS(); + std::string name; + if (!forwardName.empty()) { + if (starts_with(forwardName, INVERSE_OF)) { + name = forwardName.substr(INVERSE_OF.size()); + } else if (!sourceCRS || !targetCRS || + forwardName != buildOpName(opType, sourceCRS, targetCRS)) { + name = INVERSE_OF + forwardName; + } + } + if (name.empty() && sourceCRS && targetCRS) { + name = buildOpName(opType, targetCRS, sourceCRS); + } + if (approximateInversion) { + name += " (approx. inversion)"; + } + + if (!name.empty()) { + map.set(common::IdentifiedObject::NAME_KEY, name); + } + + addModifiedIdentifier(map, op, true, derivedFrom); + + return map; +} + +// --------------------------------------------------------------------------- + +static bool isTimeDependent(const std::string &methodName) { + return ci_find(methodName, "Time dependent") != std::string::npos || + ci_find(methodName, "Time-dependent") != std::string::npos; +} + +// --------------------------------------------------------------------------- + +// to avoid -0... +static double negate(double val) { + if (val != 0) { + return -val; + } + return 0.0; +} + +// --------------------------------------------------------------------------- + +static CoordinateOperationPtr +createApproximateInverseIfPossible(const Transformation *op) { + bool sevenParamsTransform = false; + bool fifteenParamsTransform = false; + const auto &method = op->method(); + const auto &methodName = method->nameStr(); + const int methodEPSGCode = method->getEPSGCode(); + const auto paramCount = op->parameterValues().size(); + const bool isPositionVector = + ci_find(methodName, "Position Vector") != std::string::npos; + const bool isCoordinateFrame = + ci_find(methodName, "Coordinate Frame") != std::string::npos; + + // See end of "2.4.3.3 Helmert 7-parameter transformations" + // in EPSG 7-2 guidance + // For practical purposes, the inverse of 7- or 15-parameters Helmert + // can be obtained by using the forward method with all parameters + // negated + // (except reference epoch!) + // So for WKT export use that. But for PROJ string, we use the +inv flag + // so as to get "perfect" round-tripability. + if ((paramCount == 7 && isCoordinateFrame && + !isTimeDependent(methodName)) || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) { + sevenParamsTransform = true; + } else if ( + (paramCount == 15 && isCoordinateFrame && + isTimeDependent(methodName)) || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) { + fifteenParamsTransform = true; + } else if ((paramCount == 7 && isPositionVector && + !isTimeDependent(methodName)) || + methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) { + sevenParamsTransform = true; + } else if ( + (paramCount == 15 && isPositionVector && isTimeDependent(methodName)) || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) { + fifteenParamsTransform = true; + } + if (sevenParamsTransform || fifteenParamsTransform) { + double neg_x = negate(op->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION)); + double neg_y = negate(op->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION)); + double neg_z = negate(op->parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION)); + double neg_rx = negate( + op->parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND)); + double neg_ry = negate( + op->parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND)); + double neg_rz = negate( + op->parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND)); + double neg_scaleDiff = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, + common::UnitOfMeasure::PARTS_PER_MILLION)); + auto methodProperties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, methodName); + int method_epsg_code = method->getEPSGCode(); + if (method_epsg_code) { + methodProperties + .set(metadata::Identifier::CODESPACE_KEY, + metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, method_epsg_code); + } + if (fifteenParamsTransform) { + double neg_rate_x = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION, + common::UnitOfMeasure::METRE_PER_YEAR)); + double neg_rate_y = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION, + common::UnitOfMeasure::METRE_PER_YEAR)); + double neg_rate_z = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION, + common::UnitOfMeasure::METRE_PER_YEAR)); + double neg_rate_rx = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR)); + double neg_rate_ry = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR)); + double neg_rate_rz = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR)); + double neg_rate_scaleDiff = negate(op->parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE, + common::UnitOfMeasure::PPM_PER_YEAR)); + double referenceEpochYear = + op->parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH, + common::UnitOfMeasure::YEAR); + return util::nn_static_pointer_cast<CoordinateOperation>( + createFifteenParamsTransform( + createPropertiesForInverse(op, false, true), + methodProperties, op->targetCRS(), op->sourceCRS(), + neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz, + neg_scaleDiff, neg_rate_x, neg_rate_y, neg_rate_z, + neg_rate_rx, neg_rate_ry, neg_rate_rz, + neg_rate_scaleDiff, referenceEpochYear, + op->coordinateOperationAccuracies())) + .as_nullable(); + } else { + return util::nn_static_pointer_cast<CoordinateOperation>( + createSevenParamsTransform( + createPropertiesForInverse(op, false, true), + methodProperties, op->targetCRS(), op->sourceCRS(), + neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz, + neg_scaleDiff, op->coordinateOperationAccuracies())) + .as_nullable(); + } + } + + return nullptr; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TransformationNNPtr +Transformation::Private::registerInv(util::BaseObjectNNPtr thisIn, + TransformationNNPtr invTransform) { + invTransform->d->forwardOperation_ = + util::nn_dynamic_pointer_cast<Transformation>(thisIn); + return invTransform; +} +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr Transformation::inverse() const { + return inverseAsTransformation(); +} + +// --------------------------------------------------------------------------- + +TransformationNNPtr Transformation::inverseAsTransformation() const { + + if (d->forwardOperation_) { + return NN_NO_CHECK(d->forwardOperation_); + } + const auto &l_method = method(); + const auto &methodName = l_method->nameStr(); + const int methodEPSGCode = l_method->getEPSGCode(); + const auto &l_sourceCRS = sourceCRS(); + const auto &l_targetCRS = targetCRS(); + + // For geocentric translation, the inverse is exactly the negation of + // the parameters. + if (ci_find(methodName, "Geocentric translations") != std::string::npos || + methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) { + double x = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); + double y = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); + double z = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); + return d->registerInv( + shared_from_this(), + createGeocentricTranslations( + createPropertiesForInverse(this, false, false), l_targetCRS, + l_sourceCRS, negate(x), negate(y), negate(z), + coordinateOperationAccuracies())); + } + + if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY || + methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { + double x = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); + double y = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); + double z = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); + double da = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE); + double df = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE); + + if (methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { + return d->registerInv( + shared_from_this(), + createAbridgedMolodensky( + createPropertiesForInverse(this, false, false), l_targetCRS, + l_sourceCRS, negate(x), negate(y), negate(z), negate(da), + negate(df), coordinateOperationAccuracies())); + } else { + return d->registerInv( + shared_from_this(), + createMolodensky(createPropertiesForInverse(this, false, false), + l_targetCRS, l_sourceCRS, negate(x), negate(y), + negate(z), negate(da), negate(df), + coordinateOperationAccuracies())); + } + } + + if (isLongitudeRotation()) { + auto offset = + parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); + const common::Angle newOffset(negate(offset.value()), offset.unit()); + return d->registerInv( + shared_from_this(), + createLongitudeRotation( + createPropertiesForInverse(this, false, false), l_targetCRS, + l_sourceCRS, newOffset)); + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) { + auto offsetLat = + parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); + const common::Angle newOffsetLat(negate(offsetLat.value()), + offsetLat.unit()); + + auto offsetLong = + parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); + const common::Angle newOffsetLong(negate(offsetLong.value()), + offsetLong.unit()); + + return d->registerInv( + shared_from_this(), + createGeographic2DOffsets( + createPropertiesForInverse(this, false, false), l_targetCRS, + l_sourceCRS, newOffsetLat, newOffsetLong, + coordinateOperationAccuracies())); + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) { + auto offsetLat = + parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); + const common::Angle newOffsetLat(negate(offsetLat.value()), + offsetLat.unit()); + + auto offsetLong = + parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); + const common::Angle newOffsetLong(negate(offsetLong.value()), + offsetLong.unit()); + + auto offsetHeight = + parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); + const common::Length newOffsetHeight(negate(offsetHeight.value()), + offsetHeight.unit()); + + return d->registerInv( + shared_from_this(), + createGeographic3DOffsets( + createPropertiesForInverse(this, false, false), l_targetCRS, + l_sourceCRS, newOffsetLat, newOffsetLong, newOffsetHeight, + coordinateOperationAccuracies())); + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) { + auto offsetLat = + parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); + const common::Angle newOffsetLat(negate(offsetLat.value()), + offsetLat.unit()); + + auto offsetLong = + parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); + const common::Angle newOffsetLong(negate(offsetLong.value()), + offsetLong.unit()); + + auto offsetHeight = + parameterValueMeasure(EPSG_CODE_PARAMETER_GEOID_UNDULATION); + const common::Length newOffsetHeight(negate(offsetHeight.value()), + offsetHeight.unit()); + + return d->registerInv( + shared_from_this(), + createGeographic2DWithHeightOffsets( + createPropertiesForInverse(this, false, false), l_targetCRS, + l_sourceCRS, newOffsetLat, newOffsetLong, newOffsetHeight, + coordinateOperationAccuracies())); + } + + if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) { + + auto offsetHeight = + parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); + const common::Length newOffsetHeight(negate(offsetHeight.value()), + offsetHeight.unit()); + + return d->registerInv( + shared_from_this(), + createVerticalOffset(createPropertiesForInverse(this, false, false), + l_targetCRS, l_sourceCRS, newOffsetHeight, + coordinateOperationAccuracies())); + } + + return InverseTransformation::create(NN_NO_CHECK( + util::nn_dynamic_pointer_cast<Transformation>(shared_from_this()))); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +InverseTransformation::InverseTransformation(const TransformationNNPtr &forward) + : Transformation( + forward->targetCRS(), forward->sourceCRS(), + forward->interpolationCRS(), + OperationMethod::create(createPropertiesForInverse(forward->method()), + forward->method()->parameters()), + forward->parameterValues(), forward->coordinateOperationAccuracies()), + InverseCoordinateOperation(forward, true) { + setPropertiesFromForward(); +} + +// --------------------------------------------------------------------------- + +InverseTransformation::~InverseTransformation() = default; + +// --------------------------------------------------------------------------- + +TransformationNNPtr +InverseTransformation::create(const TransformationNNPtr &forward) { + auto conv = util::nn_make_shared<InverseTransformation>(forward); + conv->assignSelf(conv); + return conv; +} + +// --------------------------------------------------------------------------- + +void InverseTransformation::_exportToWKT(io::WKTFormatter *formatter) const { + + auto approxInverse = createApproximateInverseIfPossible( + util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_).get()); + if (approxInverse) { + approxInverse->_exportToWKT(formatter); + } else { + Transformation::_exportToWKT(formatter); + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Transformation::_exportToWKT(io::WKTFormatter *formatter) const { + exportTransformationToWKT(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void SingleOperation::exportTransformationToWKT( + io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + throw io::FormattingException( + "Transformation can only be exported to WKT2"); + } + + auto l_sourceCRS = sourceCRS(); + assert(l_sourceCRS); + auto l_targetCRS = targetCRS(); + assert(l_targetCRS); + + if (formatter->abridgedTransformation()) { + formatter->startNode(io::WKTConstants::ABRIDGEDTRANSFORMATION, + !identifiers().empty()); + } else { + formatter->startNode(io::WKTConstants::COORDINATEOPERATION, + !identifiers().empty()); + } + + formatter->addQuotedString(nameStr()); + + if (!formatter->abridgedTransformation()) { + formatter->startNode(io::WKTConstants::SOURCECRS, false); + l_sourceCRS->_exportToWKT(formatter); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::TARGETCRS, false); + l_targetCRS->_exportToWKT(formatter); + formatter->endNode(); + } + + method()->_exportToWKT(formatter); + + const MethodMapping *mapping = + !isWKT2 ? getMapping(method().get()) : nullptr; + for (const auto ¶mValue : parameterValues()) { + paramValue->_exportToWKT(formatter, mapping); + } + + if (!formatter->abridgedTransformation()) { + if (interpolationCRS()) { + formatter->startNode(io::WKTConstants::INTERPOLATIONCRS, false); + interpolationCRS()->_exportToWKT(formatter); + formatter->endNode(); + } + + if (!coordinateOperationAccuracies().empty()) { + formatter->startNode(io::WKTConstants::OPERATIONACCURACY, false); + formatter->add(coordinateOperationAccuracies()[0]->value()); + formatter->endNode(); + } + } + + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string nullString; + +static const std::string &_getNTv2Filename(const Transformation *op, + bool allowInverse) { + + const auto &l_method = op->method(); + if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV2 || + (allowInverse && + ci_equal(l_method->nameStr(), INVERSE_OF + EPSG_NAME_METHOD_NTV2))) { + const auto &fileParameter = op->parameterValue( + EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + return fileParameter->valueFile(); + } + } + return nullString; +} +//! @endcond + +// --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress +const std::string &Transformation::getNTv2Filename() const { + + return _getNTv2Filename(this, false); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string &_getNTv1Filename(const Transformation *op, + bool allowInverse) { + + const auto &l_method = op->method(); + const auto &methodName = l_method->nameStr(); + if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV1 || + (allowInverse && + ci_equal(methodName, INVERSE_OF + EPSG_NAME_METHOD_NTV1))) { + const auto &fileParameter = op->parameterValue( + EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + return fileParameter->valueFile(); + } + } + return nullString; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string &_getCTABLE2Filename(const Transformation *op, + bool allowInverse) { + const auto &l_method = op->method(); + const auto &methodName = l_method->nameStr(); + if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_CTABLE2) || + (allowInverse && + ci_equal(methodName, INVERSE_OF + PROJ_WKT2_NAME_METHOD_CTABLE2))) { + const auto &fileParameter = op->parameterValue( + EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + return fileParameter->valueFile(); + } + } + return nullString; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string & +_getHeightToGeographic3DFilename(const Transformation *op, bool allowInverse) { + + const auto &methodName = op->method()->nameStr(); + + if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D) || + (allowInverse && + ci_equal(methodName, + INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D))) { + const auto &fileParameter = + op->parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, + EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + return fileParameter->valueFile(); + } + } + return nullString; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const std::string &Transformation::getHeightToGeographic3DFilename() const { + + return _getHeightToGeographic3DFilename(this, false); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static bool +isGeographic3DToGravityRelatedHeight(const OperationMethodNNPtr &method, + bool allowInverse) { + const auto &methodName = method->nameStr(); + static const char *const methodCodes[] = { + "1025", // Geographic3D to GravityRelatedHeight (EGM2008) + "1030", // Geographic3D to GravityRelatedHeight (NZgeoid) + "1045", // Geographic3D to GravityRelatedHeight (OSGM02-Ire) + "1047", // Geographic3D to GravityRelatedHeight (Gravsoft) + "1048", // Geographic3D to GravityRelatedHeight (Ausgeoid v2) + "1050", // Geographic3D to GravityRelatedHeight (CI) + "1059", // Geographic3D to GravityRelatedHeight (PNG) + "1060", // Geographic3D to GravityRelatedHeight (CGG2013) + "1072", // Geographic3D to GravityRelatedHeight (OSGM15-Ire) + "1073", // Geographic3D to GravityRelatedHeight (IGN2009) + "9661", // Geographic3D to GravityRelatedHeight (EGM) + "9662", // Geographic3D to GravityRelatedHeight (Ausgeoid98) + "9663", // Geographic3D to GravityRelatedHeight (OSGM-GB) + "9664", // Geographic3D to GravityRelatedHeight (IGN1997) + "9665", // Geographic3D to GravityRelatedHeight (US .gtx) + }; + + if (ci_find(methodName, "Geographic3D to GravityRelatedHeight") == 0) { + return true; + } + if (allowInverse && + ci_find(methodName, + INVERSE_OF + "Geographic3D to GravityRelatedHeight") == 0) { + return true; + } + + for (const auto &code : methodCodes) { + for (const auto &idSrc : method->identifiers()) { + const auto &srcAuthName = *(idSrc->codeSpace()); + const auto &srcCode = idSrc->code(); + if (ci_equal(srcAuthName, "EPSG") && srcCode == code) { + return true; + } + if (allowInverse && ci_equal(srcAuthName, "INVERSE(EPSG)") && + srcCode == code) { + return true; + } + } + } + return false; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static util::PropertyMap +createSimilarPropertiesMethod(common::IdentifiedObjectNNPtr obj) { + util::PropertyMap map; + + const std::string &forwardName = obj->nameStr(); + if (!forwardName.empty()) { + map.set(common::IdentifiedObject::NAME_KEY, forwardName); + } + + { + auto ar = util::ArrayOfBaseObject::create(); + for (const auto &idSrc : obj->identifiers()) { + const auto &srcAuthName = *(idSrc->codeSpace()); + const auto &srcCode = idSrc->code(); + auto idsProp = util::PropertyMap().set( + metadata::Identifier::CODESPACE_KEY, srcAuthName); + ar->add(metadata::Identifier::create(srcCode, idsProp)); + } + if (!ar->empty()) { + map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar); + } + } + + return map; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static util::PropertyMap +createSimilarPropertiesTransformation(TransformationNNPtr obj) { + util::PropertyMap map; + + // The domain(s) are unchanged + addDomains(map, obj.get()); + + std::string forwardName = obj->nameStr(); + if (!forwardName.empty()) { + map.set(common::IdentifiedObject::NAME_KEY, forwardName); + } + + addModifiedIdentifier(map, obj.get(), false, true); + + return map; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static TransformationNNPtr +createNTv1(const util::PropertyMap &properties, + const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, + const std::string &filename, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + return Transformation::create( + properties, sourceCRSIn, targetCRSIn, nullptr, + createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV1), + {OperationParameter::create( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, + EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE) + .set(metadata::Identifier::CODESPACE_KEY, + metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE))}, + {ParameterValue::createFilename(filename)}, accuracies); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return an equivalent transformation to the current one, but using + * PROJ alternative grid names. + */ +TransformationNNPtr Transformation::substitutePROJAlternativeGridNames( + io::DatabaseContextNNPtr databaseContext) const { + auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>( + shared_from_this().as_nullable())); + + const auto &l_method = method(); + const int methodEPSGCode = l_method->getEPSGCode(); + + std::string projFilename; + std::string projGridFormat; + bool inverseDirection = false; + + const auto &NTv1Filename = _getNTv1Filename(this, false); + const auto &NTv2Filename = _getNTv2Filename(this, false); + std::string lasFilename; + if (methodEPSGCode == EPSG_CODE_METHOD_NADCON) { + const auto &latitudeFileParameter = + parameterValue(EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE); + const auto &longitudeFileParameter = + parameterValue(EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE, + EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE); + if (latitudeFileParameter && + latitudeFileParameter->type() == ParameterValue::Type::FILENAME && + longitudeFileParameter && + longitudeFileParameter->type() == ParameterValue::Type::FILENAME) { + lasFilename = latitudeFileParameter->valueFile(); + } + } + const auto &horizontalGridName = + !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty() + ? NTv2Filename + : lasFilename; + + if (!horizontalGridName.empty() && + databaseContext->lookForGridAlternative(horizontalGridName, + projFilename, projGridFormat, + inverseDirection)) { + + if (horizontalGridName == projFilename) { + assert(!inverseDirection); + return self; + } + + const auto &l_sourceCRS = sourceCRS(); + const auto &l_targetCRS = targetCRS(); + const auto &l_accuracies = coordinateOperationAccuracies(); + if (projGridFormat == "NTv1") { + if (inverseDirection) { + return createNTv1(createPropertiesForInverse( + self.as_nullable().get(), true, false), + l_targetCRS, l_sourceCRS, projFilename, + l_accuracies) + ->inverseAsTransformation(); + } else { + return createNTv1(createSimilarPropertiesTransformation(self), + l_sourceCRS, l_targetCRS, projFilename, + l_accuracies); + } + } else if (projGridFormat == "NTv2") { + if (inverseDirection) { + return createNTv2(createPropertiesForInverse( + self.as_nullable().get(), true, false), + l_targetCRS, l_sourceCRS, projFilename, + l_accuracies) + ->inverseAsTransformation(); + } else { + return createNTv2(createSimilarPropertiesTransformation(self), + l_sourceCRS, l_targetCRS, projFilename, + l_accuracies); + } + } else if (projGridFormat == "CTable2") { + auto parameters = + std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}; + auto methodProperties = + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + PROJ_WKT2_NAME_METHOD_CTABLE2); + auto values = std::vector<ParameterValueNNPtr>{ + ParameterValue::createFilename(projFilename)}; + if (inverseDirection) { + return create(createPropertiesForInverse( + self.as_nullable().get(), true, false), + l_targetCRS, l_sourceCRS, nullptr, + methodProperties, parameters, values, + l_accuracies) + ->inverseAsTransformation(); + + } else { + return create(createSimilarPropertiesTransformation(self), + l_sourceCRS, l_targetCRS, nullptr, + methodProperties, parameters, values, + l_accuracies); + } + } + } + + const auto &heightFilename = getHeightToGeographic3DFilename(); + if (!heightFilename.empty() && !projFilename.empty()) { + if (databaseContext->lookForGridAlternative( + heightFilename, projFilename, projGridFormat, + inverseDirection)) { + + if (heightFilename == projFilename) { + assert(!inverseDirection); + return self; + } + + if (inverseDirection) { + return createGravityRelatedHeightToGeographic3D( + createPropertiesForInverse(self.as_nullable().get(), + true, false), + targetCRS(), sourceCRS(), projFilename, + coordinateOperationAccuracies()) + ->inverseAsTransformation(); + } else { + return createGravityRelatedHeightToGeographic3D( + createSimilarPropertiesTransformation(self), sourceCRS(), + targetCRS(), projFilename, coordinateOperationAccuracies()); + } + } + } + + if (isGeographic3DToGravityRelatedHeight(method(), false)) { + const auto &fileParameter = + parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, + EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + auto filename = fileParameter->valueFile(); + if (databaseContext->lookForGridAlternative( + filename, projFilename, projGridFormat, inverseDirection)) { + + if (filename == projFilename) { + assert(!inverseDirection); + return self; + } + + auto parameters = std::vector<OperationParameterNNPtr>{ + createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)}; + if (inverseDirection) { + return create(createPropertiesForInverse( + self.as_nullable().get(), true, false), + targetCRS(), sourceCRS(), nullptr, + createSimilarPropertiesMethod(method()), + parameters, {ParameterValue::createFilename( + projFilename)}, + coordinateOperationAccuracies()) + ->inverseAsTransformation(); + } else { + return create( + createSimilarPropertiesTransformation(self), + sourceCRS(), targetCRS(), nullptr, + createSimilarPropertiesMethod(method()), parameters, + {ParameterValue::createFilename(projFilename)}, + coordinateOperationAccuracies()); + } + } + } + } + + return self; +} +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static void ThrowExpectionNotGeodeticGeographic(const char *trfrm_name) { + throw io::FormattingException(concat("Can apply ", std::string(trfrm_name), + " only to GeodeticCRS / " + "GeographicCRS")); +} + +// --------------------------------------------------------------------------- + +static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter, + const crs::CRSNNPtr &crs, + const char *trfrm_name) { + auto sourceCRSGeog = dynamic_cast<const crs::GeographicCRS *>(crs.get()); + if (sourceCRSGeog) { + formatter->startInversion(); + sourceCRSGeog->_exportToPROJString(formatter); + formatter->stopInversion(); + + formatter->addStep("cart"); + sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); + } else { + auto sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get()); + if (!sourceCRSGeod) { + ThrowExpectionNotGeodeticGeographic(trfrm_name); + } + formatter->startInversion(); + sourceCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter); + formatter->stopInversion(); + } +} +// --------------------------------------------------------------------------- + +static void setupPROJGeodeticTargetCRS(io::PROJStringFormatter *formatter, + const crs::CRSNNPtr &crs, + const char *trfrm_name) { + auto targetCRSGeog = dynamic_cast<const crs::GeographicCRS *>(crs.get()); + if (targetCRSGeog) { + formatter->addStep("cart"); + formatter->setCurrentStepInverted(true); + targetCRSGeog->ellipsoid()->_exportToPROJString(formatter); + + targetCRSGeog->_exportToPROJString(formatter); + } else { + auto targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get()); + if (!targetCRSGeod) { + ThrowExpectionNotGeodeticGeographic(trfrm_name); + } + targetCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter); + } +} + +inline static void consume_unused(const std::string &) {} + +//! @endcond +// --------------------------------------------------------------------------- + +void Transformation::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(FormattingException) +{ + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + throw io::FormattingException( + "Transformation cannot be exported as a PROJ.4 string"); + } + + bool positionVectorConvention = true; + bool sevenParamsTransform = false; + bool threeParamsTransform = false; + bool fifteenParamsTransform = false; + const auto &l_method = method(); + const int methodEPSGCode = l_method->getEPSGCode(); + const auto &methodName = l_method->nameStr(); + const auto paramCount = parameterValues().size(); + const bool l_isTimeDependent = isTimeDependent(methodName); + const bool isPositionVector = + ci_find(methodName, "Position Vector") != std::string::npos || + ci_find(methodName, "PV") != std::string::npos; + const bool isCoordinateFrame = + ci_find(methodName, "Coordinate Frame") != std::string::npos || + ci_find(methodName, "CF") != std::string::npos; + if ((paramCount == 7 && isCoordinateFrame && !l_isTimeDependent) || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || + methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) { + positionVectorConvention = false; + sevenParamsTransform = true; + } else if ( + (paramCount == 15 && isCoordinateFrame && l_isTimeDependent) || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) { + positionVectorConvention = false; + fifteenParamsTransform = true; + } else if ((paramCount == 7 && isPositionVector && !l_isTimeDependent) || + methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) { + sevenParamsTransform = true; + } else if ( + (paramCount == 15 && isPositionVector && l_isTimeDependent) || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) { + fifteenParamsTransform = true; + } else if ((paramCount == 3 && + ci_find(methodName, "Geocentric translations") != + std::string::npos) || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) { + threeParamsTransform = true; + } + if (threeParamsTransform || sevenParamsTransform || + fifteenParamsTransform) { + double x = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); + double y = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); + double z = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); + + setupPROJGeodeticSourceCRS(formatter, sourceCRS(), "Helmert"); + + formatter->addStep("helmert"); + formatter->addParam("x", x); + formatter->addParam("y", y); + formatter->addParam("z", z); + if (sevenParamsTransform || fifteenParamsTransform) { + double rx = + parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND); + double ry = + parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND); + double rz = + parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND); + double scaleDiff = + parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, + common::UnitOfMeasure::PARTS_PER_MILLION); + formatter->addParam("rx", rx); + formatter->addParam("ry", ry); + formatter->addParam("rz", rz); + formatter->addParam("s", scaleDiff); + if (fifteenParamsTransform) { + double rate_x = parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION, + common::UnitOfMeasure::METRE_PER_YEAR); + double rate_y = parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION, + common::UnitOfMeasure::METRE_PER_YEAR); + double rate_z = parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION, + common::UnitOfMeasure::METRE_PER_YEAR); + double rate_rx = parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR); + double rate_ry = parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR); + double rate_rz = parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND_PER_YEAR); + double rate_scaleDiff = parameterValueNumeric( + EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE, + common::UnitOfMeasure::PPM_PER_YEAR); + double referenceEpochYear = + parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH, + common::UnitOfMeasure::YEAR); + formatter->addParam("dx", rate_x); + formatter->addParam("dy", rate_y); + formatter->addParam("dz", rate_z); + formatter->addParam("drx", rate_rx); + formatter->addParam("dry", rate_ry); + formatter->addParam("drz", rate_rz); + formatter->addParam("ds", rate_scaleDiff); + formatter->addParam("t_epoch", referenceEpochYear); + } + if (positionVectorConvention) { + formatter->addParam("convention", "position_vector"); + } else { + formatter->addParam("convention", "coordinate_frame"); + } + } + + setupPROJGeodeticTargetCRS(formatter, targetCRS(), "Helmert"); + + return; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC || + methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D) { + consume_unused(EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC); + consume_unused(EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC); + consume_unused(EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D); + consume_unused(EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D); + consume_unused(EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D); + consume_unused(EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D); + positionVectorConvention = + isPositionVector || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D || + methodEPSGCode == + EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D; + + double x = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); + double y = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); + double z = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); + double rx = parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND); + double ry = parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND); + double rz = parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, + common::UnitOfMeasure::ARC_SECOND); + double scaleDiff = + parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, + common::UnitOfMeasure::PARTS_PER_MILLION); + + double px = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT); + double py = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT); + double pz = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT); + + setupPROJGeodeticSourceCRS(formatter, sourceCRS(), + "Molodensky-Badekas"); + + formatter->addStep("molobadekas"); + formatter->addParam("x", x); + formatter->addParam("y", y); + formatter->addParam("z", z); + formatter->addParam("rx", rx); + formatter->addParam("ry", ry); + formatter->addParam("rz", rz); + formatter->addParam("s", scaleDiff); + formatter->addParam("px", px); + formatter->addParam("py", py); + formatter->addParam("pz", pz); + if (positionVectorConvention) { + formatter->addParam("convention", "position_vector"); + } else { + formatter->addParam("convention", "coordinate_frame"); + } + + setupPROJGeodeticTargetCRS(formatter, targetCRS(), + "Molodensky-Badekas"); + + return; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY || + methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { + double x = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); + double y = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); + double z = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); + double da = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE); + double df = parameterValueNumericAsSI( + EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE); + + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + "Can apply Molodensky only to GeographicCRS"); + } + + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); + if (!targetCRSGeog) { + throw io::FormattingException( + "Can apply Molodensky only to GeographicCRS"); + } + + formatter->startInversion(); + sourceCRSGeog->_exportToPROJString(formatter); + formatter->stopInversion(); + + formatter->addStep("molodensky"); + sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); + formatter->addParam("dx", x); + formatter->addParam("dy", y); + formatter->addParam("dz", z); + formatter->addParam("da", da); + formatter->addParam("df", df); + + if (ci_find(methodName, "Abridged") != std::string::npos || + methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { + formatter->addParam("abridged"); + } + + targetCRSGeog->_exportToPROJString(formatter); + + return; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) { + double offsetLat = + parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, + common::UnitOfMeasure::ARC_SECOND); + double offsetLong = + parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, + common::UnitOfMeasure::ARC_SECOND); + + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + "Can apply Geographic 2D offsets only to GeographicCRS"); + } + + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); + if (!targetCRSGeog) { + throw io::FormattingException( + "Can apply Geographic 2D offsets only to GeographicCRS"); + } + + formatter->startInversion(); + sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + if (offsetLat != 0.0 || offsetLong != 0.0) { + formatter->addStep("geogoffset"); + formatter->addParam("dlat", offsetLat); + formatter->addParam("dlon", offsetLong); + } + + targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + + return; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) { + double offsetLat = + parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, + common::UnitOfMeasure::ARC_SECOND); + double offsetLong = + parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, + common::UnitOfMeasure::ARC_SECOND); + double offsetHeight = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); + + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + "Can apply Geographic 3D offsets only to GeographicCRS"); + } + + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); + if (!targetCRSGeog) { + throw io::FormattingException( + "Can apply Geographic 3D offsets only to GeographicCRS"); + } + + formatter->startInversion(); + sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) { + formatter->addStep("geogoffset"); + formatter->addParam("dlat", offsetLat); + formatter->addParam("dlon", offsetLong); + formatter->addParam("dh", offsetHeight); + } + + targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + + return; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) { + double offsetLat = + parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, + common::UnitOfMeasure::ARC_SECOND); + double offsetLong = + parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, + common::UnitOfMeasure::ARC_SECOND); + double offsetHeight = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_UNDULATION); + + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); + if (!sourceCRSGeog) { + auto sourceCRSCompound = + dynamic_cast<const crs::CompoundCRS *>(sourceCRS().get()); + if (sourceCRSCompound) { + sourceCRSGeog = sourceCRSCompound->extractGeographicCRS().get(); + } + if (!sourceCRSGeog) { + throw io::FormattingException("Can apply Geographic 2D with " + "height offsets only to " + "GeographicCRS / CompoundCRS"); + } + } + + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); + if (!targetCRSGeog) { + auto targetCRSCompound = + dynamic_cast<const crs::CompoundCRS *>(targetCRS().get()); + if (targetCRSCompound) { + targetCRSGeog = targetCRSCompound->extractGeographicCRS().get(); + } + if (!targetCRSGeog) { + throw io::FormattingException("Can apply Geographic 2D with " + "height offsets only to " + "GeographicCRS / CompoundCRS"); + } + } + + formatter->startInversion(); + sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) { + formatter->addStep("geogoffset"); + formatter->addParam("dlat", offsetLat); + formatter->addParam("dlon", offsetLong); + formatter->addParam("dh", offsetHeight); + } + + targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + + return; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) { + + auto sourceCRSVert = + dynamic_cast<const crs::VerticalCRS *>(sourceCRS().get()); + if (!sourceCRSVert) { + throw io::FormattingException( + "Can apply Vertical offset only to VerticalCRS"); + } + + auto targetCRSVert = + dynamic_cast<const crs::VerticalCRS *>(targetCRS().get()); + if (!targetCRSVert) { + throw io::FormattingException( + "Can apply Vertical offset only to VerticalCRS"); + } + + auto offsetHeight = + parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); + + formatter->startInversion(); + sourceCRSVert->addLinearUnitConvert(formatter); + formatter->stopInversion(); + + formatter->addStep("geogoffset"); + formatter->addParam("dh", offsetHeight); + + targetCRSVert->addLinearUnitConvert(formatter); + + return; + } + + // Substitute grid names with PROJ friendly names. + if (formatter->databaseContext()) { + auto alternate = substitutePROJAlternativeGridNames( + NN_NO_CHECK(formatter->databaseContext())); + auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>( + shared_from_this().as_nullable())); + + if (alternate != self) { + alternate->_exportToPROJString(formatter); + return; + } + } + + const bool isMethodInverseOf = starts_with(methodName, INVERSE_OF); + + const auto &NTv1Filename = _getNTv1Filename(this, true); + const auto &NTv2Filename = _getNTv2Filename(this, true); + const auto &CTABLE2Filename = _getCTABLE2Filename(this, true); + const auto &hGridShiftFilename = + !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty() + ? NTv2Filename + : CTABLE2Filename; + if (!hGridShiftFilename.empty()) { + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + concat("Can apply ", methodName, " only to GeographicCRS")); + } + + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); + if (!targetCRSGeog) { + throw io::FormattingException( + concat("Can apply ", methodName, " only to GeographicCRS")); + } + + formatter->startInversion(); + sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + if (isMethodInverseOf) { + formatter->startInversion(); + } + formatter->addStep("hgridshift"); + formatter->addParam("grids", hGridShiftFilename); + if (isMethodInverseOf) { + formatter->stopInversion(); + } + + targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + + return; + } + + const auto &heightFilename = _getHeightToGeographic3DFilename(this, true); + if (!heightFilename.empty()) { + if (isMethodInverseOf) { + formatter->startInversion(); + } + formatter->addStep("vgridshift"); + formatter->addParam("grids", heightFilename); + if (isMethodInverseOf) { + formatter->stopInversion(); + } + return; + } + + if (isGeographic3DToGravityRelatedHeight(method(), true)) { + auto fileParameter = + parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, + EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + auto filename = fileParameter->valueFile(); + if (isMethodInverseOf) { + formatter->startInversion(); + } + formatter->addStep("vgridshift"); + formatter->addParam("grids", filename); + if (isMethodInverseOf) { + formatter->stopInversion(); + } + return; + } + } + + if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON) { + auto fileParameter = + parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, + EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE); + if (fileParameter && + fileParameter->type() == ParameterValue::Type::FILENAME) { + formatter->addStep("vgridshift"); + // The vertcon grids go from NGVD 29 to NAVD 88, with units + // in millimeter (see + // https://github.com/OSGeo/proj.4/issues/1071) + formatter->addParam("grids", fileParameter->valueFile()); + formatter->addParam("multiplier", 0.001); + return; + } + } + + if (isLongitudeRotation()) { + double offsetDeg = + parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, + common::UnitOfMeasure::DEGREE); + + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); + if (!sourceCRSGeog) { + throw io::FormattingException( + concat("Can apply ", methodName, " only to GeographicCRS")); + } + + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); + if (!targetCRSGeog) { + throw io::FormattingException( + concat("Can apply ", methodName + " only to GeographicCRS")); + } + + if (!sourceCRSGeog->ellipsoid()->_isEquivalentTo( + targetCRSGeog->ellipsoid().get())) { + // This is arguable if we should check this... + throw io::FormattingException("Can apply Longitude rotation " + "only to SRS with same " + "ellipsoid"); + } + + formatter->startInversion(); + sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + bool done = false; + if (offsetDeg != 0.0) { + // Optimization: as we are doing nominally a +step=inv, + // if the negation of the offset value is a well-known name, + // then use forward case with this name. + auto projPMName = datum::PrimeMeridian::getPROJStringWellKnownName( + common::Angle(-offsetDeg)); + if (!projPMName.empty()) { + done = true; + formatter->addStep("longlat"); + sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); + formatter->addParam("pm", projPMName); + } + } + if (!done) { + // To actually add the offset, we must use the reverse longlat + // operation. + formatter->startInversion(); + formatter->addStep("longlat"); + sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); + datum::PrimeMeridian::create(util::PropertyMap(), + common::Angle(offsetDeg)) + ->_exportToPROJString(formatter); + formatter->stopInversion(); + } + + targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); + + return; + } + + if (exportToPROJStringGeneric(formatter)) { + return; + } + + throw io::FormattingException("Unimplemented"); +} + +// --------------------------------------------------------------------------- + +bool SingleOperation::exportToPROJStringGeneric( + io::PROJStringFormatter *formatter) const { + const int methodEPSGCode = method()->getEPSGCode(); + + if (methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION) { + const double A0 = parameterValueMeasure(EPSG_CODE_PARAMETER_A0).value(); + const double A1 = parameterValueMeasure(EPSG_CODE_PARAMETER_A1).value(); + const double A2 = parameterValueMeasure(EPSG_CODE_PARAMETER_A2).value(); + const double B0 = parameterValueMeasure(EPSG_CODE_PARAMETER_B0).value(); + const double B1 = parameterValueMeasure(EPSG_CODE_PARAMETER_B1).value(); + const double B2 = parameterValueMeasure(EPSG_CODE_PARAMETER_B2).value(); + + // Do not mess with axis unit and order for that transformation + + formatter->addStep("affine"); + formatter->addParam("xoff", A0); + formatter->addParam("s11", A1); + formatter->addParam("s12", A2); + formatter->addParam("yoff", B0); + formatter->addParam("s21", B1); + formatter->addParam("s22", B2); + + return true; + } + + if (isAxisOrderReversal(methodEPSGCode)) { + formatter->addStep("axisswap"); + formatter->addParam("order", "2,1"); + return true; + } + + if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) { + + auto sourceCRSGeod = + dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get()); + auto targetCRSGeod = + dynamic_cast<const crs::GeodeticCRS *>(targetCRS().get()); + if (sourceCRSGeod && targetCRSGeod) { + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRSGeod); + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRSGeod); + bool isSrcGeocentric = sourceCRSGeod->isGeocentric(); + bool isSrcGeographic = sourceCRSGeog != nullptr; + bool isTargetGeocentric = targetCRSGeod->isGeocentric(); + bool isTargetGeographic = targetCRSGeog != nullptr; + if ((isSrcGeocentric && isTargetGeographic) || + (isSrcGeographic && isTargetGeocentric)) { + + formatter->startInversion(); + sourceCRSGeod->_exportToPROJString(formatter); + formatter->stopInversion(); + + targetCRSGeod->_exportToPROJString(formatter); + + return true; + } + } + + throw io::FormattingException("Invalid nature of source and/or " + "targetCRS for Geographic/Geocentric " + "conversion"); + } + + return false; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PointMotionOperation::~PointMotionOperation() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ConcatenatedOperation::Private { + std::vector<CoordinateOperationNNPtr> operations_{}; + bool computedName_ = false; + + explicit Private(const std::vector<CoordinateOperationNNPtr> &operationsIn) + : operations_(operationsIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ConcatenatedOperation::~ConcatenatedOperation() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +ConcatenatedOperation::ConcatenatedOperation( + const std::vector<CoordinateOperationNNPtr> &operationsIn) + : CoordinateOperation(), d(internal::make_unique<Private>(operationsIn)) {} + +// --------------------------------------------------------------------------- + +/** \brief Return the operation steps of the concatenated operation. + * + * @return the operation steps. + */ +const std::vector<CoordinateOperationNNPtr> & +ConcatenatedOperation::operations() const { + return d->operations_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ConcatenatedOperation + * + * @param properties See \ref general_properties. At minimum the name should + * be + * defined. + * @param operationsIn Vector of the CoordinateOperation steps. + * @param accuracies Vector of positional accuracy (might be empty). + * @return new Transformation. + * @throws InvalidOperation + */ +ConcatenatedOperationNNPtr ConcatenatedOperation::create( + const util::PropertyMap &properties, + const std::vector<CoordinateOperationNNPtr> &operationsIn, + const std::vector<metadata::PositionalAccuracyNNPtr> + &accuracies) // throw InvalidOperation +{ + if (operationsIn.size() < 2) { + throw InvalidOperation( + "ConcatenatedOperation must have at least 2 operations"); + } + crs::CRSPtr lastTargetCRS; + for (size_t i = 0; i < operationsIn.size(); i++) { + auto l_sourceCRS = operationsIn[i]->sourceCRS(); + auto l_targetCRS = operationsIn[i]->targetCRS(); + if (l_sourceCRS == nullptr || l_targetCRS == nullptr) { + throw InvalidOperation("At least one of the operation lacks a " + "source and/or target CRS"); + } + if (i >= 1) { + const auto &sourceCRSIds = l_sourceCRS->identifiers(); + const auto &targetCRSIds = lastTargetCRS->identifiers(); + if (sourceCRSIds.size() == 1 && targetCRSIds.size() == 1 && + sourceCRSIds[0]->code() == targetCRSIds[0]->code() && + *sourceCRSIds[0]->codeSpace() == + *targetCRSIds[0]->codeSpace()) { + // same id --> ok + } else if (!l_sourceCRS->_isEquivalentTo( + lastTargetCRS.get(), + util::IComparable::Criterion::EQUIVALENT)) { + throw InvalidOperation( + "Inconsistent chaining of CRS in operations"); + } + } + lastTargetCRS = l_targetCRS; + } + auto op = ConcatenatedOperation::nn_make_shared<ConcatenatedOperation>( + operationsIn); + op->assignSelf(op); + op->setProperties(properties); + op->setCRSs(NN_NO_CHECK(operationsIn[0]->sourceCRS()), + NN_NO_CHECK(operationsIn.back()->targetCRS()), nullptr); + op->setAccuracies(accuracies); + return op; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static std::string computeConcatenatedName( + const std::vector<CoordinateOperationNNPtr> &flattenOps) { + std::string name; + for (const auto &subOp : flattenOps) { + if (!name.empty()) { + name += " + "; + } + const auto &l_name = subOp->nameStr(); + if (l_name.empty()) { + name += "unnamed"; + } else { + name += l_name; + } + } + return name; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ConcatenatedOperation, or return a single + * coordinate + * operation. + * + * This computes its accuracy from the sum of its member operations, its + * extent + * + * @param operationsIn Vector of the CoordinateOperation steps. + * @param checkExtent Whether we should check the non-emptyness of the + * intersection + * of the extents of the operations + * @throws InvalidOperation + */ +CoordinateOperationNNPtr ConcatenatedOperation::createComputeMetadata( + const std::vector<CoordinateOperationNNPtr> &operationsIn, + bool checkExtent) // throw InvalidOperation +{ + util::PropertyMap properties; + + if (operationsIn.size() == 1) { + return operationsIn[0]; + } + + std::vector<CoordinateOperationNNPtr> flattenOps; + for (const auto &subOp : operationsIn) { + auto subOpConcat = + dynamic_cast<const ConcatenatedOperation *>(subOp.get()); + if (subOpConcat) { + auto subOps = subOpConcat->operations(); + for (const auto &subSubOp : subOps) { + flattenOps.emplace_back(subSubOp); + } + } else { + flattenOps.emplace_back(subOp); + } + } + if (flattenOps.size() == 1) { + return flattenOps[0]; + } + + properties.set(common::IdentifiedObject::NAME_KEY, + computeConcatenatedName(flattenOps)); + + bool emptyIntersection = false; + auto extent = getExtent(flattenOps, false, emptyIntersection); + if (checkExtent && emptyIntersection) { + std::string msg( + "empty intersection of area of validity of concantenated " + "operations"); + throw InvalidOperationEmptyIntersection(msg); + } + if (extent) { + properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + NN_NO_CHECK(extent)); + } + + std::vector<metadata::PositionalAccuracyNNPtr> accuracies; + const double accuracy = getAccuracy(flattenOps); + if (accuracy >= 0.0) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create(toString(accuracy))); + } + + auto op = create(properties, flattenOps, accuracies); + op->d->computedName_ = true; + return op; +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr ConcatenatedOperation::inverse() const { + std::vector<CoordinateOperationNNPtr> inversedOperations; + auto l_operations = operations(); + inversedOperations.reserve(l_operations.size()); + for (const auto &operation : l_operations) { + inversedOperations.emplace_back(operation->inverse()); + } + std::reverse(inversedOperations.begin(), inversedOperations.end()); + + auto properties = createPropertiesForInverse(this, false, false); + if (d->computedName_) { + properties.set(common::IdentifiedObject::NAME_KEY, + computeConcatenatedName(inversedOperations)); + } + + auto op = + create(properties, inversedOperations, coordinateOperationAccuracies()); + op->d->computedName_ = d->computedName_; + return op; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ConcatenatedOperation::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2 || !formatter->use2018Keywords()) { + throw io::FormattingException( + "Transformation can only be exported to WKT2:2018"); + } + + formatter->startNode(io::WKTConstants::CONCATENATEDOPERATION, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + formatter->startNode(io::WKTConstants::SOURCECRS, false); + sourceCRS()->_exportToWKT(formatter); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::TARGETCRS, false); + targetCRS()->_exportToWKT(formatter); + formatter->endNode(); + + for (const auto &operation : operations()) { + formatter->startNode(io::WKTConstants::STEP, false); + operation->_exportToWKT(formatter); + formatter->endNode(); + } + + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void ConcatenatedOperation::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(FormattingException) +{ + for (const auto &operation : operations()) { + operation->_exportToPROJString(formatter); + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool ConcatenatedOperation::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherCO = dynamic_cast<const ConcatenatedOperation *>(other); + if (otherCO == nullptr || !ObjectUsage::_isEquivalentTo(other, criterion)) { + return false; + } + const auto &steps = operations(); + const auto &otherSteps = otherCO->operations(); + if (steps.size() != otherSteps.size()) { + return false; + } + for (size_t i = 0; i < steps.size(); i++) { + if (!steps[i]->_isEquivalentTo(otherSteps[i].get(), criterion)) { + return false; + } + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +std::set<GridDescription> ConcatenatedOperation::gridsNeeded( + const io::DatabaseContextPtr &databaseContext) const { + std::set<GridDescription> res; + for (const auto &operation : operations()) { + const auto l_gridsNeeded = operation->gridsNeeded(databaseContext); + for (const auto &gridDesc : l_gridsNeeded) { + res.insert(gridDesc); + } + } + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CoordinateOperationContext::Private { + io::AuthorityFactoryPtr authorityFactory_{}; + metadata::ExtentPtr extent_{}; + double accuracy_ = 0.0; + SourceTargetCRSExtentUse sourceAndTargetCRSExtentUse_ = + CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST; + SpatialCriterion spatialCriterion_ = + CoordinateOperationContext::SpatialCriterion::STRICT_CONTAINMENT; + bool usePROJNames_ = true; + GridAvailabilityUse gridAvailabilityUse_ = + GridAvailabilityUse::USE_FOR_SORTING; + bool allowUseIntermediateCRS_ = true; + std::vector<std::pair<std::string, std::string>> + intermediateCRSAuthCodes_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateOperationContext::~CoordinateOperationContext() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateOperationContext::CoordinateOperationContext() + : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +/** \brief Return the authority factory, or null */ +const io::AuthorityFactoryPtr & +CoordinateOperationContext::getAuthorityFactory() const { + return d->authorityFactory_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the desired area of interest, or null */ +const metadata::ExtentPtr & +CoordinateOperationContext::getAreaOfInterest() const { + return d->extent_; +} + +// --------------------------------------------------------------------------- + +/** \brief Set the desired area of interest, or null */ +void CoordinateOperationContext::setAreaOfInterest( + const metadata::ExtentPtr &extent) { + d->extent_ = extent; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the desired accuracy (in metre), or 0 */ +double CoordinateOperationContext::getDesiredAccuracy() const { + return d->accuracy_; +} + +// --------------------------------------------------------------------------- + +/** \brief Set the desired accuracy (in metre), or 0 */ +void CoordinateOperationContext::setDesiredAccuracy(double accuracy) { + d->accuracy_ = accuracy; +} + +// --------------------------------------------------------------------------- + +/** \brief Set how source and target CRS extent should be used + * when considering if a transformation can be used (only takes effect if + * no area of interest is explicitly defined). + * + * The default is + * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST. + */ +void CoordinateOperationContext::setSourceAndTargetCRSExtentUse( + SourceTargetCRSExtentUse use) { + d->sourceAndTargetCRSExtentUse_ = use; +} + +// --------------------------------------------------------------------------- + +/** \brief Return how source and target CRS extent should be used + * when considering if a transformation can be used (only takes effect if + * no area of interest is explicitly defined). + * + * The default is + * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST. + */ +CoordinateOperationContext::SourceTargetCRSExtentUse +CoordinateOperationContext::getSourceAndTargetCRSExtentUse() const { + return d->sourceAndTargetCRSExtentUse_; +} + +// --------------------------------------------------------------------------- + +/** \brief Set the spatial criterion to use when comparing the area of + * validity + * of coordinate operations with the area of interest / area of validity of + * source and target CRS. + * + * The default is STRICT_CONTAINMENT. + */ +void CoordinateOperationContext::setSpatialCriterion( + SpatialCriterion criterion) { + d->spatialCriterion_ = criterion; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the spatial criterion to use when comparing the area of + * validity + * of coordinate operations with the area of interest / area of validity of + * source and target CRS. + * + * The default is STRICT_CONTAINMENT. + */ +CoordinateOperationContext::SpatialCriterion +CoordinateOperationContext::getSpatialCriterion() const { + return d->spatialCriterion_; +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether PROJ alternative grid names should be substituted to + * the official authority names. + * + * This only has effect is an authority factory with a non-null database context + * has been attached to this context. + * + * If set to false, it is still possible to + * obtain later the substitution by using io::PROJStringFormatter::create() + * with a non-null database context. + * + * The default is true. + */ +void CoordinateOperationContext::setUsePROJAlternativeGridNames( + bool usePROJNames) { + d->usePROJNames_ = usePROJNames; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether PROJ alternative grid names should be substituted to + * the official authority names. + * + * The default is true. + */ +bool CoordinateOperationContext::getUsePROJAlternativeGridNames() const { + return d->usePROJNames_; +} + +// --------------------------------------------------------------------------- + +/** \brief Set how grid availability is used. + * + * The default is USE_FOR_SORTING. + */ +void CoordinateOperationContext::setGridAvailabilityUse( + GridAvailabilityUse use) { + d->gridAvailabilityUse_ = use; +} + +// --------------------------------------------------------------------------- + +/** \brief Return how grid availability is used. + * + * The default is USE_FOR_SORTING. + */ +CoordinateOperationContext::GridAvailabilityUse +CoordinateOperationContext::getGridAvailabilityUse() const { + return d->gridAvailabilityUse_; +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether an intermediate pivot CRS can be used for researching + * coordinate operations between a source and target CRS. + * + * Concretely if in the database there is an operation from A to C + * (or C to A), and another one from C to B (or B to C), but no direct + * operation between A and B, setting this parameter to true, allow + * chaining both operations. + * + * The current implementation is limited to researching one intermediate + * step. + * + * By default, all potential C candidates will be used. setIntermediateCRS() + * can be used to restrict them. + * + * The default is true. + */ +void CoordinateOperationContext::setAllowUseIntermediateCRS(bool use) { + d->allowUseIntermediateCRS_ = use; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether an intermediate pivot CRS can be used for researching + * coordinate operations between a source and target CRS. + * + * Concretely if in the database there is an operation from A to C + * (or C to A), and another one from C to B (or B to C), but no direct + * operation between A and B, setting this parameter to true, allow + * chaining both operations. + * + * The default is true. + */ +bool CoordinateOperationContext::getAllowUseIntermediateCRS() const { + return d->allowUseIntermediateCRS_; +} + +// --------------------------------------------------------------------------- + +/** \brief Restrict the potential pivot CRSs that can be used when trying to + * build a coordinate operation between two CRS that have no direct operation. + * + * @param intermediateCRSAuthCodes a vector of (auth_name, code) that can be + * used as potential pivot RS + */ +void CoordinateOperationContext::setIntermediateCRS( + const std::vector<std::pair<std::string, std::string>> + &intermediateCRSAuthCodes) { + d->intermediateCRSAuthCodes_ = intermediateCRSAuthCodes; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the potential pivot CRSs that can be used when trying to + * build a coordinate operation between two CRS that have no direct operation. + * + */ +const std::vector<std::pair<std::string, std::string>> & +CoordinateOperationContext::getIntermediateCRS() const { + return d->intermediateCRSAuthCodes_; +} + +// --------------------------------------------------------------------------- + +/** \brief Creates a context for a coordinate operation. + * + * If a non null authorityFactory is provided, the resulting context should + * not be used simultaneously by more than one thread. + * + * @param authorityFactory Authority factory, or null if no database lookup + * is allowed. + * Use io::authorityFactory::create(context, std::string()) to allow all + * authorities to be used. + * @param extent Area of interest, or null if none is known. + * @param accuracy Maximum allowed accuracy in metre, as specified in or + * 0 to get best accuracy. + * @return a new context. + */ +CoordinateOperationContextNNPtr CoordinateOperationContext::create( + const io::AuthorityFactoryPtr &authorityFactory, + const metadata::ExtentPtr &extent, double accuracy) { + auto ctxt = NN_NO_CHECK( + CoordinateOperationContext::make_unique<CoordinateOperationContext>()); + ctxt->d->authorityFactory_ = authorityFactory; + ctxt->d->extent_ = extent; + ctxt->d->accuracy_ = accuracy; + return ctxt; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CoordinateOperationFactory::Private { + + struct Context { + // This is the source CRS and target CRS of the initial + // CoordinateOperationFactory::createOperations() public call, not + // necessarily the ones of intermediate + // CoordinateOperationFactory::Private::createOperations() calls. + // This is used to compare transformations area of use against the + // area of use of the source & target CRS. + const crs::CRSNNPtr &sourceCRS; + const crs::CRSNNPtr &targetCRS; + const CoordinateOperationContextNNPtr &context; + bool inCreateOperationsWithDatumPivotAntiRecursion = false; + + Context(const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, + const CoordinateOperationContextNNPtr &contextIn) + : sourceCRS(sourceCRSIn), targetCRS(targetCRSIn), + context(contextIn) {} + }; + + static std::vector<CoordinateOperationNNPtr> + createOperations(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS, Context &context); + + private: + static std::vector<CoordinateOperationNNPtr> createOperationsGeogToGeog( + std::vector<CoordinateOperationNNPtr> &res, + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst); + + static void createOperationsWithDatumPivot( + std::vector<CoordinateOperationNNPtr> &res, + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const crs::GeodeticCRS *geodSrc, const crs::GeodeticCRS *geodDst, + Context &context); + + static bool + hasPerfectAccuracyResult(const std::vector<CoordinateOperationNNPtr> &res, + const Context &context); + + static ConversionNNPtr + createGeographicGeocentric(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS); +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateOperationFactory::~CoordinateOperationFactory() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateOperationFactory::CoordinateOperationFactory() : d(nullptr) {} + +// --------------------------------------------------------------------------- + +/** \brief Find a CoordinateOperation from sourceCRS to targetCRS. + * + * This is a helper of createOperations(), using a coordinate operation + * context + * with no authority factory (so no catalog searching is done), no desired + * accuracy and no area of interest. + * This returns the first operation of the result set of createOperations(), + * or null if none found. + * + * @param sourceCRS source CRS. + * @param targetCRS source CRS. + * @return a CoordinateOperation or nullptr. + */ +CoordinateOperationPtr CoordinateOperationFactory::createOperation( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) const { + auto res = createOperations( + sourceCRS, targetCRS, + CoordinateOperationContext::create(nullptr, nullptr, 0.0)); + if (!res.empty()) { + return res[0]; + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +struct PrecomputedOpCharacteristics { + double area_{}; + double accuracy_{}; + bool hasGrids_ = false; + bool gridsAvailable_ = false; + bool gridsKnown_ = false; + size_t stepCount_ = 0; + + PrecomputedOpCharacteristics() = default; + PrecomputedOpCharacteristics(double area, double accuracy, bool hasGrids, + bool gridsAvailable, bool gridsKnown, + size_t stepCount) + : area_(area), accuracy_(accuracy), hasGrids_(hasGrids), + gridsAvailable_(gridsAvailable), gridsKnown_(gridsKnown), + stepCount_(stepCount) {} +}; + +// --------------------------------------------------------------------------- + +// We could have used a lambda instead of this old-school way, but +// filterAndSort() is already huge. +struct SortFunction { + + const std::map<CoordinateOperation *, PrecomputedOpCharacteristics> ↦ + + explicit SortFunction(const std::map<CoordinateOperation *, + PrecomputedOpCharacteristics> &mapIn) + : map(mapIn) {} + + // Sorting function + // Return true if a < b + bool operator()(const CoordinateOperationNNPtr &a, + const CoordinateOperationNNPtr &b) const { + auto iterA = map.find(a.get()); + assert(iterA != map.end()); + auto iterB = map.find(b.get()); + assert(iterB != map.end()); + + // CAUTION: the order of the comparisons is extremely important + // to get the intended result. + + if (iterA->second.hasGrids_ && iterB->second.hasGrids_) { + // Operations where grids are all available go before other + if (iterA->second.gridsAvailable_ && + !iterB->second.gridsAvailable_) { + return true; + } + if (iterB->second.gridsAvailable_ && + !iterA->second.gridsAvailable_) { + return false; + } + } + + // Operations where grids are all known in our DB go before other + if (iterA->second.gridsKnown_ && !iterB->second.gridsKnown_) { + return true; + } + if (iterB->second.gridsKnown_ && !iterA->second.gridsKnown_) { + return false; + } + + // Operations with known accuracy go before those with unknown accuracy + const double accuracyA = iterA->second.accuracy_; + const double accuracyB = iterB->second.accuracy_; + if (accuracyA >= 0 && accuracyB < 0) { + return true; + } + if (accuracyB >= 0 && accuracyA < 0) { + return false; + } + + // Operations with larger non-zero area of use go before those with + // lower one + const double areaA = iterA->second.area_; + const double areaB = iterB->second.area_; + if (areaA > 0) { + if (areaA > areaB) { + return true; + } + if (areaA < areaB) { + return false; + } + } else if (areaB > 0) { + return false; + } + + // Operations with better accuracy go before those with worse one + if (accuracyA >= 0 && accuracyA < accuracyB) { + return true; + } + if (accuracyB >= 0 && accuracyB < accuracyA) { + return false; + } + + if (accuracyA >= 0 && accuracyA == accuracyB) { + // same accuracy ? then prefer operations without grids + if (!iterA->second.hasGrids_ && iterB->second.hasGrids_) { + return true; + } + if (iterA->second.hasGrids_ && !iterB->second.hasGrids_) { + return false; + } + } else if (accuracyA < 0 && accuracyB < 0) { + // unknown accuracy ? then prefer operations with grids, which + // are likely to have best practical accuracy + if (iterA->second.hasGrids_ && !iterB->second.hasGrids_) { + return true; + } + if (!iterA->second.hasGrids_ && iterB->second.hasGrids_) { + return false; + } + } + + // The less intermediate steps, the better + if (iterA->second.stepCount_ < iterB->second.stepCount_) { + return true; + } + if (iterB->second.stepCount_ < iterA->second.stepCount_) { + return false; + } + + const auto &a_name = a->nameStr(); + const auto &b_name = b->nameStr(); + // The shorter name, the better ? + if (a_name.size() < b_name.size()) { + return true; + } + if (b_name.size() < a_name.size()) { + return false; + } + + // Arbitrary final criterion + return a_name < b_name; + } +}; + +// --------------------------------------------------------------------------- + +static size_t getStepCount(const CoordinateOperationNNPtr &op) { + auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get()); + size_t stepCount = 1; + if (concat) { + stepCount = concat->operations().size(); + } + return stepCount; +} + +// --------------------------------------------------------------------------- + +struct FilterAndSort { + + FilterAndSort(const std::vector<CoordinateOperationNNPtr> &sourceListIn, + const CoordinateOperationContextNNPtr &contextIn, + const crs::CRSNNPtr &sourceCRSIn, + const crs::CRSNNPtr &targetCRSIn, + bool forceStrictContainmentTest) + : sourceList(sourceListIn), context(contextIn), sourceCRS(sourceCRSIn), + targetCRS(targetCRSIn), sourceCRSExtent(getExtent(sourceCRS)), + targetCRSExtent(getExtent(targetCRS)), + areaOfInterest(context->getAreaOfInterest()), + desiredAccuracy(context->getDesiredAccuracy()), + sourceAndTargetCRSExtentUse( + context->getSourceAndTargetCRSExtentUse()) { + + computeAreaOfIntest(); + filterOut(forceStrictContainmentTest); + sort(); + + // And now that we have a sorted list, we can remove uninteresting + // results + // ... + removeSyntheticNullTransforms(); + removeUninterestingOps(); + removeDuplicateOps(); + removeSyntheticNullTransforms(); + } + + // ---------------------------------------------------------------------- + + // cppcheck-suppress functionStatic + const std::vector<CoordinateOperationNNPtr> &getRes() { return res; } + + // ---------------------------------------------------------------------- + private: + const std::vector<CoordinateOperationNNPtr> &sourceList; + const CoordinateOperationContextNNPtr &context; + const crs::CRSNNPtr &sourceCRS; + const crs::CRSNNPtr &targetCRS; + const metadata::ExtentPtr &sourceCRSExtent; + const metadata::ExtentPtr &targetCRSExtent; + metadata::ExtentPtr areaOfInterest; + const double desiredAccuracy = context->getDesiredAccuracy(); + const CoordinateOperationContext::SourceTargetCRSExtentUse + sourceAndTargetCRSExtentUse; + + bool hasOpThatContainsAreaOfInterest = false; + std::vector<CoordinateOperationNNPtr> res{}; + + // ---------------------------------------------------------------------- + void computeAreaOfIntest() { + + // Compute an area of interest from the CRS extent if the user did + // not specify one + if (!areaOfInterest) { + if (sourceAndTargetCRSExtentUse == + CoordinateOperationContext::SourceTargetCRSExtentUse:: + INTERSECTION) { + if (sourceCRSExtent && targetCRSExtent) { + areaOfInterest = sourceCRSExtent->intersection( + NN_NO_CHECK(targetCRSExtent)); + } + } else if (sourceAndTargetCRSExtentUse == + CoordinateOperationContext::SourceTargetCRSExtentUse:: + SMALLEST) { + if (sourceCRSExtent && targetCRSExtent) { + if (getPseudoArea(sourceCRSExtent) < + getPseudoArea(targetCRSExtent)) { + areaOfInterest = sourceCRSExtent; + } else { + areaOfInterest = targetCRSExtent; + } + } else if (sourceCRSExtent) { + areaOfInterest = sourceCRSExtent; + } else { + areaOfInterest = targetCRSExtent; + } + } + } + } + + // --------------------------------------------------------------------------- + + void filterOut(bool forceStrictContainmentTest) { + + // Filter out operations that do not match the expected accuracy + // and area of use. + const auto spatialCriterion = + forceStrictContainmentTest + ? CoordinateOperationContext::SpatialCriterion:: + STRICT_CONTAINMENT + : context->getSpatialCriterion(); + for (const auto &op : sourceList) { + if (desiredAccuracy != 0) { + const double accuracy = getAccuracy(op); + if (accuracy < 0 || accuracy > desiredAccuracy) { + continue; + } + } + if (areaOfInterest) { + bool emptyIntersection = false; + auto extent = getExtent(op, true, emptyIntersection); + if (!extent) + continue; + bool extentContains = + extent->contains(NN_NO_CHECK(areaOfInterest)); + if (extentContains) { + hasOpThatContainsAreaOfInterest = true; + } + if (spatialCriterion == + CoordinateOperationContext::SpatialCriterion:: + STRICT_CONTAINMENT && + !extentContains) { + continue; + } + if (spatialCriterion == + CoordinateOperationContext::SpatialCriterion:: + PARTIAL_INTERSECTION && + !extent->intersects(NN_NO_CHECK(areaOfInterest))) { + continue; + } + } else if (sourceAndTargetCRSExtentUse == + CoordinateOperationContext::SourceTargetCRSExtentUse:: + BOTH) { + bool emptyIntersection = false; + auto extent = getExtent(op, true, emptyIntersection); + if (!extent) + continue; + bool extentContainsSource = + !sourceCRSExtent || + extent->contains(NN_NO_CHECK(sourceCRSExtent)); + bool extentContainsTarget = + !targetCRSExtent || + extent->contains(NN_NO_CHECK(targetCRSExtent)); + if (extentContainsSource && extentContainsTarget) { + hasOpThatContainsAreaOfInterest = true; + } + if (spatialCriterion == + CoordinateOperationContext::SpatialCriterion:: + STRICT_CONTAINMENT) { + if (!extentContainsSource || !extentContainsTarget) { + continue; + } + } else if (spatialCriterion == + CoordinateOperationContext::SpatialCriterion:: + PARTIAL_INTERSECTION) { + bool extentIntersectsSource = + !sourceCRSExtent || + extent->intersects(NN_NO_CHECK(sourceCRSExtent)); + bool extentIntersectsTarget = + targetCRSExtent && + extent->intersects(NN_NO_CHECK(targetCRSExtent)); + if (!extentIntersectsSource || !extentIntersectsTarget) { + continue; + } + } + } + res.emplace_back(op); + } + } + + // ---------------------------------------------------------------------- + + void sort() { + + // Precompute a number of parameters for each operation that will be + // useful for the sorting. + std::map<CoordinateOperation *, PrecomputedOpCharacteristics> map; + for (const auto &op : res) { + bool dummy = false; + auto extentOp = getExtent(op, true, dummy); + double area = 0.0; + if (extentOp) { + if (areaOfInterest) { + area = getPseudoArea( + extentOp->intersection(NN_NO_CHECK(areaOfInterest))); + } else if (sourceCRSExtent && targetCRSExtent) { + auto x = + extentOp->intersection(NN_NO_CHECK(sourceCRSExtent)); + auto y = + extentOp->intersection(NN_NO_CHECK(targetCRSExtent)); + area = getPseudoArea(x) + getPseudoArea(y) - + ((x && y) + ? getPseudoArea(x->intersection(NN_NO_CHECK(y))) + : 0.0); + } else if (sourceCRSExtent) { + area = getPseudoArea( + extentOp->intersection(NN_NO_CHECK(sourceCRSExtent))); + } else if (targetCRSExtent) { + area = getPseudoArea( + extentOp->intersection(NN_NO_CHECK(targetCRSExtent))); + } else { + area = getPseudoArea(extentOp); + } + } + + bool hasGrids = false; + bool gridsAvailable = true; + bool gridsKnown = true; + if (context->getAuthorityFactory() && + context->getGridAvailabilityUse() == + CoordinateOperationContext::GridAvailabilityUse:: + USE_FOR_SORTING) { + const auto gridsNeeded = op->gridsNeeded( + context->getAuthorityFactory()->databaseContext()); + for (const auto &gridDesc : gridsNeeded) { + hasGrids = true; + if (!gridDesc.available) { + gridsAvailable = false; + } + if (gridDesc.packageName.empty()) { + gridsKnown = false; + } + } + } + + const auto stepCount = getStepCount(op); + + map[op.get()] = PrecomputedOpCharacteristics( + area, getAccuracy(op), hasGrids, gridsAvailable, gridsKnown, + stepCount); + } + + // Sort ! + std::sort(res.begin(), res.end(), SortFunction(map)); + } + + // ---------------------------------------------------------------------- + + void removeSyntheticNullTransforms() { + + // If we have more than one result, and than the last result is the + // default "Null geographic offset" or "Null geocentric translation" + // operations we have synthetized, remove it as + // all previous results are necessarily better + if (hasOpThatContainsAreaOfInterest && res.size() > 1) { + const std::string &name = res.back()->nameStr(); + if (name.find(NULL_GEOGRAPHIC_OFFSET) != std::string::npos || + name.find(NULL_GEOCENTRIC_TRANSLATION) != std::string::npos) { + std::vector<CoordinateOperationNNPtr> resTemp; + for (size_t i = 0; i < res.size() - 1; i++) { + resTemp.emplace_back(res[i]); + } + res = std::move(resTemp); + } + } + } + + // ---------------------------------------------------------------------- + + void removeUninterestingOps() { + + // Eliminate operations that bring nothing, ie for a given area of use, + // do not keep operations that have greater accuracy. Actually we must + // be a bit more subtle than that, and take into account grid + // availability + std::vector<CoordinateOperationNNPtr> resTemp; + metadata::ExtentPtr lastExtent; + double lastAccuracy = -1; + bool lastHasGrids = false; + bool lastGridsAvailable = true; + std::set<std::set<std::string>> setOfSetOfGrids; + size_t lastStepCount = 0; + CoordinateOperationPtr lastOp; + + bool first = true; + for (const auto &op : res) { + const auto curAccuracy = getAccuracy(op); + bool dummy = false; + const auto curExtent = getExtent(op, true, dummy); + bool curHasGrids = false; + bool curGridsAvailable = true; + std::set<std::string> curSetOfGrids; + + const auto curStepCount = getStepCount(op); + + if (context->getAuthorityFactory()) { + const auto gridsNeeded = op->gridsNeeded( + context->getAuthorityFactory()->databaseContext()); + for (const auto &gridDesc : gridsNeeded) { + curHasGrids = true; + curSetOfGrids.insert(gridDesc.shortName); + if (!gridDesc.available) { + curGridsAvailable = false; + } + } + } + + if (first) { + resTemp.emplace_back(op); + + lastHasGrids = curHasGrids; + lastGridsAvailable = curGridsAvailable; + first = false; + } else { + if (lastOp->_isEquivalentTo(op.get())) { + continue; + } + const bool sameExtent = + ((!curExtent && !lastExtent) || + (curExtent && lastExtent && + curExtent->contains(NN_NO_CHECK(lastExtent)) && + lastExtent->contains(NN_NO_CHECK(curExtent)))); + if (((curAccuracy > lastAccuracy && lastAccuracy >= 0) || + (curAccuracy < 0 && lastAccuracy >= 0)) && + sameExtent) { + // If that set of grids has always been used for that + // extent, + // no need to add them again + if (setOfSetOfGrids.find(curSetOfGrids) != + setOfSetOfGrids.end()) { + continue; + } + // If we have already found a operation without grids for + // that extent, no need to add any lower accuracy operation + if (!lastHasGrids) { + continue; + } + // If we had only operations involving grids, but one + // past operation had available grids, no need to add + // the new one. + if (curHasGrids && curGridsAvailable && + lastGridsAvailable) { + continue; + } + } else if (curAccuracy == lastAccuracy && sameExtent) { + if (curStepCount > lastStepCount) { + continue; + } + } + + resTemp.emplace_back(op); + + if (sameExtent) { + if (!curHasGrids) { + lastHasGrids = false; + } + if (curGridsAvailable) { + lastGridsAvailable = true; + } + } else { + setOfSetOfGrids.clear(); + + lastHasGrids = curHasGrids; + lastGridsAvailable = curGridsAvailable; + } + } + + lastOp = op.as_nullable(); + lastStepCount = curStepCount; + lastExtent = curExtent; + lastAccuracy = curAccuracy; + if (!curSetOfGrids.empty()) { + setOfSetOfGrids.insert(curSetOfGrids); + } + } + res = std::move(resTemp); + } + + // ---------------------------------------------------------------------- + + // cppcheck-suppress functionStatic + void removeDuplicateOps() { + + // When going from EPSG:4807 (NTF Paris) to EPSG:4171 (RGC93), we get + // EPSG:7811, NTF (Paris) to RGF93 (2), 1 m + // and unknown id, NTF (Paris) to NTF (1) + Inverse of RGF93 to NTF (2), + // 1 m + // both have same PROJ string and extent + // Do not keep the later (that has more steps) as it adds no value. + + std::set<std::string> setPROJPlusExtent; + std::vector<CoordinateOperationNNPtr> resTemp; + for (const auto &op : res) { + auto formatter = io::PROJStringFormatter::create(); + try { + std::string key(op->exportToPROJString(formatter.get())); + bool dummy = false; + auto extentOp = getExtent(op, true, dummy); + if (extentOp) { + const auto &geogElts = extentOp->geographicElements(); + if (geogElts.size() == 1) { + auto bbox = dynamic_cast< + const metadata::GeographicBoundingBox *>( + geogElts[0].get()); + if (bbox) { + double w = bbox->westBoundLongitude(); + double s = bbox->southBoundLatitude(); + double e = bbox->eastBoundLongitude(); + double n = bbox->northBoundLatitude(); + key += "-"; + key += toString(w); + key += "-"; + key += toString(s); + key += "-"; + key += toString(e); + key += "-"; + key += toString(n); + } + } + } + + if (setPROJPlusExtent.find(key) == setPROJPlusExtent.end()) { + resTemp.emplace_back(op); + setPROJPlusExtent.insert(key); + } + } catch (const std::exception &) { + resTemp.emplace_back(op); + } + } + res = std::move(resTemp); + } +}; + +// --------------------------------------------------------------------------- + +/** \brief Filter operations and sort them given context. + * + * If a desired accuracy is specified, only keep operations whose accuracy + * is at least the desired one. + * If an area of interest is specified, only keep operations whose area of + * use include the area of interest. + * Then sort remaining operations by descending area of use, and increasing + * accuracy. + */ +static std::vector<CoordinateOperationNNPtr> +filterAndSort(const std::vector<CoordinateOperationNNPtr> &sourceList, + const CoordinateOperationContextNNPtr &context, + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) { + return FilterAndSort(sourceList, context, sourceCRS, targetCRS, false) + .getRes(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +// Apply the inverse() method on all elements of the input list +static std::vector<CoordinateOperationNNPtr> +applyInverse(const std::vector<CoordinateOperationNNPtr> &list) { + auto res = list; + for (auto &op : res) { + op = op->inverse(); + } + return res; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +// Look in the authority registry for operations from sourceCRS to targetCRS +static std::vector<CoordinateOperationNNPtr> +findOpsInRegistryDirect(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS, + const CoordinateOperationContextNNPtr &context) { + const auto &authFactory = context->getAuthorityFactory(); + assert(authFactory); + for (const auto &idSrc : sourceCRS->identifiers()) { + const auto &srcAuthName = *(idSrc->codeSpace()); + const auto &srcCode = idSrc->code(); + if (!srcAuthName.empty()) { + for (const auto &idTarget : targetCRS->identifiers()) { + const auto &targetAuthName = *(idTarget->codeSpace()); + const auto &targetCode = idTarget->code(); + if (!targetAuthName.empty()) { + auto res = + authFactory->createFromCoordinateReferenceSystemCodes( + srcAuthName, srcCode, targetAuthName, targetCode, + context->getUsePROJAlternativeGridNames(), + context->getGridAvailabilityUse() == + CoordinateOperationContext:: + GridAvailabilityUse:: + DISCARD_OPERATION_IF_MISSING_GRID); + if (!res.empty()) { + return res; + } + } + } + } + } + return std::vector<CoordinateOperationNNPtr>(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// Look in the authority registry for operations from sourceCRS to targetCRS +// using an intermediate pivot +static std::vector<CoordinateOperationNNPtr> findsOpsInRegistryWithIntermediate( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const CoordinateOperationContextNNPtr &context) { + if (!context->getAllowUseIntermediateCRS()) { + return std::vector<CoordinateOperationNNPtr>(); + } + + const auto &authFactory = context->getAuthorityFactory(); + assert(authFactory); + for (const auto &idSrc : sourceCRS->identifiers()) { + const auto &srcAuthName = *(idSrc->codeSpace()); + const auto &srcCode = idSrc->code(); + if (!srcAuthName.empty()) { + for (const auto &idTarget : targetCRS->identifiers()) { + const auto &targetAuthName = *(idTarget->codeSpace()); + const auto &targetCode = idTarget->code(); + if (!targetAuthName.empty()) { + auto res = authFactory->createFromCRSCodesWithIntermediates( + srcAuthName, srcCode, targetAuthName, targetCode, + context->getUsePROJAlternativeGridNames(), + context->getGridAvailabilityUse() == + CoordinateOperationContext::GridAvailabilityUse:: + DISCARD_OPERATION_IF_MISSING_GRID, + context->getIntermediateCRS()); + if (!res.empty()) { + return res; + } + } + } + } + } + return std::vector<CoordinateOperationNNPtr>(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static TransformationNNPtr +createNullGeographicOffset(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS) { + std::string name(NULL_GEOGRAPHIC_OFFSET); + name += " from "; + name += sourceCRS->nameStr(); + name += " to "; + name += targetCRS->nameStr(); + + const auto &sourceCRSExtent = getExtent(sourceCRS); + const auto &targetCRSExtent = getExtent(targetCRS); + const bool sameExtent = + sourceCRSExtent && targetCRSExtent && + sourceCRSExtent->_isEquivalentTo( + targetCRSExtent.get(), util::IComparable::Criterion::EQUIVALENT); + + util::PropertyMap map; + map.set(common::IdentifiedObject::NAME_KEY, name) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + sameExtent ? NN_NO_CHECK(sourceCRSExtent) + : metadata::Extent::WORLD); + const common::Angle angle0(0); + if (dynamic_cast<const crs::SingleCRS *>(sourceCRS.get()) + ->coordinateSystem() + ->axisList() + .size() == 3 || + dynamic_cast<const crs::SingleCRS *>(targetCRS.get()) + ->coordinateSystem() + ->axisList() + .size() == 3) { + return Transformation::createGeographic3DOffsets( + map, sourceCRS, targetCRS, angle0, angle0, common::Length(0), {}); + } else { + return Transformation::createGeographic2DOffsets( + map, sourceCRS, targetCRS, angle0, angle0, {}); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +struct MyPROJStringExportableGeodToGeod final + : public io::IPROJStringExportable { + crs::GeodeticCRSPtr geodSrc{}; + crs::GeodeticCRSPtr geodDst{}; + + MyPROJStringExportableGeodToGeod(const crs::GeodeticCRSPtr &geodSrcIn, + const crs::GeodeticCRSPtr &geodDstIn) + : geodSrc(geodSrcIn), geodDst(geodDstIn) {} + + ~MyPROJStringExportableGeodToGeod() override; + + void + // cppcheck-suppress functionStatic + _exportToPROJString(io::PROJStringFormatter *formatter) const override { + + formatter->startInversion(); + geodSrc->_exportToPROJString(formatter); + formatter->stopInversion(); + geodDst->_exportToPROJString(formatter); + } +}; + +MyPROJStringExportableGeodToGeod::~MyPROJStringExportableGeodToGeod() = default; + +// --------------------------------------------------------------------------- + +struct MyPROJStringExportableHorizVertical final + : public io::IPROJStringExportable { + CoordinateOperationPtr horizTransform{}; + CoordinateOperationPtr verticalTransform{}; + crs::GeographicCRSPtr geogDst{}; + + MyPROJStringExportableHorizVertical( + const CoordinateOperationPtr &horizTransformIn, + const CoordinateOperationPtr &verticalTransformIn, + const crs::GeographicCRSPtr &geogDstIn) + : horizTransform(horizTransformIn), + verticalTransform(verticalTransformIn), geogDst(geogDstIn) {} + + ~MyPROJStringExportableHorizVertical() override; + + void + // cppcheck-suppress functionStatic + _exportToPROJString(io::PROJStringFormatter *formatter) const override { + + formatter->setOmitZUnitConversion(true); + horizTransform->_exportToPROJString(formatter); + + formatter->startInversion(); + geogDst->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + formatter->setOmitZUnitConversion(false); + + verticalTransform->_exportToPROJString(formatter); + + formatter->setOmitZUnitConversion(true); + geogDst->addAngularUnitConvertAndAxisSwap(formatter); + formatter->setOmitZUnitConversion(false); + } +}; + +MyPROJStringExportableHorizVertical::~MyPROJStringExportableHorizVertical() = + default; + +// --------------------------------------------------------------------------- + +struct MyPROJStringExportableHorizVerticalHorizPROJBased final + : public io::IPROJStringExportable { + CoordinateOperationPtr opSrcCRSToGeogCRS{}; + CoordinateOperationPtr verticalTransform{}; + CoordinateOperationPtr opGeogCRStoDstCRS{}; + crs::GeographicCRSPtr interpolationGeogCRS{}; + + MyPROJStringExportableHorizVerticalHorizPROJBased( + const CoordinateOperationPtr &opSrcCRSToGeogCRSIn, + const CoordinateOperationPtr &verticalTransformIn, + const CoordinateOperationPtr &opGeogCRStoDstCRSIn, + const crs::GeographicCRSPtr &interpolationGeogCRSIn) + : opSrcCRSToGeogCRS(opSrcCRSToGeogCRSIn), + verticalTransform(verticalTransformIn), + opGeogCRStoDstCRS(opGeogCRStoDstCRSIn), + interpolationGeogCRS(interpolationGeogCRSIn) {} + + ~MyPROJStringExportableHorizVerticalHorizPROJBased() override; + + void + // cppcheck-suppress functionStatic + _exportToPROJString(io::PROJStringFormatter *formatter) const override { + + formatter->setOmitZUnitConversion(true); + + opSrcCRSToGeogCRS->_exportToPROJString(formatter); + + formatter->startInversion(); + interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter); + formatter->stopInversion(); + + formatter->setOmitZUnitConversion(false); + + verticalTransform->_exportToPROJString(formatter); + + formatter->setOmitZUnitConversion(true); + + interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter); + + opGeogCRStoDstCRS->_exportToPROJString(formatter); + + formatter->setOmitZUnitConversion(false); + } +}; + +MyPROJStringExportableHorizVerticalHorizPROJBased:: + ~MyPROJStringExportableHorizVerticalHorizPROJBased() = default; +} +NS_PROJ_END + +#if 0 +namespace dropbox{ namespace oxygen { +template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableGeodToGeod>>::~nn() = default; +template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableHorizVertical>>::~nn() = default; +template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableHorizVerticalHorizPROJBased>>::~nn() = default; +}} +#endif + +NS_PROJ_START +namespace operation { + +// --------------------------------------------------------------------------- + +static std::string buildTransfName(const std::string &srcName, + const std::string &targetName) { + std::string name("Transformation from "); + name += srcName; + name += " to "; + name += targetName; + return name; +} + +// --------------------------------------------------------------------------- + +static CoordinateOperationNNPtr +createGeodToGeodPROJBased(const crs::CRSNNPtr &geodSrc, + const crs::CRSNNPtr &geodDst) { + + auto exportable = util::nn_make_shared<MyPROJStringExportableGeodToGeod>( + util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(geodSrc), + util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(geodDst)); + + auto properties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + buildTransfName(geodSrc->nameStr(), geodDst->nameStr())); + return createPROJBased(properties, exportable, geodSrc, geodDst); +} + +// --------------------------------------------------------------------------- + +static CoordinateOperationNNPtr createHorizVerticalPROJBased( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const operation::CoordinateOperationNNPtr &horizTransform, + const operation::CoordinateOperationNNPtr &verticalTransform) { + + auto geogDst = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(targetCRS); + assert(geogDst); + + auto exportable = util::nn_make_shared<MyPROJStringExportableHorizVertical>( + horizTransform, verticalTransform, geogDst); + + bool dummy = false; + auto ops = std::vector<CoordinateOperationNNPtr>{horizTransform, + verticalTransform}; + auto extent = getExtent(ops, true, dummy); + auto properties = util::PropertyMap(); + properties.set(common::IdentifiedObject::NAME_KEY, + computeConcatenatedName(ops)); + + if (extent) { + properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + NN_NO_CHECK(extent)); + } + + std::vector<metadata::PositionalAccuracyNNPtr> accuracies; + const double accuracy = getAccuracy(ops); + if (accuracy >= 0.0) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create(toString(accuracy))); + } + + return createPROJBased(properties, exportable, sourceCRS, targetCRS, + accuracies); +} + +// --------------------------------------------------------------------------- + +static CoordinateOperationNNPtr createHorizVerticalHorizPROJBased( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const operation::CoordinateOperationNNPtr &opSrcCRSToGeogCRS, + const operation::CoordinateOperationNNPtr &verticalTransform, + const operation::CoordinateOperationNNPtr &opGeogCRStoDstCRS, + const crs::GeographicCRSPtr &interpolationGeogCRS) { + + auto exportable = + util::nn_make_shared<MyPROJStringExportableHorizVerticalHorizPROJBased>( + opSrcCRSToGeogCRS, verticalTransform, opGeogCRStoDstCRS, + interpolationGeogCRS); + + bool dummy = false; + auto ops = std::vector<CoordinateOperationNNPtr>{ + opSrcCRSToGeogCRS, verticalTransform, opGeogCRStoDstCRS}; + auto extent = getExtent(ops, true, dummy); + auto properties = util::PropertyMap(); + properties.set(common::IdentifiedObject::NAME_KEY, + computeConcatenatedName(ops)); + + if (extent) { + properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + NN_NO_CHECK(extent)); + } + + std::vector<metadata::PositionalAccuracyNNPtr> accuracies; + const double accuracy = getAccuracy(ops); + if (accuracy >= 0.0) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create(toString(accuracy))); + } + + return createPROJBased(properties, exportable, sourceCRS, targetCRS, + accuracies); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +ConversionNNPtr CoordinateOperationFactory::Private::createGeographicGeocentric( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) { + auto properties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + buildOpName("Conversion", sourceCRS, targetCRS)); + auto conv = Conversion::createGeographicGeocentric(properties); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + return conv; +} + +// --------------------------------------------------------------------------- + +std::vector<CoordinateOperationNNPtr> +CoordinateOperationFactory::Private::createOperationsGeogToGeog( + std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS, const crs::GeographicCRS *geogSrc, + const crs::GeographicCRS *geogDst) { + + assert(sourceCRS.get() == geogSrc); + assert(targetCRS.get() == geogDst); + const bool allowEmptyIntersection = true; + + const auto &src_pm = geogSrc->primeMeridian()->longitude(); + const auto &dst_pm = geogDst->primeMeridian()->longitude(); + auto offset_pm = + (src_pm.unit() == dst_pm.unit()) + ? common::Angle(src_pm.value() - dst_pm.value(), src_pm.unit()) + : common::Angle( + src_pm.convertToUnit(common::UnitOfMeasure::DEGREE) - + dst_pm.convertToUnit(common::UnitOfMeasure::DEGREE), + common::UnitOfMeasure::DEGREE); + + double vconvSrc = 1.0; + const auto &srcCS = geogSrc->coordinateSystem(); + const auto &srcAxisList = srcCS->axisList(); + if (srcAxisList.size() == 3) { + vconvSrc = srcAxisList[2]->unit().conversionToSI(); + } + double vconvDst = 1.0; + const auto &dstCS = geogDst->coordinateSystem(); + const auto &dstAxisList = dstCS->axisList(); + if (dstAxisList.size() == 3) { + vconvDst = dstAxisList[2]->unit().conversionToSI(); + } + + std::string name(buildTransfName(geogSrc->nameStr(), geogDst->nameStr())); + + // Do they differ by vertical units ? + if (vconvSrc != vconvDst && + geogSrc->ellipsoid()->_isEquivalentTo( + geogDst->ellipsoid().get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (offset_pm.value() == 0) { + // If only by vertical units, use a Change of Vertical + // Unit + // transformation + const double factor = vconvSrc / vconvDst; + auto conv = Conversion::createChangeVerticalUnit( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + name), + common::Scale(factor)); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + res.push_back(conv); + return res; + } else { + res.emplace_back(createGeodToGeodPROJBased(sourceCRS, targetCRS)); + return res; + } + } + + // Do the CRS differ only by their axis order ? + if (geogSrc->datum() != nullptr && geogDst->datum() != nullptr && + geogSrc->datum()->_isEquivalentTo( + geogDst->datum().get(), util::IComparable::Criterion::EQUIVALENT) && + !srcCS->_isEquivalentTo(dstCS.get(), + util::IComparable::Criterion::EQUIVALENT)) { + auto srcOrder = srcCS->axisOrder(); + auto dstOrder = dstCS->axisOrder(); + if ((srcOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST && + dstOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH) || + (srcOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH && + dstOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST)) { + auto conv = Conversion::createAxisOrderReversal(false); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + res.emplace_back(conv); + return res; + } + if ((srcOrder == + cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST_HEIGHT_UP && + dstOrder == + cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH_HEIGHT_UP) || + (srcOrder == + cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH_HEIGHT_UP && + dstOrder == + cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST_HEIGHT_UP)) { + auto conv = Conversion::createAxisOrderReversal(true); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + res.emplace_back(conv); + return res; + } + } + + std::vector<CoordinateOperationNNPtr> steps; + // If both are geographic and only differ by their prime + // meridian, + // apply a longitude rotation transformation. + if (geogSrc->ellipsoid()->_isEquivalentTo( + geogDst->ellipsoid().get(), + util::IComparable::Criterion::EQUIVALENT) && + src_pm.getSIValue() != dst_pm.getSIValue()) { + + steps.emplace_back(Transformation::createLongitudeRotation( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, name) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + metadata::Extent::WORLD), + sourceCRS, targetCRS, offset_pm)); + // If only the target has a non-zero prime meridian, chain a + // null geographic offset and then the longitude rotation + } else if (src_pm.getSIValue() == 0 && dst_pm.getSIValue() != 0) { + auto datum = datum::GeodeticReferenceFrame::create( + util::PropertyMap(), geogDst->ellipsoid(), + util::optional<std::string>(), geogSrc->primeMeridian()); + std::string interm_crs_name(geogDst->nameStr()); + interm_crs_name += " altered to use prime meridian of "; + interm_crs_name += geogSrc->nameStr(); + auto interm_crs = + util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, interm_crs_name) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + metadata::Extent::WORLD), + datum, dstCS)); + + steps.emplace_back(createNullGeographicOffset(sourceCRS, interm_crs)); + + steps.emplace_back(Transformation::createLongitudeRotation( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, + buildTransfName(geogSrc->nameStr(), interm_crs->nameStr())) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + metadata::Extent::WORLD), + interm_crs, targetCRS, offset_pm)); + + } else { + // If the prime meridians are different, chain a longitude + // rotation and the null geographic offset. + if (src_pm.getSIValue() != dst_pm.getSIValue()) { + auto datum = datum::GeodeticReferenceFrame::create( + util::PropertyMap(), geogSrc->ellipsoid(), + util::optional<std::string>(), geogDst->primeMeridian()); + std::string interm_crs_name(geogSrc->nameStr()); + interm_crs_name += " altered to use prime meridian of "; + interm_crs_name += geogDst->nameStr(); + auto interm_crs = util::nn_static_pointer_cast<crs::CRS>( + crs::GeographicCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + interm_crs_name), + datum, srcCS)); + + steps.emplace_back(Transformation::createLongitudeRotation( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, + buildTransfName(geogSrc->nameStr(), + interm_crs->nameStr())) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + metadata::Extent::WORLD), + sourceCRS, interm_crs, offset_pm)); + steps.emplace_back( + createNullGeographicOffset(interm_crs, targetCRS)); + } else { + steps.emplace_back( + createNullGeographicOffset(sourceCRS, targetCRS)); + } + } + + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + steps, !allowEmptyIntersection)); + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static bool hasIdentifiers(const CoordinateOperationNNPtr &op) { + if (!op->identifiers().empty()) { + return true; + } + auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get()); + if (concatenated) { + for (const auto &subOp : concatenated->operations()) { + if (hasIdentifiers(subOp)) { + return true; + } + } + } + return false; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static std::vector<crs::CRSNNPtr> +findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory, + const datum::GeodeticReferenceFramePtr &datum) { + std::vector<crs::CRSNNPtr> candidates; + for (const auto &id : datum->identifiers()) { + const auto &authName = *(id->codeSpace()); + const auto &code = id->code(); + if (!authName.empty()) { + auto l_candidates = authFactory->createGeodeticCRSFromDatum( + authName, code, std::string()); + for (const auto &candidate : l_candidates) { + candidates.emplace_back(candidate); + } + } + } + return candidates; +} + +// --------------------------------------------------------------------------- + +static bool isNullTransformation(const std::string &name) { + + return starts_with(name, NULL_GEOCENTRIC_TRANSLATION) || + starts_with(name, NULL_GEOGRAPHIC_OFFSET); +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private::createOperationsWithDatumPivot( + std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS, const crs::GeodeticCRS *geodSrc, + const crs::GeodeticCRS *geodDst, Private::Context &context) { + + const bool allowEmptyIntersection = true; + + struct CreateOperationsWithDatumPivotAntiRecursion { + Context &context; + + explicit CreateOperationsWithDatumPivotAntiRecursion(Context &contextIn) + : context(contextIn) { + assert(!context.inCreateOperationsWithDatumPivotAntiRecursion); + context.inCreateOperationsWithDatumPivotAntiRecursion = true; + } + + ~CreateOperationsWithDatumPivotAntiRecursion() { + context.inCreateOperationsWithDatumPivotAntiRecursion = false; + } + }; + CreateOperationsWithDatumPivotAntiRecursion guard(context); + + const auto &authFactory = context.context->getAuthorityFactory(); + const auto candidatesSrcGeod( + findCandidateGeodCRSForDatum(authFactory, geodSrc->datum())); + const auto candidatesDstGeod( + findCandidateGeodCRSForDatum(authFactory, geodDst->datum())); + + for (const auto &candidateSrcGeod : candidatesSrcGeod) { + const auto opsFirst = + createOperations(sourceCRS, candidateSrcGeod, context); + assert(!opsFirst.empty()); + const bool isNullFirst = isNullTransformation(opsFirst[0]->nameStr()); + + for (const auto &candidateDstGeod : candidatesDstGeod) { + const auto opsSecond = + createOperations(candidateSrcGeod, candidateDstGeod, context); + const auto opsThird = + createOperations(candidateDstGeod, targetCRS, context); + assert(!opsThird.empty()); + + for (auto &opSecond : opsSecond) { + // Check that it is not a transformation synthetized by + // ourselves + if (!hasIdentifiers(opSecond)) { + continue; + } + // And even if it is a referenced transformation, check that + // it is not a trivial one + auto so = dynamic_cast<const SingleOperation *>(opSecond.get()); + if (so && isAxisOrderReversal(so->method()->getEPSGCode())) { + continue; + } + + std::vector<CoordinateOperationNNPtr> subOps; + if (isNullFirst) { + opSecond->setCRSs(sourceCRS, + NN_CHECK_ASSERT(opSecond->targetCRS()), + nullptr); + } else { + subOps.emplace_back(opsFirst[0]); + } + if (isNullTransformation(opsThird[0]->nameStr())) { + opSecond->setCRSs(NN_CHECK_ASSERT(opSecond->sourceCRS()), + targetCRS, nullptr); + subOps.emplace_back(opSecond); + } else { + subOps.emplace_back(opSecond); + subOps.emplace_back(opsThird[0]); + } + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + subOps, !allowEmptyIntersection)); + } + } + if (!res.empty()) { + return; + } + } +} + +// --------------------------------------------------------------------------- + +static CoordinateOperationNNPtr +createNullGeocentricTranslation(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS) { + std::string name(NULL_GEOCENTRIC_TRANSLATION); + name += " from "; + name += sourceCRS->nameStr(); + name += " to "; + name += targetCRS->nameStr(); + + return util::nn_static_pointer_cast<CoordinateOperation>( + Transformation::createGeocentricTranslations( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, name) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + metadata::Extent::WORLD), + sourceCRS, targetCRS, 0.0, 0.0, 0.0, {})); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +bool CoordinateOperationFactory::Private::hasPerfectAccuracyResult( + const std::vector<CoordinateOperationNNPtr> &res, const Context &context) { + auto resTmp = FilterAndSort(res, context.context, context.sourceCRS, + context.targetCRS, true) + .getRes(); + for (const auto &op : resTmp) { + const double acc = getAccuracy(op); + if (acc == 0.0) { + return true; + } + } + return false; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::vector<CoordinateOperationNNPtr> +CoordinateOperationFactory::Private::createOperations( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context) { + + std::vector<CoordinateOperationNNPtr> res; + const bool allowEmptyIntersection = true; + + auto geodSrc = dynamic_cast<const crs::GeodeticCRS *>(sourceCRS.get()); + auto geodDst = dynamic_cast<const crs::GeodeticCRS *>(targetCRS.get()); + + // First look-up if the registry provide us with operations. + auto derivedSrc = dynamic_cast<const crs::DerivedCRS *>(sourceCRS.get()); + auto derivedDst = dynamic_cast<const crs::DerivedCRS *>(targetCRS.get()); + if (context.context->getAuthorityFactory() && + (derivedSrc == nullptr || + !derivedSrc->baseCRS()->_isEquivalentTo( + targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) && + (derivedDst == nullptr || + !derivedDst->baseCRS()->_isEquivalentTo( + sourceCRS.get(), util::IComparable::Criterion::EQUIVALENT))) { + + bool doFilterAndCheckPerfectOp = true; + res = findOpsInRegistryDirect(sourceCRS, targetCRS, context.context); + if (!sourceCRS->_isEquivalentTo(targetCRS.get())) { + auto resFromInverse = applyInverse( + findOpsInRegistryDirect(targetCRS, sourceCRS, context.context)); + res.insert(res.end(), resFromInverse.begin(), resFromInverse.end()); + + // If we get at least a result with perfect accuracy, do not + // bother generating synthetic transforms. + if (hasPerfectAccuracyResult(res, context)) { + return res; + } + + doFilterAndCheckPerfectOp = false; + + // NAD27 to NAD83 has tens of results already. No need to look + // for a pivot + if (res.size() < 5 || getenv("PROJ_FORCE_SEARCH_PIVOT")) { + auto resWithIntermediate = findsOpsInRegistryWithIntermediate( + sourceCRS, targetCRS, context.context); + res.insert(res.end(), resWithIntermediate.begin(), + resWithIntermediate.end()); + doFilterAndCheckPerfectOp = true; + } + } + + if (res.empty() && + !context.inCreateOperationsWithDatumPivotAntiRecursion && geodSrc && + geodDst) { + // If we still didn't find a transformation, and that the source + // and target are GeodeticCRS, then go through their underlying + // datum to find potential transformations between other GeodeticRSs + // that are made of those datum + // The typical example is if transforming between two GeographicCRS, + // but transformations are only available between their + // corresponding geocentric CRS. + const auto &srcDatum = geodSrc->datum(); + const bool srcHasDatumWithId = + srcDatum && !srcDatum->identifiers().empty(); + const auto &dstDatum = geodDst->datum(); + const bool dstHasDatumWithId = + dstDatum && !dstDatum->identifiers().empty(); + if (srcHasDatumWithId && dstHasDatumWithId) { + createOperationsWithDatumPivot(res, sourceCRS, targetCRS, + geodSrc, geodDst, context); + doFilterAndCheckPerfectOp = !res.empty(); + } + } + + if (doFilterAndCheckPerfectOp) { + // If we get at least a result with perfect accuracy, do not bother + // generating synthetic transforms. + if (hasPerfectAccuracyResult(res, context)) { + return res; + } + } + } + + // Special case if both CRS are geodetic + if (geodSrc && geodDst && !derivedSrc && !derivedDst) { + + if (geodSrc->ellipsoid()->celestialBody() != + geodDst->ellipsoid()->celestialBody()) { + throw util::UnsupportedOperationException( + "Source and target ellipsoid do not belong to the same " + "celestial body"); + } + + auto geogSrc = + dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); + auto geogDst = + dynamic_cast<const crs::GeographicCRS *>(targetCRS.get()); + if (geogSrc && geogDst) { + return createOperationsGeogToGeog(res, sourceCRS, targetCRS, + geogSrc, geogDst); + } + + const bool isSrcGeocentric = geodSrc->isGeocentric(); + const bool isSrcGeographic = geogSrc != nullptr; + const bool isTargetGeocentric = geodDst->isGeocentric(); + const bool isTargetGeographic = geogDst != nullptr; + if (((isSrcGeocentric && isTargetGeographic) || + (isSrcGeographic && isTargetGeocentric)) && + geodSrc->datum() != nullptr && geodDst->datum() != nullptr) { + + // Same datum ? + if (geodSrc->datum()->_isEquivalentTo( + geodDst->datum().get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back( + createGeographicGeocentric(sourceCRS, targetCRS)); + } else if (isSrcGeocentric) { + std::string interm_crs_name(geogDst->nameStr()); + interm_crs_name += " (geocentric)"; + auto interm_crs = util::nn_static_pointer_cast<crs::CRS>( + crs::GeodeticCRS::create( + addDomains(util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + interm_crs_name), + geogDst), + NN_NO_CHECK(geogDst->datum()), + NN_CHECK_ASSERT( + util::nn_dynamic_pointer_cast<cs::CartesianCS>( + geodSrc->coordinateSystem())))); + auto opFirst = + createNullGeocentricTranslation(sourceCRS, interm_crs); + auto opSecond = + createGeographicGeocentric(interm_crs, targetCRS); + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + {opFirst, opSecond}, !allowEmptyIntersection)); + } else { + return applyInverse( + createOperations(targetCRS, sourceCRS, context)); + } + + return res; + } + + if (isSrcGeocentric && isTargetGeocentric) { + res.emplace_back( + createNullGeocentricTranslation(sourceCRS, targetCRS)); + return res; + } + + // Tranformation between two geodetic systems of unknown type + // This should normally not be triggered with "standard" CRS + res.emplace_back(createGeodToGeodPROJBased(sourceCRS, targetCRS)); + return res; + } + + // If the source is a derived CRS, then chain the inverse of its + // deriving conversion, with transforms from its baseCRS to the + // targetCRS + if (derivedSrc) { + auto opFirst = derivedSrc->derivingConversion()->inverse(); + // Small optimization if the targetCRS is the baseCRS of the source + // derivedCRS. + if (derivedSrc->baseCRS()->_isEquivalentTo( + targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(opFirst); + return res; + } + auto opsSecond = + createOperations(derivedSrc->baseCRS(), targetCRS, context); + for (const auto &opSecond : opsSecond) { + try { + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + {opFirst, opSecond}, !allowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + return res; + } + + // reverse of previous case + if (derivedDst) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + // boundCRS to a geogCRS that is the same as the hubCRS + auto boundSrc = dynamic_cast<const crs::BoundCRS *>(sourceCRS.get()); + auto geogDst = dynamic_cast<const crs::GeographicCRS *>(targetCRS.get()); + if (boundSrc && geogDst) { + const auto &hubSrc = boundSrc->hubCRS(); + auto hubSrcGeog = + dynamic_cast<const crs::GeographicCRS *>(hubSrc.get()); + auto geogCRSOfBaseOfBoundSrc = + boundSrc->baseCRS()->extractGeographicCRS(); + if (hubSrcGeog && + (hubSrcGeog->_isEquivalentTo( + geogDst, util::IComparable::Criterion::EQUIVALENT) || + hubSrcGeog->is2DPartOf3D(NN_NO_CHECK(geogDst))) && + geogCRSOfBaseOfBoundSrc) { + if (boundSrc->baseCRS() == geogCRSOfBaseOfBoundSrc) { + // Optimization to avoid creating a useless concatenated + // operation + res.emplace_back(boundSrc->transformation()); + return res; + } + auto opsFirst = + createOperations(boundSrc->baseCRS(), + NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context); + if (!opsFirst.empty()) { + for (const auto &opFirst : opsFirst) { + try { + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + {opFirst, boundSrc->transformation()}, + !allowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + if (!res.empty()) { + return res; + } + } + } + + if (hubSrcGeog && + hubSrcGeog->_isEquivalentTo( + geogDst, util::IComparable::Criterion::EQUIVALENT) && + dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get())) { + res.emplace_back(boundSrc->transformation()); + return res; + } + + return createOperations(boundSrc->baseCRS(), targetCRS, context); + } + + // reverse of previous case + auto boundDst = dynamic_cast<const crs::BoundCRS *>(targetCRS.get()); + auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); + if (geogSrc && boundDst) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + // vertCRS (as boundCRS with transformation to target vertCRS) to + // vertCRS + auto vertDst = dynamic_cast<const crs::VerticalCRS *>(targetCRS.get()); + if (boundSrc && vertDst) { + auto baseSrcVert = + dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get()); + const auto &hubSrc = boundSrc->hubCRS(); + auto hubSrcVert = dynamic_cast<const crs::VerticalCRS *>(hubSrc.get()); + if (baseSrcVert && hubSrcVert && + vertDst->_isEquivalentTo( + hubSrcVert, util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(boundSrc->transformation()); + return res; + } + + return createOperations(boundSrc->baseCRS(), targetCRS, context); + } + + // reverse of previous case + auto vertSrc = dynamic_cast<const crs::VerticalCRS *>(sourceCRS.get()); + if (boundDst && vertSrc) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + if (vertSrc && vertDst) { + const auto &srcDatum = vertSrc->datum(); + const auto &dstDatum = vertDst->datum(); + if (srcDatum && dstDatum && + srcDatum->_isEquivalentTo( + dstDatum.get(), util::IComparable::Criterion::EQUIVALENT)) { + const double convSrc = vertSrc->coordinateSystem() + ->axisList()[0] + ->unit() + .conversionToSI(); + const double convDst = vertDst->coordinateSystem() + ->axisList()[0] + ->unit() + .conversionToSI(); + if (convSrc != convDst) { + const double factor = convSrc / convDst; + auto conv = Conversion::createChangeVerticalUnit( + util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + buildTransfName(sourceCRS->nameStr(), + targetCRS->nameStr())), + common::Scale(factor)); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + res.push_back(conv); + return res; + } + } + } + + // A bit odd case as we are comparing apples to oranges, but in case + // the vertical unit differ, do something useful. + if (vertSrc && geogDst) { + const double convSrc = + vertSrc->coordinateSystem()->axisList()[0]->unit().conversionToSI(); + double convDst = 1.0; + const auto &geogAxis = geogDst->coordinateSystem()->axisList(); + if (geogAxis.size() == 3) { + convDst = geogAxis[2]->unit().conversionToSI(); + } + if (convSrc != convDst) { + const double factor = convSrc / convDst; + auto conv = Conversion::createChangeVerticalUnit( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + buildTransfName(sourceCRS->nameStr(), + targetCRS->nameStr())), + common::Scale(factor)); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + res.push_back(conv); + return res; + } + } + + // reverse of previous case + if (vertDst && geogSrc) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + // boundCRS to boundCRS using the same geographic hubCRS + if (boundSrc && boundDst) { + const auto &hubSrc = boundSrc->hubCRS(); + auto hubSrcGeog = + dynamic_cast<const crs::GeographicCRS *>(hubSrc.get()); + const auto &hubDst = boundDst->hubCRS(); + auto hubDstGeog = + dynamic_cast<const crs::GeographicCRS *>(hubDst.get()); + auto geogCRSOfBaseOfBoundSrc = + boundSrc->baseCRS()->extractGeographicCRS(); + auto geogCRSOfBaseOfBoundDst = + boundDst->baseCRS()->extractGeographicCRS(); + if (hubSrcGeog && hubDstGeog && + hubSrcGeog->_isEquivalentTo( + hubDstGeog, util::IComparable::Criterion::EQUIVALENT) && + geogCRSOfBaseOfBoundSrc && geogCRSOfBaseOfBoundDst) { + const bool firstIsNoOp = geogCRSOfBaseOfBoundSrc->_isEquivalentTo( + boundSrc->baseCRS().get(), + util::IComparable::Criterion::EQUIVALENT); + const bool lastIsNoOp = geogCRSOfBaseOfBoundDst->_isEquivalentTo( + boundDst->baseCRS().get(), + util::IComparable::Criterion::EQUIVALENT); + auto opsFirst = + createOperations(boundSrc->baseCRS(), + NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context); + auto opsLast = + createOperations(NN_NO_CHECK(geogCRSOfBaseOfBoundDst), + boundDst->baseCRS(), context); + if (!opsFirst.empty() && !opsLast.empty()) { + const auto &opSecond = boundSrc->transformation(); + auto opThird = boundDst->transformation()->inverse(); + for (const auto &opFirst : opsFirst) { + for (const auto &opLast : opsLast) { + try { + std::vector<CoordinateOperationNNPtr> ops; + if (!firstIsNoOp) { + ops.push_back(opFirst); + } + ops.push_back(opSecond); + ops.push_back(opThird); + if (!lastIsNoOp) { + ops.push_back(opLast); + } + res.emplace_back( + ConcatenatedOperation::createComputeMetadata( + ops, !allowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } + } + if (!res.empty()) { + return res; + } + } + } + + return createOperations(boundSrc->baseCRS(), boundDst->baseCRS(), + context); + } + + auto compoundSrc = dynamic_cast<crs::CompoundCRS *>(sourceCRS.get()); + if (compoundSrc && geogDst) { + const auto &componentsSrc = compoundSrc->componentReferenceSystems(); + if (!componentsSrc.empty()) { + std::vector<CoordinateOperationNNPtr> horizTransforms; + if (componentsSrc[0]->extractGeographicCRS()) { + horizTransforms = + createOperations(componentsSrc[0], targetCRS, context); + } + std::vector<CoordinateOperationNNPtr> verticalTransforms; + if (componentsSrc.size() >= 2 && + componentsSrc[1]->extractVerticalCRS()) { + verticalTransforms = + createOperations(componentsSrc[1], targetCRS, context); + } + if (!horizTransforms.empty() && !verticalTransforms.empty()) { + for (const auto &horizTransform : horizTransforms) { + for (const auto &verticalTransform : verticalTransforms) { + + auto op = createHorizVerticalPROJBased( + sourceCRS, targetCRS, horizTransform, + verticalTransform); + + res.emplace_back(op); + } + } + return res; + } else { + return horizTransforms; + } + } + } + + // reverse of previous case + auto compoundDst = dynamic_cast<const crs::CompoundCRS *>(targetCRS.get()); + if (geogSrc && compoundDst) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + + if (compoundSrc && compoundDst) { + const auto &componentsSrc = compoundSrc->componentReferenceSystems(); + const auto &componentsDst = compoundDst->componentReferenceSystems(); + if (!componentsSrc.empty() && + componentsSrc.size() == componentsDst.size()) { + if (componentsSrc[0]->extractGeographicCRS() && + componentsDst[0]->extractGeographicCRS()) { + + std::vector<CoordinateOperationNNPtr> verticalTransforms; + if (componentsSrc.size() >= 2 && + componentsSrc[1]->extractVerticalCRS() && + componentsDst[1]->extractVerticalCRS()) { + verticalTransforms = createOperations( + componentsSrc[1], componentsDst[1], context); + } + + for (const auto &verticalTransform : verticalTransforms) { + auto interpolationGeogCRS = + NN_NO_CHECK(componentsSrc[0]->extractGeographicCRS()); + auto transformationVerticalTransform = + dynamic_cast<const Transformation *>( + verticalTransform.get()); + if (transformationVerticalTransform) { + auto interpTransformCRS = + transformationVerticalTransform->interpolationCRS(); + if (interpTransformCRS) { + auto nn_interpTransformCRS = + NN_NO_CHECK(interpTransformCRS); + if (dynamic_cast<const crs::GeographicCRS *>( + nn_interpTransformCRS.get())) { + interpolationGeogCRS = + NN_NO_CHECK(util::nn_dynamic_pointer_cast< + crs::GeographicCRS>( + nn_interpTransformCRS)); + } + } + } + auto opSrcCRSToGeogCRS = createOperations( + componentsSrc[0], interpolationGeogCRS, context); + auto opGeogCRStoDstCRS = createOperations( + interpolationGeogCRS, componentsDst[0], context); + for (const auto &opSrc : opSrcCRSToGeogCRS) { + for (const auto &opDst : opGeogCRStoDstCRS) { + + auto op = createHorizVerticalHorizPROJBased( + sourceCRS, targetCRS, opSrc, verticalTransform, + opDst, interpolationGeogCRS); + res.emplace_back(op); + } + } + } + + if (verticalTransforms.empty()) { + return createOperations(componentsSrc[0], componentsDst[0], + context); + } + } + } + } + + return res; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Find a list of CoordinateOperation from sourceCRS to targetCRS. + * + * The operations are sorted with the most relevant ones first: by + * descending + * area (intersection of the transformation area with the area of interest, + * or intersection of the transformation with the area of use of the CRS), + * and + * by increasing accuracy. Operations with unknown accuracy are sorted last, + * whatever their area. + * + * @param sourceCRS source CRS. + * @param targetCRS source CRS. + * @param context Search context. + * @return a list + */ +std::vector<CoordinateOperationNNPtr> +CoordinateOperationFactory::createOperations( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const CoordinateOperationContextNNPtr &context) const { + + // Look if we are called on CRS that have a link to a 'canonical' + // BoundCRS + // If so, use that one as input + const auto &srcBoundCRS = sourceCRS->canonicalBoundCRS(); + const auto &targetBoundCRS = targetCRS->canonicalBoundCRS(); + auto l_sourceCRS = srcBoundCRS ? NN_NO_CHECK(srcBoundCRS) : sourceCRS; + auto l_targetCRS = targetBoundCRS ? NN_NO_CHECK(targetBoundCRS) : targetCRS; + + Private::Context contextPrivate(sourceCRS, targetCRS, context); + return filterAndSort( + Private::createOperations(l_sourceCRS, l_targetCRS, contextPrivate), + context, l_sourceCRS, l_targetCRS); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CoordinateOperationFactory. + */ +CoordinateOperationFactoryNNPtr CoordinateOperationFactory::create() { + return NN_NO_CHECK( + CoordinateOperationFactory::make_unique<CoordinateOperationFactory>()); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +InverseCoordinateOperation::~InverseCoordinateOperation() = default; + +// --------------------------------------------------------------------------- + +InverseCoordinateOperation::InverseCoordinateOperation( + const CoordinateOperationNNPtr &forwardOperation, bool wktSupportsInversion) + : forwardOperation_(forwardOperation), + wktSupportsInversion_(wktSupportsInversion) {} + +// --------------------------------------------------------------------------- + +void InverseCoordinateOperation::setPropertiesFromForward() { + setProperties( + createPropertiesForInverse(forwardOperation_.get(), false, false)); + setAccuracies(forwardOperation_->coordinateOperationAccuracies()); + if (forwardOperation_->sourceCRS() && forwardOperation_->targetCRS()) { + setCRSs(forwardOperation_.get(), true); + } +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr InverseCoordinateOperation::inverse() const { + return forwardOperation_; +} + +// --------------------------------------------------------------------------- + +void InverseCoordinateOperation::_exportToPROJString( + io::PROJStringFormatter *formatter) const { + formatter->startInversion(); + forwardOperation_->_exportToPROJString(formatter); + formatter->stopInversion(); +} + +// --------------------------------------------------------------------------- + +bool InverseCoordinateOperation::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherICO = dynamic_cast<const InverseCoordinateOperation *>(other); + if (otherICO == nullptr || + !ObjectUsage::_isEquivalentTo(other, criterion)) { + return false; + } + return inverse()->_isEquivalentTo(otherICO->inverse().get(), criterion); +} + +// --------------------------------------------------------------------------- + +PROJBasedOperation::~PROJBasedOperation() = default; + +// --------------------------------------------------------------------------- + +PROJBasedOperation::PROJBasedOperation( + const OperationMethodNNPtr &methodIn, + const std::vector<GeneralParameterValueNNPtr> &values) + : SingleOperation(methodIn) { + setParameterValues(values); +} + +// --------------------------------------------------------------------------- + +static const std::string PROJSTRING_PARAMETER_NAME("PROJ string"); + +PROJBasedOperationNNPtr PROJBasedOperation::create( + const util::PropertyMap &properties, const std::string &PROJString, + const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + auto parameter = OperationParameter::create(util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, PROJSTRING_PARAMETER_NAME)); + auto method = OperationMethod::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + "PROJ-based operation method"), + std::vector<OperationParameterNNPtr>{parameter}); + std::vector<GeneralParameterValueNNPtr> values; + values.push_back(OperationParameterValue::create( + parameter, ParameterValue::create(PROJString))); + auto op = + PROJBasedOperation::nn_make_shared<PROJBasedOperation>(method, values); + op->assignSelf(op); + if (sourceCRS && targetCRS) { + op->setCRSs(NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), nullptr); + } + op->setProperties( + addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation")); + op->setAccuracies(accuracies); + return op; +} + +// --------------------------------------------------------------------------- + +static const std::string + APPROX_PROJSTRING_PARAMETER_NAME("(Approximte) PROJ string"); + +PROJBasedOperationNNPtr PROJBasedOperation::create( + const util::PropertyMap &properties, + const io::IPROJStringExportableNNPtr &projExportable, bool inverse, + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { + auto parameter = OperationParameter::create(util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, APPROX_PROJSTRING_PARAMETER_NAME)); + auto method = OperationMethod::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + "PROJ-based operation method"), + std::vector<OperationParameterNNPtr>{parameter}); + std::vector<GeneralParameterValueNNPtr> values; + + auto formatter = io::PROJStringFormatter::create(); + if (inverse) { + formatter->startInversion(); + } + projExportable->_exportToPROJString(formatter.get()); + if (inverse) { + formatter->stopInversion(); + } + auto projString = formatter->toString(); + + values.push_back(OperationParameterValue::create( + parameter, ParameterValue::create(projString))); + auto op = + PROJBasedOperation::nn_make_shared<PROJBasedOperation>(method, values); + op->assignSelf(op); + op->setCRSs(sourceCRS, targetCRS, nullptr); + op->setProperties( + addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation")); + op->setAccuracies(accuracies); + op->projStringExportable_ = projExportable.as_nullable(); + op->inverse_ = inverse; + return op; +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr PROJBasedOperation::inverse() const { + + if (projStringExportable_) { + return util::nn_static_pointer_cast<CoordinateOperation>( + PROJBasedOperation::create( + createPropertiesForInverse(this, false, false), + NN_NO_CHECK(projStringExportable_), !inverse_, + NN_NO_CHECK(targetCRS()), NN_NO_CHECK(sourceCRS()), + coordinateOperationAccuracies())); + } + + auto formatter = io::PROJStringFormatter::create(); + formatter->startInversion(); + try { + formatter->ingestPROJString( + parameterValue(PROJSTRING_PARAMETER_NAME)->stringValue()); + } catch (const io::ParsingException &e) { + throw util::UnsupportedOperationException( + std::string("PROJBasedOperation::inverse() failed: ") + e.what()); + } + formatter->stopInversion(); + + return util::nn_static_pointer_cast<CoordinateOperation>( + PROJBasedOperation::create( + createPropertiesForInverse(this, false, false), + formatter->toString(), targetCRS(), sourceCRS(), + coordinateOperationAccuracies())); +} + +// --------------------------------------------------------------------------- + +void PROJBasedOperation::_exportToWKT(io::WKTFormatter *formatter) const { + + if (sourceCRS() && targetCRS()) { + exportTransformationToWKT(formatter); + return; + } + + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + throw io::FormattingException( + "PROJBasedOperation can only be exported to WKT2"); + } + + formatter->startNode(io::WKTConstants::CONVERSION, false); + formatter->addQuotedString(nameStr()); + method()->_exportToWKT(formatter); + + for (const auto ¶mValue : parameterValues()) { + paramValue->_exportToWKT(formatter); + } + formatter->endNode(); +} + +// --------------------------------------------------------------------------- + +void PROJBasedOperation::_exportToPROJString( + io::PROJStringFormatter *formatter) const { + if (projStringExportable_) { + if (inverse_) { + formatter->startInversion(); + } + projStringExportable_->_exportToPROJString(formatter); + if (inverse_) { + formatter->stopInversion(); + } + return; + } + + try { + formatter->ingestPROJString( + parameterValue(PROJSTRING_PARAMETER_NAME)->stringValue()); + } catch (const io::ParsingException &e) { + throw io::FormattingException( + std::string("PROJBasedOperation::exportToPROJString() failed: ") + + e.what()); + } +} + +// --------------------------------------------------------------------------- + +std::set<GridDescription> PROJBasedOperation::gridsNeeded( + const io::DatabaseContextPtr &databaseContext) const { + std::set<GridDescription> res; + + try { + auto formatterOut = io::PROJStringFormatter::create(); + auto formatter = io::PROJStringFormatter::create(); + formatter->ingestPROJString(exportToPROJString(formatterOut.get())); + const auto usedGridNames = formatter->getUsedGridNames(); + for (const auto &shortName : usedGridNames) { + GridDescription desc; + desc.shortName = shortName; + if (databaseContext) { + databaseContext->lookForGridInfo( + desc.shortName, desc.fullName, desc.packageName, desc.url, + desc.directDownload, desc.openLicense, desc.available); + } + res.insert(desc); + } + } catch (const io::ParsingException &) { + } + + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace operation +NS_PROJ_END diff --git a/src/coordinatesystem.cpp b/src/coordinatesystem.cpp new file mode 100644 index 00000000..1c84cf2c --- /dev/null +++ b/src/coordinatesystem.cpp @@ -0,0 +1,1107 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/coordinatesystem.hpp" +#include "proj/common.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/coordinatesystem_internal.hpp" +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +using namespace NS_PROJ::internal; + +#if 0 +namespace dropbox{ namespace oxygen { +template<> nn<NS_PROJ::cs::MeridianPtr>::~nn() = default; +template<> nn<NS_PROJ::cs::CoordinateSystemAxisPtr>::~nn() = default; +template<> nn<NS_PROJ::cs::CoordinateSystemPtr>::~nn() = default; +template<> nn<NS_PROJ::cs::SphericalCSPtr>::~nn() = default; +template<> nn<NS_PROJ::cs::EllipsoidalCSPtr>::~nn() = default; +template<> nn<NS_PROJ::cs::CartesianCSPtr>::~nn() = default; +template<> nn<NS_PROJ::cs::TemporalCSPtr>::~nn() = default; +template<> nn<NS_PROJ::cs::TemporalCountCSPtr>::~nn() = default; +template<> nn<NS_PROJ::cs::TemporalMeasureCSPtr>::~nn() = default; +template<> nn<NS_PROJ::cs::DateTimeTemporalCSPtr>::~nn() = default; +template<> nn<NS_PROJ::cs::VerticalCSPtr>::~nn() = default; +template<> nn<NS_PROJ::cs::ParametricCSPtr>::~nn() = default; +template<> nn<NS_PROJ::cs::OrdinalCSPtr>::~nn() = default; +}} +#endif + +NS_PROJ_START +namespace cs { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Meridian::Private { + common::Angle longitude_{}; + + explicit Private(const common::Angle &longitude) : longitude_(longitude) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +Meridian::Meridian(const common::Angle &longitudeIn) + : d(internal::make_unique<Private>(longitudeIn)) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +Meridian::Meridian(const Meridian &other) + : IdentifiedObject(other), d(internal::make_unique<Private>(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Meridian::~Meridian() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the longitude of the meridian that the axis follows from the + * pole. + * + * @return the longitude. + */ +const common::Angle &Meridian::longitude() PROJ_CONST_DEFN { + return d->longitude_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Meridian. + * + * @param longitudeIn longitude of the meridian that the axis follows from the + * pole. + * @return new Meridian. + */ +MeridianNNPtr Meridian::create(const common::Angle &longitudeIn) { + return Meridian::nn_make_shared<Meridian>(longitudeIn); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Meridian::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + formatter->startNode(io::WKTConstants::MERIDIAN, !identifiers().empty()); + formatter->add(longitude().value()); + longitude().unit()._exportToWKT(formatter, io::WKTConstants::ANGLEUNIT); + if (formatter->outputId()) { + formatID(formatter); + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CoordinateSystemAxis::Private { + std::string abbreviation{}; + const AxisDirection *direction = &(AxisDirection::UNSPECIFIED); + common::UnitOfMeasure unit{}; + util::optional<double> minimumValue{}; + util::optional<double> maximumValue{}; + MeridianPtr meridian{}; + // TODO rangeMeaning +}; +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateSystemAxis::CoordinateSystemAxis() + : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +CoordinateSystemAxis::CoordinateSystemAxis(const CoordinateSystemAxis &other) + : IdentifiedObject(other), d(internal::make_unique<Private>(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateSystemAxis::~CoordinateSystemAxis() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the axis abbreviation. + * + * The abbreviation used for this coordinate system axis; this abbreviation + * is also used to identify the coordinates in the coordinate tuple. + * Examples are X and Y. + * + * @return the abbreviation. + */ +const std::string &CoordinateSystemAxis::abbreviation() PROJ_CONST_DEFN { + return d->abbreviation; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the axis direction. + * + * The direction of this coordinate system axis (or in the case of Cartesian + * projected coordinates, the direction of this coordinate system axis locally) + * Examples: north or south, east or west, up or down. Within any set of + * coordinate system axes, only one of each pair of terms can be used. For + * Earth-fixed CRSs, this direction is often approximate and intended to + * provide a human interpretable meaning to the axis. When a geodetic reference + * frame is used, the precise directions of the axes may therefore vary + * slightly from this approximate direction. Note that an EngineeringCRS often + * requires specific descriptions of the directions of its coordinate system + * axes. + * + * @return the direction. + */ +const AxisDirection &CoordinateSystemAxis::direction() PROJ_CONST_DEFN { + return *(d->direction); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the axis unit. + * + * This is the spatial unit or temporal quantity used for this coordinate + * system axis. The value of a coordinate in a coordinate tuple shall be + * recorded using this unit. + * + * @return the axis unit. + */ +const common::UnitOfMeasure &CoordinateSystemAxis::unit() PROJ_CONST_DEFN { + return d->unit; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the minimum value normally allowed for this axis, in the unit + * for the axis. + * + * @return the minimum value, or empty. + */ +const util::optional<double> & +CoordinateSystemAxis::minimumValue() PROJ_CONST_DEFN { + return d->minimumValue; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the maximum value normally allowed for this axis, in the unit + * for the axis. + * + * @return the maximum value, or empty. + */ +const util::optional<double> & +CoordinateSystemAxis::maximumValue() PROJ_CONST_DEFN { + return d->maximumValue; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the meridian that the axis follows from the pole, for a + * coordinate + * reference system centered on a pole. + * + * @return the meridian, or null. + */ +const MeridianPtr &CoordinateSystemAxis::meridian() PROJ_CONST_DEFN { + return d->meridian; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CoordinateSystemAxis. + * + * @param properties See \ref general_properties. The name should generally be + * defined. + * @param abbreviationIn Axis abbreviation (might be empty) + * @param directionIn Axis direction + * @param unitIn Axis unit + * @param meridianIn The meridian that the axis follows from the pole, for a + * coordinate + * reference system centered on a pole, or nullptr + * @return a new CoordinateSystemAxis. + */ +CoordinateSystemAxisNNPtr CoordinateSystemAxis::create( + const util::PropertyMap &properties, const std::string &abbreviationIn, + const AxisDirection &directionIn, const common::UnitOfMeasure &unitIn, + const MeridianPtr &meridianIn) { + auto csa(CoordinateSystemAxis::nn_make_shared<CoordinateSystemAxis>()); + csa->setProperties(properties); + csa->d->abbreviation = abbreviationIn; + csa->d->direction = &directionIn; + csa->d->unit = unitIn; + csa->d->meridian = meridianIn; + return csa; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void CoordinateSystemAxis::_exportToWKT( + // cppcheck-suppress passedByValue + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + _exportToWKT(formatter, 0, false); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::string CoordinateSystemAxis::normalizeAxisName(const std::string &str) { + if (str.empty()) { + return str; + } + // on import, transform from WKT2 "longitude" to "Longitude", as in the + // EPSG database. + return toupper(str.substr(0, 1)) + str.substr(1); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void CoordinateSystemAxis::_exportToWKT(io::WKTFormatter *formatter, int order, + bool disableAbbrev) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(io::WKTConstants::AXIS, !identifiers().empty()); + std::string axisName = *(name()->description()); + std::string abbrev = abbreviation(); + std::string parenthesedAbbrev = "(" + abbrev + ")"; + std::string dir = direction().toString(); + std::string axisDesignation; + + // It seems that the convention in WKT2 for axis name is first letter in + // lower case. Whereas in WKT1 GDAL, it is in upper case (as in the EPSG + // database) + if (!axisName.empty()) { + if (isWKT2) { + axisDesignation = + tolower(axisName.substr(0, 1)) + axisName.substr(1); + } else { + axisDesignation = axisName; + } + } + + if (!disableAbbrev && isWKT2 && + // For geodetic CS, export the axis name without abbreviation + !(axisName == AxisName::Latitude || axisName == AxisName::Longitude)) { + if (!axisDesignation.empty() && !abbrev.empty()) { + axisDesignation += " "; + } + if (!abbrev.empty()) { + axisDesignation += parenthesedAbbrev; + } + } + if (!isWKT2) { + dir = toupper(dir); + + if (direction() == AxisDirection::GEOCENTRIC_Z) { + dir = AxisDirectionWKT1::NORTH; + } else if (AxisDirectionWKT1::valueOf(dir) == nullptr) { + dir = AxisDirectionWKT1::OTHER; + } + } else if (!abbrev.empty()) { + // For geocentric CS, just put the abbreviation + if (direction() == AxisDirection::GEOCENTRIC_X || + direction() == AxisDirection::GEOCENTRIC_Y || + direction() == AxisDirection::GEOCENTRIC_Z) { + axisDesignation = parenthesedAbbrev; + } + // For cartesian CS with Easting/Northing, export only the abbreviation + else if ((order == 1 && axisName == AxisName::Easting && + abbrev == AxisAbbreviation::E) || + (order == 2 && axisName == AxisName::Northing && + abbrev == AxisAbbreviation::N)) { + axisDesignation = parenthesedAbbrev; + } + } + formatter->addQuotedString(axisDesignation); + formatter->add(dir); + const auto &l_meridian = meridian(); + if (isWKT2 && l_meridian) { + l_meridian->_exportToWKT(formatter); + } + if (formatter->outputAxisOrder() && order > 0) { + formatter->startNode(io::WKTConstants::ORDER, false); + formatter->add(order); + formatter->endNode(); + } + if (formatter->outputUnit() && + unit().type() != common::UnitOfMeasure::Type::NONE) { + unit()._exportToWKT(formatter); + } + if (formatter->outputId()) { + formatID(formatter); + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool CoordinateSystemAxis::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherCSA = dynamic_cast<const CoordinateSystemAxis *>(other); + if (otherCSA == nullptr) { + return false; + } + // For approximate comparison, only care about axis direction and unit. + if (!(*(d->direction) == *(otherCSA->d->direction) && + d->unit._isEquivalentTo(otherCSA->d->unit, criterion))) { + return false; + } + if (criterion == util::IComparable::Criterion::STRICT) { + if (!IdentifiedObject::_isEquivalentTo(other, criterion)) { + return false; + } + if (abbreviation() != otherCSA->abbreviation()) { + return false; + } + // TODO other metadata + } + + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CoordinateSystem::Private { + std::vector<CoordinateSystemAxisNNPtr> axisList{}; + + explicit Private(const std::vector<CoordinateSystemAxisNNPtr> &axisListIn) + : axisList(axisListIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +CoordinateSystem::CoordinateSystem( + const std::vector<CoordinateSystemAxisNNPtr> &axisIn) + : d(internal::make_unique<Private>(axisIn)) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +CoordinateSystem::CoordinateSystem(const CoordinateSystem &other) + : IdentifiedObject(other), d(internal::make_unique<Private>(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateSystem::~CoordinateSystem() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the list of axes of this coordinate system. + * + * @return the axes. + */ +const std::vector<CoordinateSystemAxisNNPtr> & +CoordinateSystem::axisList() PROJ_CONST_DEFN { + return d->axisList; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void CoordinateSystem::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + if (!formatter->outputAxis()) { + return; + } + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + + const auto &l_axisList = axisList(); + if (isWKT2) { + formatter->startNode(io::WKTConstants::CS, !identifiers().empty()); + formatter->add(getWKT2Type(formatter->use2018Keywords())); + formatter->add(static_cast<int>(l_axisList.size())); + formatter->endNode(); + formatter->startNode(std::string(), + false); // anonymous indentation level + } + + common::UnitOfMeasure unit = common::UnitOfMeasure::NONE; + bool bAllSameUnit = true; + bool bFirstUnit = true; + for (const auto &axis : l_axisList) { + const auto &l_unit = axis->unit(); + if (bFirstUnit) { + unit = l_unit; + bFirstUnit = false; + } else if (unit != l_unit) { + bAllSameUnit = false; + } + } + + formatter->pushOutputUnit( + isWKT2 && (!bAllSameUnit || !formatter->outputCSUnitOnlyOnceIfSame())); + + int order = 1; + const bool disableAbbrev = + (l_axisList.size() == 3 && + l_axisList[0]->nameStr() == AxisName::Latitude && + l_axisList[1]->nameStr() == AxisName::Longitude && + l_axisList[2]->nameStr() == AxisName::Ellipsoidal_height); + + for (auto &axis : l_axisList) { + int axisOrder = (isWKT2 && l_axisList.size() > 1) ? order : 0; + axis->_exportToWKT(formatter, axisOrder, disableAbbrev); + order++; + } + if (isWKT2 && !l_axisList.empty() && bAllSameUnit && + formatter->outputCSUnitOnlyOnceIfSame()) { + unit._exportToWKT(formatter); + } + + formatter->popOutputUnit(); + + if (isWKT2) { + formatter->endNode(); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool CoordinateSystem::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherCS = dynamic_cast<const CoordinateSystem *>(other); + if (otherCS == nullptr || + !IdentifiedObject::_isEquivalentTo(other, criterion)) { + return false; + } + const auto &list = axisList(); + const auto &otherList = otherCS->axisList(); + if (list.size() != otherList.size()) { + return false; + } + if (getWKT2Type(true) != otherCS->getWKT2Type(true)) { + return false; + } + for (size_t i = 0; i < list.size(); i++) { + if (!list[i]->_isEquivalentTo(otherList[i].get(), criterion)) { + return false; + } + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +SphericalCS::~SphericalCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +SphericalCS::SphericalCS(const std::vector<CoordinateSystemAxisNNPtr> &axisIn) + : CoordinateSystem(axisIn) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +SphericalCS::SphericalCS(const SphericalCS &) = default; +#endif + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a SphericalCS. + * + * @param properties See \ref general_properties. + * @param axis1 The first axis. + * @param axis2 The second axis. + * @param axis3 The third axis. + * @return a new SphericalCS. + */ +SphericalCSNNPtr SphericalCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axis1, + const CoordinateSystemAxisNNPtr &axis2, + const CoordinateSystemAxisNNPtr &axis3) { + std::vector<CoordinateSystemAxisNNPtr> axis{axis1, axis2, axis3}; + auto cs(SphericalCS::nn_make_shared<SphericalCS>(axis)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +EllipsoidalCS::~EllipsoidalCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +EllipsoidalCS::EllipsoidalCS( + const std::vector<CoordinateSystemAxisNNPtr> &axisIn) + : CoordinateSystem(axisIn) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +EllipsoidalCS::EllipsoidalCS(const EllipsoidalCS &) = default; +#endif + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EllipsoidalCS. + * + * @param properties See \ref general_properties. + * @param axis1 The first axis. + * @param axis2 The second axis. + * @return a new EllipsoidalCS. + */ +EllipsoidalCSNNPtr +EllipsoidalCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axis1, + const CoordinateSystemAxisNNPtr &axis2) { + std::vector<CoordinateSystemAxisNNPtr> axis{axis1, axis2}; + auto cs(EllipsoidalCS::nn_make_shared<EllipsoidalCS>(axis)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EllipsoidalCS. + * + * @param properties See \ref general_properties. + * @param axis1 The first axis. + * @param axis2 The second axis. + * @param axis3 The third axis. + * @return a new EllipsoidalCS. + */ +EllipsoidalCSNNPtr +EllipsoidalCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axis1, + const CoordinateSystemAxisNNPtr &axis2, + const CoordinateSystemAxisNNPtr &axis3) { + std::vector<CoordinateSystemAxisNNPtr> axis{axis1, axis2, axis3}; + auto cs(EllipsoidalCS::nn_make_shared<EllipsoidalCS>(axis)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CoordinateSystemAxisNNPtr +CoordinateSystemAxis::createLAT_NORTH(const common::UnitOfMeasure &unit) { + return create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Latitude), + AxisAbbreviation::lat, AxisDirection::NORTH, unit); +} + +CoordinateSystemAxisNNPtr +CoordinateSystemAxis::createLONG_EAST(const common::UnitOfMeasure &unit) { + return create(util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Longitude), + AxisAbbreviation::lon, AxisDirection::EAST, unit); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EllipsoidalCS with a Latitude (first) and Longitude + * (second) axis. + * + * @param unit Angular unit of the axes. + * @return a new EllipsoidalCS. + */ +EllipsoidalCSNNPtr +EllipsoidalCS::createLatitudeLongitude(const common::UnitOfMeasure &unit) { + return EllipsoidalCS::create(util::PropertyMap(), + CoordinateSystemAxis::createLAT_NORTH(unit), + CoordinateSystemAxis::createLONG_EAST(unit)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EllipsoidalCS with a Latitude (first), Longitude + * (second) axis and ellipsoidal height (third) axis. + * + * @param angularUnit Angular unit of the latitude and longitude axes. + * @param linearUnit Linear unit of the ellipsoidal height axis. + * @return a new EllipsoidalCS. + */ +EllipsoidalCSNNPtr EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( + const common::UnitOfMeasure &angularUnit, + const common::UnitOfMeasure &linearUnit) { + return EllipsoidalCS::create( + util::PropertyMap(), CoordinateSystemAxis::createLAT_NORTH(angularUnit), + CoordinateSystemAxis::createLONG_EAST(angularUnit), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Ellipsoidal_height), + AxisAbbreviation::h, AxisDirection::UP, linearUnit)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EllipsoidalCS with a Longitude (first) and Latitude + * (second) axis. + * + * @param unit Angular unit of the axes. + * @return a new EllipsoidalCS. + */ +EllipsoidalCSNNPtr +EllipsoidalCS::createLongitudeLatitude(const common::UnitOfMeasure &unit) { + return EllipsoidalCS::create(util::PropertyMap(), + CoordinateSystemAxis::createLONG_EAST(unit), + CoordinateSystemAxis::createLAT_NORTH(unit)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +/** \brief Return the axis order in an enumerated way. */ +EllipsoidalCS::AxisOrder EllipsoidalCS::axisOrder() const { + const auto &l_axisList = CoordinateSystem::getPrivate()->axisList; + const auto &dir0 = l_axisList[0]->direction(); + const auto &dir1 = l_axisList[1]->direction(); + if (&dir0 == &AxisDirection::NORTH && &dir1 == &AxisDirection::EAST) { + if (l_axisList.size() == 2) { + return AxisOrder::LAT_NORTH_LONG_EAST; + } else if (&l_axisList[2]->direction() == &AxisDirection::UP) { + return AxisOrder::LAT_NORTH_LONG_EAST_HEIGHT_UP; + } + } else if (&dir0 == &AxisDirection::EAST && + &dir1 == &AxisDirection::NORTH) { + if (l_axisList.size() == 2) { + return AxisOrder::LONG_EAST_LAT_NORTH; + } else if (&l_axisList[2]->direction() == &AxisDirection::UP) { + return AxisOrder::LONG_EAST_LAT_NORTH_HEIGHT_UP; + } + } + + return AxisOrder::OTHER; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +VerticalCS::~VerticalCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +VerticalCS::VerticalCS(const CoordinateSystemAxisNNPtr &axisIn) + : CoordinateSystem(std::vector<CoordinateSystemAxisNNPtr>{axisIn}) {} +//! @endcond + +// --------------------------------------------------------------------------- + +#ifdef notdef +VerticalCS::VerticalCS(const VerticalCS &) = default; +#endif + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a VerticalCS. + * + * @param properties See \ref general_properties. + * @param axis The axis. + * @return a new VerticalCS. + */ +VerticalCSNNPtr VerticalCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axis) { + auto cs(VerticalCS::nn_make_shared<VerticalCS>(axis)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a VerticalCS with a Gravity-related height axis + * + * @param unit linear unit. + * @return a new VerticalCS. + */ +VerticalCSNNPtr +VerticalCS::createGravityRelatedHeight(const common::UnitOfMeasure &unit) { + auto cs(VerticalCS::nn_make_shared<VerticalCS>(CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + "Gravity-related height"), + "H", AxisDirection::UP, unit))); + return cs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CartesianCS::~CartesianCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CartesianCS::CartesianCS(const std::vector<CoordinateSystemAxisNNPtr> &axisIn) + : CoordinateSystem(axisIn) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +CartesianCS::CartesianCS(const CartesianCS &) = default; +#endif + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CartesianCS. + * + * @param properties See \ref general_properties. + * @param axis1 The first axis. + * @param axis2 The second axis. + * @return a new CartesianCS. + */ +CartesianCSNNPtr CartesianCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axis1, + const CoordinateSystemAxisNNPtr &axis2) { + std::vector<CoordinateSystemAxisNNPtr> axis{axis1, axis2}; + auto cs(CartesianCS::nn_make_shared<CartesianCS>(axis)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CartesianCS. + * + * @param properties See \ref general_properties. + * @param axis1 The first axis. + * @param axis2 The second axis. + * @param axis3 The third axis. + * @return a new CartesianCS. + */ +CartesianCSNNPtr CartesianCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axis1, + const CoordinateSystemAxisNNPtr &axis2, + const CoordinateSystemAxisNNPtr &axis3) { + std::vector<CoordinateSystemAxisNNPtr> axis{axis1, axis2, axis3}; + auto cs(CartesianCS::nn_make_shared<CartesianCS>(axis)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CartesianCS with a Easting (first) and Northing + * (second) axis. + * + * @param unit Linear unit of the axes. + * @return a new CartesianCS. + */ +CartesianCSNNPtr +CartesianCS::createEastingNorthing(const common::UnitOfMeasure &unit) { + return create(util::PropertyMap(), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Easting), + AxisAbbreviation::E, AxisDirection::EAST, unit), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Northing), + AxisAbbreviation::N, AxisDirection::NORTH, unit)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CartesianCS with the three geocentric axes. + * + * @param unit Liinear unit of the axes. + * @return a new CartesianCS. + */ +CartesianCSNNPtr +CartesianCS::createGeocentric(const common::UnitOfMeasure &unit) { + return create(util::PropertyMap(), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Geocentric_X), + AxisAbbreviation::X, AxisDirection::GEOCENTRIC_X, unit), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Geocentric_Y), + AxisAbbreviation::Y, AxisDirection::GEOCENTRIC_Y, unit), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Geocentric_Z), + AxisAbbreviation::Z, AxisDirection::GEOCENTRIC_Z, unit)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +OrdinalCS::~OrdinalCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +OrdinalCS::OrdinalCS(const std::vector<CoordinateSystemAxisNNPtr> &axisIn) + : CoordinateSystem(axisIn) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +OrdinalCS::OrdinalCS(const OrdinalCS &) = default; +#endif + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a OrdinalCS. + * + * @param properties See \ref general_properties. + * @param axisIn List of axis. + * @return a new OrdinalCS. + */ +OrdinalCSNNPtr +OrdinalCS::create(const util::PropertyMap &properties, + const std::vector<CoordinateSystemAxisNNPtr> &axisIn) { + auto cs(OrdinalCS::nn_make_shared<OrdinalCS>(axisIn)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ParametricCS::~ParametricCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +ParametricCS::ParametricCS(const std::vector<CoordinateSystemAxisNNPtr> &axisIn) + : CoordinateSystem(axisIn) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +ParametricCS::ParametricCS(const ParametricCS &) = default; +#endif + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ParametricCS. + * + * @param properties See \ref general_properties. + * @param axisIn Axis. + * @return a new ParametricCS. + */ +ParametricCSNNPtr +ParametricCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axisIn) { + auto cs(ParametricCS::nn_make_shared<ParametricCS>( + std::vector<CoordinateSystemAxisNNPtr>{axisIn})); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +AxisDirection::AxisDirection(const std::string &nameIn) : CodeList(nameIn) { + assert(registry.find(nameIn) == registry.end()); + registry[nameIn] = this; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const AxisDirection * +AxisDirection::valueOf(const std::string &nameIn) noexcept { + auto iter = registry.find(nameIn); + if (iter == registry.end()) + return nullptr; + return iter->second; +} +//! @endcond + +//! @cond Doxygen_Suppress +// --------------------------------------------------------------------------- + +AxisDirectionWKT1::AxisDirectionWKT1(const std::string &nameIn) + : CodeList(nameIn) { + assert(registry.find(nameIn) == registry.end()); + registry[nameIn] = this; +} + +// --------------------------------------------------------------------------- + +const AxisDirectionWKT1 *AxisDirectionWKT1::valueOf(const std::string &nameIn) { + auto iter = registry.find(nameIn); + if (iter == registry.end()) + return nullptr; + return iter->second; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalCS::~TemporalCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalCS::TemporalCS(const CoordinateSystemAxisNNPtr &axisIn) + : CoordinateSystem(std::vector<CoordinateSystemAxisNNPtr>{axisIn}) {} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DateTimeTemporalCS::~DateTimeTemporalCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DateTimeTemporalCS::DateTimeTemporalCS(const CoordinateSystemAxisNNPtr &axisIn) + : TemporalCS(axisIn) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DateTimeTemporalCS. + * + * @param properties See \ref general_properties. + * @param axisIn The axis. + * @return a new DateTimeTemporalCS. + */ +DateTimeTemporalCSNNPtr +DateTimeTemporalCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axisIn) { + auto cs(DateTimeTemporalCS::nn_make_shared<DateTimeTemporalCS>(axisIn)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +std::string DateTimeTemporalCS::getWKT2Type(bool use2018Keywords) const { + return use2018Keywords ? "TemporalDateTime" : "temporal"; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalCountCS::~TemporalCountCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +TemporalCountCS::TemporalCountCS(const CoordinateSystemAxisNNPtr &axisIn) + : TemporalCS(axisIn) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a TemporalCountCS. + * + * @param properties See \ref general_properties. + * @param axisIn The axis. + * @return a new TemporalCountCS. + */ +TemporalCountCSNNPtr +TemporalCountCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axisIn) { + auto cs(TemporalCountCS::nn_make_shared<TemporalCountCS>(axisIn)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +std::string TemporalCountCS::getWKT2Type(bool use2018Keywords) const { + return use2018Keywords ? "TemporalCount" : "temporal"; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalMeasureCS::~TemporalMeasureCS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +TemporalMeasureCS::TemporalMeasureCS(const CoordinateSystemAxisNNPtr &axisIn) + : TemporalCS(axisIn) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a TemporalMeasureCS. + * + * @param properties See \ref general_properties. + * @param axisIn The axis. + * @return a new TemporalMeasureCS. + */ +TemporalMeasureCSNNPtr +TemporalMeasureCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axisIn) { + auto cs(TemporalMeasureCS::nn_make_shared<TemporalMeasureCS>(axisIn)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + +std::string TemporalMeasureCS::getWKT2Type(bool use2018Keywords) const { + return use2018Keywords ? "TemporalMeasure" : "temporal"; +} + +} // namespace cs +NS_PROJ_END diff --git a/src/crs.cpp b/src/crs.cpp new file mode 100644 index 00000000..dab704b4 --- /dev/null +++ b/src/crs.cpp @@ -0,0 +1,4490 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +//! @cond Doxygen_Suppress +#define DO_NOT_DEFINE_EXTERN_DERIVED_CRS_TEMPLATE +//! @endcond + +#include "proj/crs.hpp" +#include "proj/common.hpp" +#include "proj/coordinateoperation.hpp" +#include "proj/coordinatesystem.hpp" +#include "proj/io.hpp" +#include "proj/util.hpp" + +#include "proj/internal/coordinatesystem_internal.hpp" +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include <cassert> +#include <cstring> +#include <iostream> +#include <memory> +#include <string> +#include <vector> + +using namespace NS_PROJ::internal; + +#if 0 +namespace dropbox{ namespace oxygen { +template<> nn<NS_PROJ::crs::CRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::SingleCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::GeodeticCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::GeographicCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::ProjectedCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::VerticalCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::CompoundCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::TemporalCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::EngineeringCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::ParametricCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::BoundCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedGeodeticCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedGeographicCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedProjectedCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedVerticalCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedTemporalCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedEngineeringCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedParametricCRSPtr>::~nn() = default; +}} +#endif + +NS_PROJ_START + +namespace crs { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CRS::Private { + BoundCRSPtr canonicalBoundCRS_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +CRS::CRS() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +CRS::CRS(const CRS &other) + : ObjectUsage(other), d(internal::make_unique<Private>(*(other.d))) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CRS::~CRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the BoundCRS potentially attached to this CRS. + * + * In the case this method is called on a object returned by + * BoundCRS::baseCRSWithCanonicalBoundCRS(), this method will return this + * BoundCRS + * + * @return a BoundCRSPtr, that might be null. + */ +const BoundCRSPtr &CRS::canonicalBoundCRS() PROJ_CONST_DEFN { + return d->canonicalBoundCRS_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the GeodeticCRS of the CRS. + * + * Returns the GeodeticCRS contained in a CRS. This works currently with + * input parameters of type GeodeticCRS or derived, ProjectedCRS, + * CompoundCRS or BoundCRS. + * + * @return a GeodeticCRSPtr, that might be null. + */ +GeodeticCRSPtr CRS::extractGeodeticCRS() const { + auto raw = extractGeodeticCRSRaw(); + if (raw) { + return std::dynamic_pointer_cast<GeodeticCRS>( + raw->shared_from_this().as_nullable()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const GeodeticCRS *CRS::extractGeodeticCRSRaw() const { + auto geodCRS = dynamic_cast<const GeodeticCRS *>(this); + if (geodCRS) { + return geodCRS; + } + auto projCRS = dynamic_cast<const ProjectedCRS *>(this); + if (projCRS) { + return projCRS->baseCRS()->extractGeodeticCRSRaw(); + } + auto compoundCRS = dynamic_cast<const CompoundCRS *>(this); + if (compoundCRS) { + for (const auto &subCrs : compoundCRS->componentReferenceSystems()) { + auto retGeogCRS = subCrs->extractGeodeticCRSRaw(); + if (retGeogCRS) { + return retGeogCRS; + } + } + } + auto boundCRS = dynamic_cast<const BoundCRS *>(this); + if (boundCRS) { + return boundCRS->baseCRS()->extractGeodeticCRSRaw(); + } + return nullptr; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the GeographicCRS of the CRS. + * + * Returns the GeographicCRS contained in a CRS. This works currently with + * input parameters of type GeographicCRS or derived, ProjectedCRS, + * CompoundCRS or BoundCRS. + * + * @return a GeographicCRSPtr, that might be null. + */ +GeographicCRSPtr CRS::extractGeographicCRS() const { + auto raw = extractGeodeticCRSRaw(); + if (raw) { + return std::dynamic_pointer_cast<GeographicCRS>( + raw->shared_from_this().as_nullable()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the VerticalCRS of the CRS. + * + * Returns the VerticalCRS contained in a CRS. This works currently with + * input parameters of type VerticalCRS or derived, CompoundCRS or BoundCRS. + * + * @return a VerticalCRSPtr, that might be null. + */ +VerticalCRSPtr CRS::extractVerticalCRS() const { + auto vertCRS = dynamic_cast<const VerticalCRS *>(this); + if (vertCRS) { + return std::dynamic_pointer_cast<VerticalCRS>( + shared_from_this().as_nullable()); + } + auto compoundCRS = dynamic_cast<const CompoundCRS *>(this); + if (compoundCRS) { + for (const auto &subCrs : compoundCRS->componentReferenceSystems()) { + auto retVertCRS = subCrs->extractVerticalCRS(); + if (retVertCRS) { + return retVertCRS; + } + } + } + auto boundCRS = dynamic_cast<const BoundCRS *>(this); + if (boundCRS) { + return boundCRS->baseCRS()->extractVerticalCRS(); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns potentially + * a BoundCRS, with a transformation to EPSG:4326, wrapping this CRS + * + * If no such BoundCRS is possible, the object will be returned. + * + * The purpose of this method is to be able to format a PROJ.4 string with + * a +towgs84 parameter or a WKT1:GDAL string with a TOWGS node. + * + * This method will fetch the GeographicCRS of this CRS and find a + * transformation to EPSG:4326 using the domain of the validity of the main CRS. + * + * @return a CRS. + */ +CRSNNPtr CRS::createBoundCRSToWGS84IfPossible( + const io::DatabaseContextPtr &dbContext) const { + auto thisAsCRS = NN_NO_CHECK( + std::static_pointer_cast<CRS>(shared_from_this().as_nullable())); + auto boundCRS = util::nn_dynamic_pointer_cast<BoundCRS>(thisAsCRS); + if (!boundCRS) { + boundCRS = canonicalBoundCRS(); + } + if (boundCRS) { + if (boundCRS->hubCRS()->_isEquivalentTo( + GeographicCRS::EPSG_4326.get(), + util::IComparable::Criterion::EQUIVALENT)) { + return NN_NO_CHECK(boundCRS); + } + } + + auto geodCRS = util::nn_dynamic_pointer_cast<GeodeticCRS>(thisAsCRS); + auto geogCRS = extractGeographicCRS(); + auto hubCRS = util::nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326); + if (geodCRS && !geogCRS) { + hubCRS = util::nn_static_pointer_cast<CRS>(GeodeticCRS::EPSG_4978); + } else if (!geogCRS || + geogCRS->_isEquivalentTo( + GeographicCRS::EPSG_4326.get(), + util::IComparable::Criterion::EQUIVALENT)) { + return thisAsCRS; + } else { + geodCRS = geogCRS; + } + auto l_domains = domains(); + metadata::ExtentPtr extent; + if (!l_domains.empty()) { + extent = l_domains[0]->domainOfValidity(); + } + + try { + auto authFactory = dbContext + ? io::AuthorityFactory::create( + NN_NO_CHECK(dbContext), std::string()) + .as_nullable() + : nullptr; + auto ctxt = operation::CoordinateOperationContext::create(authFactory, + extent, 0.0); + // ctxt->setSpatialCriterion( + // operation::CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); + auto list = + operation::CoordinateOperationFactory::create()->createOperations( + NN_NO_CHECK(geodCRS), hubCRS, ctxt); + for (const auto &op : list) { + auto transf = + util::nn_dynamic_pointer_cast<operation::Transformation>(op); + if (transf) { + try { + transf->getTOWGS84Parameters(); + } catch (const std::exception &) { + continue; + } + return util::nn_static_pointer_cast<CRS>( + BoundCRS::create(thisAsCRS, hubCRS, NN_NO_CHECK(transf))); + } else { + auto concatenated = + dynamic_cast<const operation::ConcatenatedOperation *>( + op.get()); + if (concatenated) { + // Case for EPSG:4807 / "NTF (Paris)" that is made of a + // longitude rotation followed by a Helmert + // The prime meridian shift will be accounted elsewhere + const auto &subops = concatenated->operations(); + if (subops.size() == 2) { + auto firstOpIsTransformation = + dynamic_cast<const operation::Transformation *>( + subops[0].get()); + auto firstOpIsConversion = + dynamic_cast<const operation::Conversion *>( + subops[0].get()); + if ((firstOpIsTransformation && + firstOpIsTransformation->isLongitudeRotation()) || + (dynamic_cast<DerivedCRS *>(thisAsCRS.get()) && + firstOpIsConversion)) { + transf = util::nn_dynamic_pointer_cast< + operation::Transformation>(subops[1]); + if (transf) { + try { + transf->getTOWGS84Parameters(); + } catch (const std::exception &) { + continue; + } + return util::nn_static_pointer_cast<CRS>( + BoundCRS::create(thisAsCRS, hubCRS, + NN_NO_CHECK(transf))); + } + } + } + } + } + } + } catch (const std::exception &) { + } + return thisAsCRS; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a CRS whose coordinate system does not contain a vertical + * component + * + * @return a CRS. + */ +CRSNNPtr CRS::stripVerticalComponent() const { + auto self = NN_NO_CHECK( + std::dynamic_pointer_cast<CRS>(shared_from_this().as_nullable())); + + auto geogCRS = dynamic_cast<const GeographicCRS *>(this); + if (geogCRS) { + const auto &axisList = geogCRS->coordinateSystem()->axisList(); + if (axisList.size() == 3) { + auto cs = cs::EllipsoidalCS::create(util::PropertyMap(), + axisList[0], axisList[1]); + return util::nn_static_pointer_cast<CRS>(GeographicCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + nameStr()), + geogCRS->datum(), geogCRS->datumEnsemble(), cs)); + } + } + auto projCRS = dynamic_cast<const ProjectedCRS *>(this); + if (projCRS) { + const auto &axisList = projCRS->coordinateSystem()->axisList(); + if (axisList.size() == 3) { + auto cs = cs::CartesianCS::create(util::PropertyMap(), axisList[0], + axisList[1]); + return util::nn_static_pointer_cast<CRS>(ProjectedCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + nameStr()), + projCRS->baseCRS(), projCRS->derivingConversion(), cs)); + } + } + return self; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +/** \brief Return a shallow clone of this object. */ +CRSNNPtr CRS::shallowClone() const { return _shallowClone(); } + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are either hard-coded, or looked in the database when + * authorityFactory is not null. + * + * 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 + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent), but the names do not match at all. + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * Other confidence values may be returned by some specialized implementations. + * + * This is implemented for GeodeticCRS, ProjectedCRS, VerticalCRS and + * CompoundCRS. + * + * @param authorityFactory Authority factory (or null, but degraded + * functionality) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list<std::pair<CRSNNPtr, int>> +CRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + return _identify(authorityFactory); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +CRS::_identify(const io::AuthorityFactoryPtr &) const { + return {}; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct SingleCRS::Private { + datum::DatumPtr datum{}; + datum::DatumEnsemblePtr datumEnsemble{}; + cs::CoordinateSystemNNPtr coordinateSystem; + + Private(const datum::DatumPtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::CoordinateSystemNNPtr &csIn) + : datum(datumIn), datumEnsemble(datumEnsembleIn), + coordinateSystem(csIn) { + if ((datum ? 1 : 0) + (datumEnsemble ? 1 : 0) != 1) { + throw util::Exception("datum or datumEnsemble should be set"); + } + } +}; +//! @endcond + +// --------------------------------------------------------------------------- + +SingleCRS::SingleCRS(const datum::DatumPtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::CoordinateSystemNNPtr &csIn) + : d(internal::make_unique<Private>(datumIn, datumEnsembleIn, csIn)) {} + +// --------------------------------------------------------------------------- + +SingleCRS::SingleCRS(const SingleCRS &other) + : CRS(other), d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +SingleCRS::~SingleCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::Datum associated with the CRS. + * + * This might be null, in which case datumEnsemble() return will not be null. + * + * @return a Datum that might be null. + */ +const datum::DatumPtr &SingleCRS::datum() PROJ_CONST_DEFN { return d->datum; } + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::DatumEnsemble associated with the CRS. + * + * This might be null, in which case datum() return will not be null. + * + * @return a DatumEnsemble that might be null. + */ +const datum::DatumEnsemblePtr &SingleCRS::datumEnsemble() PROJ_CONST_DEFN { + return d->datumEnsemble; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::CoordinateSystem associated with the CRS. + * + * This might be null, in which case datumEnsemble() return will not be null. + * + * @return a CoordinateSystem that might be null. + */ +const cs::CoordinateSystemNNPtr &SingleCRS::coordinateSystem() PROJ_CONST_DEFN { + return d->coordinateSystem; +} + +// --------------------------------------------------------------------------- + +bool SingleCRS::baseIsEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherSingleCRS = dynamic_cast<const SingleCRS *>(other); + if (otherSingleCRS == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !ObjectUsage::_isEquivalentTo(other, criterion))) { + return false; + } + const auto &thisDatum = d->datum; + const auto &otherDatum = otherSingleCRS->d->datum; + if (thisDatum) { + if (!thisDatum->_isEquivalentTo(otherDatum.get(), criterion)) { + return false; + } + } else { + if (otherDatum) { + return false; + } + } + + // TODO test DatumEnsemble + return d->coordinateSystem->_isEquivalentTo( + otherSingleCRS->d->coordinateSystem.get(), criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void SingleCRS::exportDatumOrDatumEnsembleToWkt( + io::WKTFormatter *formatter) const // throw(io::FormattingException) +{ + const auto &l_datum = d->datum; + if (l_datum) { + l_datum->_exportToWKT(formatter); + } else { + const auto &l_datumEnsemble = d->datumEnsemble; + assert(l_datumEnsemble); + l_datumEnsemble->_exportToWKT(formatter); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeodeticCRS::Private { + std::vector<operation::PointMotionOperationNNPtr> velocityModel{}; + datum::GeodeticReferenceFramePtr datum_; + + explicit Private(const datum::GeodeticReferenceFramePtr &datumIn) + : datum_(datumIn) {} +}; + +// --------------------------------------------------------------------------- + +static const datum::DatumEnsemblePtr & +checkEnsembleForGeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &ensemble) { + const char *msg = "One of Datum or DatumEnsemble should be defined"; + if (datumIn) { + if (!ensemble) { + return ensemble; + } + msg = "Datum and DatumEnsemble should not be defined"; + } else if (ensemble) { + const auto &datums = ensemble->datums(); + assert(!datums.empty()); + auto grfFirst = + dynamic_cast<datum::GeodeticReferenceFrame *>(datums[0].get()); + if (grfFirst) { + return ensemble; + } + msg = "Ensemble should contain GeodeticReferenceFrame"; + } + throw util::Exception(msg); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::EllipsoidalCSNNPtr &csIn) + : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), + csIn), + d(internal::make_unique<Private>(datumIn)) {} + +// --------------------------------------------------------------------------- + +GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::SphericalCSNNPtr &csIn) + : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), + csIn), + d(internal::make_unique<Private>(datumIn)) {} + +// --------------------------------------------------------------------------- + +GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::CartesianCSNNPtr &csIn) + : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), + csIn), + d(internal::make_unique<Private>(datumIn)) {} + +// --------------------------------------------------------------------------- + +GeodeticCRS::GeodeticCRS(const GeodeticCRS &other) + : SingleCRS(other), d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeodeticCRS::~GeodeticCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr GeodeticCRS::_shallowClone() const { + auto crs(GeodeticCRS::nn_make_shared<GeodeticCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::GeodeticReferenceFrame associated with the CRS. + * + * @return a GeodeticReferenceFrame or null (in which case datumEnsemble() + * should return a non-null pointer.) + */ +const datum::GeodeticReferenceFramePtr &GeodeticCRS::datum() PROJ_CONST_DEFN { + return d->datum_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static datum::GeodeticReferenceFrame *oneDatum(const GeodeticCRS *crs) { + const auto &l_datumEnsemble = crs->datumEnsemble(); + assert(l_datumEnsemble); + const auto &l_datums = l_datumEnsemble->datums(); + return static_cast<datum::GeodeticReferenceFrame *>(l_datums[0].get()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the PrimeMeridian associated with the GeodeticReferenceFrame + * or with one of the GeodeticReferenceFrame of the datumEnsemble(). + * + * @return the PrimeMeridian. + */ +const datum::PrimeMeridianNNPtr &GeodeticCRS::primeMeridian() PROJ_CONST_DEFN { + if (d->datum_) { + return d->datum_->primeMeridian(); + } + return oneDatum(this)->primeMeridian(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the ellipsoid associated with the GeodeticReferenceFrame + * or with one of the GeodeticReferenceFrame of the datumEnsemble(). + * + * @return the PrimeMeridian. + */ +const datum::EllipsoidNNPtr &GeodeticCRS::ellipsoid() PROJ_CONST_DEFN { + if (d->datum_) { + return d->datum_->ellipsoid(); + } + return oneDatum(this)->ellipsoid(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the velocity model associated with the CRS. + * + * @return a velocity model. might be null. + */ +const std::vector<operation::PointMotionOperationNNPtr> & +GeodeticCRS::velocityModel() PROJ_CONST_DEFN { + return d->velocityModel; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether the CRS is a geocentric one. + * + * A geocentric CRS is a geodetic CRS that has a Cartesian coordinate system + * with three axis, whose direction is respectively + * cs::AxisDirection::GEOCENTRIC_X, + * cs::AxisDirection::GEOCENTRIC_Y and cs::AxisDirection::GEOCENTRIC_Z. + * + * @return true if the CRS is a geocentric CRS. + */ +bool GeodeticCRS::isGeocentric() PROJ_CONST_DEFN { + const auto &cs = coordinateSystem(); + const auto &axisList = cs->axisList(); + return axisList.size() == 3 && + dynamic_cast<cs::CartesianCS *>(cs.get()) != nullptr && + &axisList[0]->direction() == &cs::AxisDirection::GEOCENTRIC_X && + &axisList[1]->direction() == &cs::AxisDirection::GEOCENTRIC_Y && + &axisList[2]->direction() == &cs::AxisDirection::GEOCENTRIC_Z; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeodeticCRS from a datum::GeodeticReferenceFrame and a + * cs::SphericalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS. + * @param cs a SphericalCS. + * @return new GeodeticCRS. + */ +GeodeticCRSNNPtr +GeodeticCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFrameNNPtr &datum, + const cs::SphericalCSNNPtr &cs) { + return create(properties, datum.as_nullable(), nullptr, cs); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeodeticCRS from a datum::GeodeticReferenceFrame or + * datum::DatumEnsemble and a cs::SphericalCS. + * + * One and only one of datum or datumEnsemble should be set to a non-null value. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS, or nullptr + * @param datumEnsemble The datum ensemble of the CRS, or nullptr. + * @param cs a SphericalCS. + * @return new GeodeticCRS. + */ +GeodeticCRSNNPtr +GeodeticCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFramePtr &datum, + const datum::DatumEnsemblePtr &datumEnsemble, + const cs::SphericalCSNNPtr &cs) { + auto crs( + GeodeticCRS::nn_make_shared<GeodeticCRS>(datum, datumEnsemble, cs)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeodeticCRS from a datum::GeodeticReferenceFrame and a + * cs::CartesianCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS. + * @param cs a CartesianCS. + * @return new GeodeticCRS. + */ +GeodeticCRSNNPtr +GeodeticCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFrameNNPtr &datum, + const cs::CartesianCSNNPtr &cs) { + return create(properties, datum.as_nullable(), nullptr, cs); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeodeticCRS from a datum::GeodeticReferenceFrame or + * datum::DatumEnsemble and a cs::CartesianCS. + * + * One and only one of datum or datumEnsemble should be set to a non-null value. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS, or nullptr + * @param datumEnsemble The datum ensemble of the CRS, or nullptr. + * @param cs a CartesianCS + * @return new GeodeticCRS. + */ +GeodeticCRSNNPtr +GeodeticCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFramePtr &datum, + const datum::DatumEnsemblePtr &datumEnsemble, + const cs::CartesianCSNNPtr &cs) { + auto crs( + GeodeticCRS::nn_make_shared<GeodeticCRS>(datum, datumEnsemble, cs)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? ((formatter->use2018Keywords() && + dynamic_cast<const GeographicCRS *>(this)) + ? io::WKTConstants::GEOGCRS + : io::WKTConstants::GEODCRS) + : isGeocentric() ? io::WKTConstants::GEOCCS + : io::WKTConstants::GEOGCS, + !identifiers().empty()); + auto l_name = nameStr(); + const auto &cs = coordinateSystem(); + const auto &axisList = cs->axisList(); + + if (formatter->useESRIDialect()) { + if (axisList.size() != 2) { + io::FormattingException::Throw( + "Only export of Geographic 2D CRS is supported in ESRI_WKT1"); + } + + if (l_name == "WGS 84") { + l_name = "GCS_WGS_1984"; + } else { + bool aliasFound = false; + const auto &dbContext = formatter->databaseContext(); + if (dbContext) { + auto l_alias = dbContext->getAliasFromOfficialName( + l_name, "geodetic_crs", "ESRI"); + if (!l_alias.empty()) { + l_name = l_alias; + aliasFound = true; + } + } + if (!aliasFound) { + l_name = io::WKTFormatter::morphNameToESRI(l_name); + if (!starts_with(l_name, "GCS_")) { + l_name = "GCS_" + l_name; + } + } + } + } + if (!isWKT2 && !formatter->useESRIDialect() && isDeprecated()) { + l_name += " (deprecated)"; + } + formatter->addQuotedString(l_name); + + const auto &unit = axisList[0]->unit(); + formatter->pushAxisAngularUnit(common::UnitOfMeasure::create(unit)); + exportDatumOrDatumEnsembleToWkt(formatter); + primeMeridian()->_exportToWKT(formatter); + formatter->popAxisAngularUnit(); + if (!isWKT2) { + unit._exportToWKT(formatter); + } + cs->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeodeticCRS::addGeocentricUnitConversionIntoPROJString( + io::PROJStringFormatter *formatter) const { + + const auto &axisList = coordinateSystem()->axisList(); + const auto &unit = axisList[0]->unit(); + if (unit != common::UnitOfMeasure::METRE) { + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + io::FormattingException::Throw("GeodeticCRS::exportToPROJString(): " + "non-meter unit not supported for " + "PROJ.4"); + } + + formatter->addStep("unitconvert"); + formatter->addParam("xy_in", "m"); + formatter->addParam("z_in", "m"); + { + auto projUnit = unit.exportToPROJString(); + if (!projUnit.empty()) { + formatter->addParam("xy_out", projUnit); + formatter->addParam("z_out", projUnit); + return; + } + } + + const auto &toSI = unit.conversionToSI(); + formatter->addParam("xy_out", toSI); + formatter->addParam("z_out", toSI); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeodeticCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + if (!isGeocentric()) { + io::FormattingException::Throw( + "GeodeticCRS::exportToPROJString() only " + "supports geocentric coordinate systems"); + } + + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + formatter->addStep("geocent"); + } else { + formatter->addStep("cart"); + } + ellipsoid()->_exportToPROJString(formatter); + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + const auto &TOWGS84Params = formatter->getTOWGS84Parameters(); + if (TOWGS84Params.size() == 7) { + formatter->addParam("towgs84", TOWGS84Params); + } + } + addGeocentricUnitConversionIntoPROJString(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeodeticCRS::addDatumInfoToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + const auto &TOWGS84Params = formatter->getTOWGS84Parameters(); + bool datumWritten = false; + const auto &nadgrids = formatter->getHDatumExtension(); + const auto &l_datum = datum(); + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4 && + l_datum && TOWGS84Params.empty() && nadgrids.empty()) { + if (l_datum->_isEquivalentTo( + datum::GeodeticReferenceFrame::EPSG_6326.get(), + util::IComparable::Criterion::EQUIVALENT)) { + datumWritten = true; + formatter->addParam("datum", "WGS84"); + } else if (l_datum->_isEquivalentTo( + datum::GeodeticReferenceFrame::EPSG_6267.get(), + util::IComparable::Criterion::EQUIVALENT)) { + datumWritten = true; + formatter->addParam("datum", "NAD27"); + } else if (l_datum->_isEquivalentTo( + datum::GeodeticReferenceFrame::EPSG_6269.get(), + util::IComparable::Criterion::EQUIVALENT)) { + datumWritten = true; + formatter->addParam("datum", "NAD83"); + } + } + if (!datumWritten) { + ellipsoid()->_exportToPROJString(formatter); + primeMeridian()->_exportToPROJString(formatter); + } + if (TOWGS84Params.size() == 7) { + formatter->addParam("towgs84", TOWGS84Params); + } + if (!nadgrids.empty()) { + formatter->addParam("nadgrids", nadgrids); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static util::IComparable::Criterion +getStandardCriterion(util::IComparable::Criterion criterion) { + return criterion == util::IComparable::Criterion:: + EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS + ? util::IComparable::Criterion::EQUIVALENT + : criterion; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool GeodeticCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + const auto standardCriterion = getStandardCriterion(criterion); + auto otherGeodCRS = dynamic_cast<const GeodeticCRS *>(other); + // TODO test velocityModel + return otherGeodCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, standardCriterion); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static util::PropertyMap createMapNameEPSGCode(const char *name, int code) { + return util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, name) + .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, code); +} +//! @endcond + +// --------------------------------------------------------------------------- + +GeodeticCRSNNPtr GeodeticCRS::createEPSG_4978() { + return create( + createMapNameEPSGCode("WGS 84", 4978), + datum::GeodeticReferenceFrame::EPSG_6326, + cs::CartesianCS::createGeocentric(common::UnitOfMeasure::METRE)); +} + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are either hard-coded, or looked in the database when + * authorityFactory is not null. + * + * 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 + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent (equivalent datum and coordinate system), + * but the names do not match at all. + * 60% means that ellipsoid, prime meridian and coordinate systems are + * equivalent, but the CRS and datum names do not match. + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * + * @param authorityFactory Authority factory (or null, but degraded + * functionality) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list<std::pair<GeodeticCRSNNPtr, int>> +GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<GeodeticCRSNNPtr, int> Pair; + std::list<Pair> res; + const auto &thisName(nameStr()); + + const GeographicCRSNNPtr candidatesCRS[] = {GeographicCRS::EPSG_4326, + GeographicCRS::EPSG_4267, + GeographicCRS::EPSG_4269}; + for (const auto &crs : candidatesCRS) { + 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(), util::IComparable::Criterion::EQUIVALENT); + if (nameEquivalent && isEq && (!authorityFactory || nameEqual)) { + res.emplace_back(util::nn_static_pointer_cast<GeodeticCRS>(crs), + nameEqual ? 100 : 90); + return res; + } else if (nameEqual && !isEq && !authorityFactory) { + res.emplace_back(util::nn_static_pointer_cast<GeodeticCRS>(crs), + 25); + return res; + } else if (isEq && !authorityFactory) { + res.emplace_back(util::nn_static_pointer_cast<GeodeticCRS>(crs), + 70); + return res; + } + } + + std::string geodetic_crs_type; + if (isGeocentric()) { + geodetic_crs_type = "geocentric"; + } else { + auto geogCRS = dynamic_cast<const GeographicCRS *>(this); + if (geogCRS) { + if (coordinateSystem()->axisList().size() == 2) { + geodetic_crs_type = "geographic 2D"; + } else { + geodetic_crs_type = "geographic 3D"; + } + } + } + + if (authorityFactory) { + + const auto &thisDatum(datum()); + + auto searchByDatum = [this, &authorityFactory, &res, &thisDatum, + &geodetic_crs_type]() { + 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(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(crs, 70); + } + } + } catch (const std::exception &) { + } + } + }; + + const auto &thisEllipsoid(ellipsoid()); + auto searchByEllipsoid = [this, &authorityFactory, &res, &thisDatum, + &thisEllipsoid, &geodetic_crs_type]() { + const auto ellipsoids = + thisEllipsoid->identifiers().empty() + ? authorityFactory->createEllipsoidFromExisting( + thisEllipsoid) + : std::list<datum::EllipsoidNNPtr>{thisEllipsoid}; + for (const auto &ellps : ellipsoids) { + for (const auto &id : ellps->identifiers()) { + try { + auto tempRes = + authorityFactory->createGeodeticCRSFromEllipsoid( + *id->codeSpace(), id->code(), + geodetic_crs_type); + for (const auto &crs : tempRes) { + const auto &crsDatum(crs->datum()); + if (crsDatum && + crsDatum->ellipsoid()->_isEquivalentTo( + ellps.get(), + util::IComparable::Criterion::EQUIVALENT) && + crsDatum->primeMeridian()->_isEquivalentTo( + thisDatum->primeMeridian().get(), + util::IComparable::Criterion::EQUIVALENT) && + coordinateSystem()->_isEquivalentTo( + crs->coordinateSystem().get(), + util::IComparable::Criterion::EQUIVALENT) + + ) { + res.emplace_back(crs, 60); + } + } + } catch (const std::exception &) { + } + } + } + }; + + const bool unsignificantName = thisName.empty() || + ci_equal(thisName, "unknown") || + ci_equal(thisName, "unnamed"); + + if (unsignificantName) { + if (thisDatum) { + if (!thisDatum->identifiers().empty()) { + searchByDatum(); + } else { + searchByEllipsoid(); + } + } + } else if (!identifiers().empty()) { + // If the CRS has already an id, check in the database for the + // official object, and verify that they are equivalent. + for (const auto &id : identifiers()) { + try { + auto crs = io::AuthorityFactory::create( + authorityFactory->databaseContext(), + *id->codeSpace()) + ->createGeodeticCRS(id->code()); + bool match = _isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT); + res.emplace_back(crs, match ? 100 : 25); + return res; + } catch (const std::exception &) { + } + } + } else { + for (int ipass = 0; ipass < 2; ipass++) { + const bool approximateMatch = ipass == 1; + auto objects = authorityFactory->createObjectsFromName( + thisName, {io::AuthorityFactory::ObjectType::GEODETIC_CRS}, + approximateMatch); + for (const auto &obj : objects) { + auto crs = util::nn_dynamic_pointer_cast<GeodeticCRS>(obj); + assert(crs); + auto crsNN = NN_NO_CHECK(crs); + if (_isEquivalentTo( + crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (crs->nameStr() == thisName) { + res.clear(); + res.emplace_back(crsNN, 100); + return res; + } + const bool eqName = + metadata::Identifier::isEquivalentName( + thisName.c_str(), crs->nameStr().c_str()); + res.emplace_back(crsNN, eqName ? 90 : 70); + } else { + res.emplace_back(crsNN, 25); + } + } + if (!res.empty()) { + break; + } + } + if (res.empty() && thisDatum) { + if (!thisDatum->identifiers().empty()) { + searchByDatum(); + } else { + searchByEllipsoid(); + } + } + } + + const auto &thisCS(coordinateSystem()); + // Sort results + res.sort([&thisName, &thisDatum, &thisCS](const Pair &a, + const Pair &b) { + // First consider confidence + if (a.second > b.second) { + return true; + } + if (a.second < b.second) { + return false; + } + + // Then consider exact name matching + const auto &aName(a.first->nameStr()); + const auto &bName(b.first->nameStr()); + if (aName == thisName && bName != thisName) { + return true; + } + if (bName == thisName && aName != thisName) { + return false; + } + + // Then datum matching + const auto &aDatum(a.first->datum()); + const auto &bDatum(b.first->datum()); + if (thisDatum && aDatum && bDatum) { + const auto thisEquivADatum(thisDatum->_isEquivalentTo( + aDatum.get(), util::IComparable::Criterion::EQUIVALENT)); + const auto thisEquivBDatum(thisDatum->_isEquivalentTo( + bDatum.get(), util::IComparable::Criterion::EQUIVALENT)); + + if (thisEquivADatum && !thisEquivBDatum) { + return true; + } + if (!thisEquivADatum && thisEquivBDatum) { + return false; + } + } + + // Then coordinate system matching + const auto &aCS(a.first->coordinateSystem()); + const auto &bCS(b.first->coordinateSystem()); + const auto thisEquivACs(thisCS->_isEquivalentTo( + aCS.get(), util::IComparable::Criterion::EQUIVALENT)); + const auto thisEquivBCs(thisCS->_isEquivalentTo( + bCS.get(), util::IComparable::Criterion::EQUIVALENT)); + if (thisEquivACs && !thisEquivBCs) { + return true; + } + if (!thisEquivACs && thisEquivBCs) { + return false; + } + + // Then dimension of the coordinate system matching + const auto thisCSAxisListSize = thisCS->axisList().size(); + const auto aCSAxistListSize = aCS->axisList().size(); + const auto bCSAxistListSize = bCS->axisList().size(); + if (thisCSAxisListSize == aCSAxistListSize && + thisCSAxisListSize != bCSAxistListSize) { + return true; + } + if (thisCSAxisListSize != aCSAxistListSize && + thisCSAxisListSize == bCSAxistListSize) { + return false; + } + + if (aDatum && bDatum) { + // Favor the CRS whole ellipsoid names matches the ellipsoid + // name (WGS84...) + const bool aEllpsNameEqCRSName = + metadata::Identifier::isEquivalentName( + aDatum->ellipsoid()->nameStr().c_str(), + a.first->nameStr().c_str()); + const bool bEllpsNameEqCRSName = + metadata::Identifier::isEquivalentName( + bDatum->ellipsoid()->nameStr().c_str(), + b.first->nameStr().c_str()); + if (aEllpsNameEqCRSName && !bEllpsNameEqCRSName) { + return true; + } + if (bEllpsNameEqCRSName && !aEllpsNameEqCRSName) { + return false; + } + } + + // Arbitrary final sorting criterion + return aName < bName; + }); + + // If there are results with 90% confidence, only keep those + if (res.size() >= 2 && res.front().second == 90) { + std::list<Pair> newRes; + for (const auto &pair : res) { + if (pair.second == 90) { + newRes.push_back(pair); + } else { + break; + } + } + return newRes; + } + } + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +GeodeticCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<CRSNNPtr, int> Pair; + std::list<Pair> res; + auto resTemp = identify(authorityFactory); + for (const auto &pair : resTemp) { + res.emplace_back(pair.first, pair.second); + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeographicCRS::Private { + cs::EllipsoidalCSNNPtr coordinateSystem_; + explicit Private(const cs::EllipsoidalCSNNPtr &csIn) + : coordinateSystem_(csIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +GeographicCRS::GeographicCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::EllipsoidalCSNNPtr &csIn) + : SingleCRS(datumIn, datumEnsembleIn, csIn), + GeodeticCRS(datumIn, + checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), csIn), + d(internal::make_unique<Private>(csIn)) {} + +// --------------------------------------------------------------------------- + +GeographicCRS::GeographicCRS(const GeographicCRS &other) + : SingleCRS(other), GeodeticCRS(other), + d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeographicCRS::~GeographicCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr GeographicCRS::_shallowClone() const { + auto crs(GeographicCRS::nn_make_shared<GeographicCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::EllipsoidalCS associated with the CRS. + * + * @return a EllipsoidalCS. + */ +const cs::EllipsoidalCSNNPtr & +GeographicCRS::coordinateSystem() PROJ_CONST_DEFN { + return d->coordinateSystem_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeographicCRS from a datum::GeodeticReferenceFrameNNPtr + * and a + * cs::EllipsoidalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS. + * @param cs a EllipsoidalCS. + * @return new GeographicCRS. + */ +GeographicCRSNNPtr +GeographicCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFrameNNPtr &datum, + const cs::EllipsoidalCSNNPtr &cs) { + return create(properties, datum.as_nullable(), nullptr, cs); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeographicCRS from a datum::GeodeticReferenceFramePtr + * or + * datum::DatumEnsemble and a + * cs::EllipsoidalCS. + * + * One and only one of datum or datumEnsemble should be set to a non-null value. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS, or nullptr + * @param datumEnsemble The datum ensemble of the CRS, or nullptr. + * @param cs a EllipsoidalCS. + * @return new GeographicCRS. + */ +GeographicCRSNNPtr +GeographicCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFramePtr &datum, + const datum::DatumEnsemblePtr &datumEnsemble, + const cs::EllipsoidalCSNNPtr &cs) { + GeographicCRSNNPtr crs( + GeographicCRS::nn_make_shared<GeographicCRS>(datum, datumEnsemble, cs)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +/** \brief Return whether the current GeographicCRS is the 2D part of the + * other 3D GeographicCRS. + */ +bool GeographicCRS::is2DPartOf3D(util::nn<const GeographicCRS *> other) + PROJ_CONST_DEFN { + const auto &axis = d->coordinateSystem_->axisList(); + const auto &otherAxis = other->d->coordinateSystem_->axisList(); + if (!(axis.size() == 2 && otherAxis.size() == 3)) { + return false; + } + const auto &firstAxis = axis[0]; + const auto &secondAxis = axis[1]; + const auto &otherFirstAxis = otherAxis[0]; + const auto &otherSecondAxis = otherAxis[1]; + if (!(firstAxis->_isEquivalentTo(otherFirstAxis.get()) && + secondAxis->_isEquivalentTo(otherSecondAxis.get()))) { + return false; + } + const auto &thisDatum = GeodeticCRS::getPrivate()->datum_; + const auto &otherDatum = other->GeodeticCRS::getPrivate()->datum_; + if (thisDatum && otherDatum) { + return thisDatum->_isEquivalentTo(otherDatum.get()); + } + return false; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool GeographicCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherGeogCRS = dynamic_cast<const GeographicCRS *>(other); + if (otherGeogCRS == nullptr) { + return false; + } + const auto standardCriterion = getStandardCriterion(criterion); + if (GeodeticCRS::_isEquivalentTo(other, standardCriterion)) { + return true; + } + if (criterion != + util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS) { + return false; + } + const auto axisOrder = coordinateSystem()->axisOrder(); + if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH || + axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) { + const auto &unit = coordinateSystem()->axisList()[0]->unit(); + return GeographicCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + nameStr()), + datum(), datumEnsemble(), + axisOrder == + cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH + ? cs::EllipsoidalCS::createLatitudeLongitude(unit) + : cs::EllipsoidalCS::createLongitudeLatitude(unit)) + ->GeodeticCRS::_isEquivalentTo(other, standardCriterion); + } + return false; +} +//! @endcond + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4267() { + return create(createMapNameEPSGCode("NAD27", 4267), + datum::GeodeticReferenceFrame::EPSG_6267, + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4269() { + return create(createMapNameEPSGCode("NAD83", 4269), + datum::GeodeticReferenceFrame::EPSG_6269, + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4326() { + return create(createMapNameEPSGCode("WGS 84", 4326), + datum::GeodeticReferenceFrame::EPSG_6326, + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createOGC_CRS84() { + util::PropertyMap propertiesCRS; + propertiesCRS + .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::OGC) + .set(metadata::Identifier::CODE_KEY, "CRS84") + .set(common::IdentifiedObject::NAME_KEY, "WGS 84 (CRS84)"); + return create(propertiesCRS, datum::GeodeticReferenceFrame::EPSG_6326, + cs::EllipsoidalCS::createLongitudeLatitude( // Long Lat ! + common::UnitOfMeasure::DEGREE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4979() { + return create( + createMapNameEPSGCode("WGS 84", 4979), + datum::GeodeticReferenceFrame::EPSG_6326, + cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( + common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4807() { + auto ellps(datum::Ellipsoid::createFlattenedSphere( + createMapNameEPSGCode("Clarke 1880 (IGN)", 7011), + common::Length(6378249.2), common::Scale(293.4660212936269))); + + auto cs(cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::GRAD)); + + auto datum(datum::GeodeticReferenceFrame::create( + createMapNameEPSGCode("Nouvelle Triangulation Francaise (Paris)", 6807), + ellps, util::optional<std::string>(), datum::PrimeMeridian::PARIS)); + + return create(createMapNameEPSGCode("NTF (Paris)", 4807), datum, cs); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeographicCRS::addAngularUnitConvertAndAxisSwap( + io::PROJStringFormatter *formatter) const { + const auto &axisList = coordinateSystem()->axisList(); + + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + + formatter->addStep("unitconvert"); + formatter->addParam("xy_in", "rad"); + if (axisList.size() == 3 && !formatter->omitZUnitConversion()) { + formatter->addParam("z_in", "m"); + } + { + const auto &unitHoriz = axisList[0]->unit(); + const auto projUnit = unitHoriz.exportToPROJString(); + if (projUnit.empty()) { + formatter->addParam("xy_out", unitHoriz.conversionToSI()); + } else { + formatter->addParam("xy_out", projUnit); + } + } + if (axisList.size() == 3 && !formatter->omitZUnitConversion()) { + const auto &unitZ = axisList[2]->unit(); + auto projVUnit = unitZ.exportToPROJString(); + if (projVUnit.empty()) { + formatter->addParam("z_out", unitZ.conversionToSI()); + } else { + formatter->addParam("z_out", projVUnit); + } + } + + const char *order[2] = {nullptr, nullptr}; + const char *one = "1"; + const char *two = "2"; + for (int i = 0; i < 2; i++) { + const auto &dir = axisList[i]->direction(); + if (&dir == &cs::AxisDirection::WEST) { + order[i] = "-1"; + } else if (&dir == &cs::AxisDirection::EAST) { + order[i] = one; + } else if (&dir == &cs::AxisDirection::SOUTH) { + order[i] = "-2"; + } else if (&dir == &cs::AxisDirection::NORTH) { + order[i] = two; + } + } + if (order[0] && order[1] && (order[0] != one || order[1] != two)) { + formatter->addStep("axisswap"); + char orderStr[10]; + strcpy(orderStr, order[0]); + strcat(orderStr, ","); + strcat(orderStr, order[1]); + formatter->addParam("order", orderStr); + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeographicCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + if (!formatter->omitProjLongLatIfPossible() || + primeMeridian()->longitude().getSIValue() != 0.0 || + !formatter->getTOWGS84Parameters().empty() || + !formatter->getHDatumExtension().empty()) { + formatter->addStep("longlat"); + addDatumInfoToPROJString(formatter); + } + + addAngularUnitConvertAndAxisSwap(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct VerticalCRS::Private { + std::vector<operation::TransformationNNPtr> geoidModel{}; + std::vector<operation::PointMotionOperationNNPtr> velocityModel{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const datum::DatumEnsemblePtr & +checkEnsembleForVerticalCRS(const datum::VerticalReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &ensemble) { + const char *msg = "One of Datum or DatumEnsemble should be defined"; + if (datumIn) { + if (!ensemble) { + return ensemble; + } + msg = "Datum and DatumEnsemble should not be defined"; + } else if (ensemble) { + const auto &datums = ensemble->datums(); + assert(!datums.empty()); + auto grfFirst = + dynamic_cast<datum::VerticalReferenceFrame *>(datums[0].get()); + if (grfFirst) { + return ensemble; + } + msg = "Ensemble should contain VerticalReferenceFrame"; + } + throw util::Exception(msg); +} +//! @endcond + +// --------------------------------------------------------------------------- + +VerticalCRS::VerticalCRS(const datum::VerticalReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::VerticalCSNNPtr &csIn) + : SingleCRS(datumIn, checkEnsembleForVerticalCRS(datumIn, datumEnsembleIn), + csIn), + d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +VerticalCRS::VerticalCRS(const VerticalCRS &other) + : SingleCRS(other), d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +VerticalCRS::~VerticalCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr VerticalCRS::_shallowClone() const { + auto crs(VerticalCRS::nn_make_shared<VerticalCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::VerticalReferenceFrame associated with the CRS. + * + * @return a VerticalReferenceFrame. + */ +const datum::VerticalReferenceFramePtr VerticalCRS::datum() const { + return std::static_pointer_cast<datum::VerticalReferenceFrame>( + SingleCRS::getPrivate()->datum); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the geoid model associated with the CRS. + * + * Geoid height model or height correction model linked to a geoid-based + * vertical CRS. + * + * @return a geoid model. might be null + */ +const std::vector<operation::TransformationNNPtr> & +VerticalCRS::geoidModel() PROJ_CONST_DEFN { + return d->geoidModel; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the velocity model associated with the CRS. + * + * @return a velocity model. might be null. + */ +const std::vector<operation::PointMotionOperationNNPtr> & +VerticalCRS::velocityModel() PROJ_CONST_DEFN { + return d->velocityModel; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::VerticalCS associated with the CRS. + * + * @return a VerticalCS. + */ +const cs::VerticalCSNNPtr VerticalCRS::coordinateSystem() const { + return util::nn_static_pointer_cast<cs::VerticalCS>( + SingleCRS::getPrivate()->coordinateSystem); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void VerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::VERTCRS + : io::WKTConstants::VERT_CS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + exportDatumOrDatumEnsembleToWkt(formatter); + const auto &cs = SingleCRS::getPrivate()->coordinateSystem; + const auto &axisList = cs->axisList(); + if (!isWKT2) { + axisList[0]->unit()._exportToWKT(formatter); + } + cs->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void VerticalCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + auto geoidgrids = formatter->getVDatumExtension(); + if (!geoidgrids.empty()) { + formatter->addParam("geoidgrids", geoidgrids); + } + + auto &axisList = coordinateSystem()->axisList(); + if (!axisList.empty()) { + auto projUnit = axisList[0]->unit().exportToPROJString(); + if (projUnit.empty()) { + formatter->addParam("vto_meter", + axisList[0]->unit().conversionToSI()); + } else { + formatter->addParam("vunits", projUnit); + } + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void VerticalCRS::addLinearUnitConvert( + io::PROJStringFormatter *formatter) const { + auto &axisList = coordinateSystem()->axisList(); + + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + if (!axisList.empty()) { + auto projUnit = axisList[0]->unit().exportToPROJString(); + if (axisList[0]->unit().conversionToSI() != 1.0) { + formatter->addStep("unitconvert"); + formatter->addParam("z_in", "m"); + auto projVUnit = axisList[0]->unit().exportToPROJString(); + if (projVUnit.empty()) { + formatter->addParam("z_out", + axisList[0]->unit().conversionToSI()); + } else { + formatter->addParam("z_out", projVUnit); + } + } + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a VerticalCRS from a datum::VerticalReferenceFrame and a + * cs::VerticalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn The datum of the CRS. + * @param csIn a VerticalCS. + * @return new VerticalCRS. + */ +VerticalCRSNNPtr +VerticalCRS::create(const util::PropertyMap &properties, + const datum::VerticalReferenceFrameNNPtr &datumIn, + const cs::VerticalCSNNPtr &csIn) { + return create(properties, datumIn.as_nullable(), nullptr, csIn); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a VerticalCRS from a datum::VerticalReferenceFrame or + * datum::DatumEnsemble and a cs::VerticalCS. + * + * One and only one of datum or datumEnsemble should be set to a non-null value. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn The datum of the CRS, or nullptr + * @param datumEnsembleIn The datum ensemble of the CRS, or nullptr. + * @param csIn a VerticalCS. + * @return new VerticalCRS. + */ +VerticalCRSNNPtr +VerticalCRS::create(const util::PropertyMap &properties, + const datum::VerticalReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::VerticalCSNNPtr &csIn) { + auto crs(VerticalCRS::nn_make_shared<VerticalCRS>(datumIn, datumEnsembleIn, + csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool VerticalCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherVertCRS = dynamic_cast<const VerticalCRS *>(other); + // TODO test geoidModel and velocityModel + return otherVertCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, criterion); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are looked in the database when + * authorityFactory is not null. + * + * 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 + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent (equivalent datum and coordinate system), + * but the names do not match at all. + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * + * @param authorityFactory Authority factory (if null, will return an empty + * list) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list<std::pair<VerticalCRSNNPtr, int>> +VerticalCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<VerticalCRSNNPtr, int> Pair; + std::list<Pair> res; + + const auto &thisName(nameStr()); + + if (authorityFactory) { + + const bool unsignificantName = thisName.empty() || + ci_equal(thisName, "unknown") || + ci_equal(thisName, "unnamed"); + if (!identifiers().empty()) { + // If the CRS has already an id, check in the database for the + // official object, and verify that they are equivalent. + for (const auto &id : identifiers()) { + try { + auto crs = io::AuthorityFactory::create( + authorityFactory->databaseContext(), + *id->codeSpace()) + ->createVerticalCRS(id->code()); + bool match = _isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT); + res.emplace_back(crs, match ? 100 : 25); + return res; + } catch (const std::exception &) { + } + } + } else if (!unsignificantName) { + for (int ipass = 0; ipass < 2; ipass++) { + const bool approximateMatch = ipass == 1; + auto objects = authorityFactory->createObjectsFromName( + thisName, {io::AuthorityFactory::ObjectType::VERTICAL_CRS}, + approximateMatch); + for (const auto &obj : objects) { + auto crs = util::nn_dynamic_pointer_cast<VerticalCRS>(obj); + assert(crs); + auto crsNN = NN_NO_CHECK(crs); + if (_isEquivalentTo( + crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (crs->nameStr() == thisName) { + res.clear(); + res.emplace_back(crsNN, 100); + return res; + } + res.emplace_back(crsNN, 90); + } else { + res.emplace_back(crsNN, 25); + } + } + if (!res.empty()) { + break; + } + } + } + + // Sort results + res.sort([&thisName](const Pair &a, const Pair &b) { + // First consider confidence + if (a.second > b.second) { + return true; + } + if (a.second < b.second) { + return false; + } + + // Then consider exact name matching + const auto &aName(a.first->nameStr()); + const auto &bName(b.first->nameStr()); + if (aName == thisName && bName != thisName) { + return true; + } + if (bName == thisName && aName != thisName) { + return false; + } + + // Arbitrary final sorting criterion + return aName < bName; + }); + + // Keep only results of the highest confidence + if (res.size() >= 2) { + const auto highestConfidence = res.front().second; + std::list<Pair> newRes; + for (const auto &pair : res) { + if (pair.second == highestConfidence) { + newRes.push_back(pair); + } else { + break; + } + } + return newRes; + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +VerticalCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<CRSNNPtr, int> Pair; + std::list<Pair> res; + auto resTemp = identify(authorityFactory); + for (const auto &pair : resTemp) { + res.emplace_back(pair.first, pair.second); + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedCRS::Private { + SingleCRSNNPtr baseCRS_; + operation::ConversionNNPtr derivingConversion_; + + Private(const SingleCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn) + : baseCRS_(baseCRSIn), derivingConversion_(derivingConversionIn) {} + + // For the conversion make a _shallowClone(), so that we can later set + // its targetCRS to this. + Private(const Private &other) + : baseCRS_(other.baseCRS_), + derivingConversion_(other.derivingConversion_->shallowClone()) {} +}; + +//! @endcond + +// --------------------------------------------------------------------------- + +// DerivedCRS is an abstract class, that virtually inherits from SingleCRS +// Consequently the base constructor in SingleCRS will never be called by +// that constructor. clang -Wabstract-vbase-init and VC++ underline this, but +// other +// compilers will complain if we don't call the base constructor. + +DerivedCRS::DerivedCRS(const SingleCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CoordinateSystemNNPtr & +#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) + cs +#endif + ) + : +#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) + SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), cs), +#endif + d(internal::make_unique<Private>(baseCRSIn, derivingConversionIn)) { +} + +// --------------------------------------------------------------------------- + +DerivedCRS::DerivedCRS(const DerivedCRS &other) + : +#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) + SingleCRS(other), +#endif + d(internal::make_unique<Private>(*other.d)) { +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedCRS::~DerivedCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS of a DerivedCRS. + * + * @return the base CRS. + */ +const SingleCRSNNPtr &DerivedCRS::baseCRS() PROJ_CONST_DEFN { + return d->baseCRS_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the deriving conversion from the base CRS to this CRS. + * + * @return the deriving conversion. + */ +const operation::ConversionNNPtr DerivedCRS::derivingConversion() const { + return d->derivingConversion_->shallowClone(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const operation::ConversionNNPtr & +DerivedCRS::derivingConversionRef() PROJ_CONST_DEFN { + return d->derivingConversion_; +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool DerivedCRS::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast<const DerivedCRS *>(other); + const auto standardCriterion = getStandardCriterion(criterion); + if (otherDerivedCRS == nullptr || + !SingleCRS::baseIsEquivalentTo(other, standardCriterion)) { + return false; + } + return d->baseCRS_->_isEquivalentTo(otherDerivedCRS->d->baseCRS_.get(), + criterion) && + d->derivingConversion_->_isEquivalentTo( + otherDerivedCRS->d->derivingConversion_.get(), + standardCriterion); +} + +// --------------------------------------------------------------------------- + +void DerivedCRS::setDerivingConversionCRS() { + derivingConversionRef()->setWeakSourceTargetCRS( + baseCRS().as_nullable(), + std::static_pointer_cast<CRS>(shared_from_this().as_nullable())); +} + +// --------------------------------------------------------------------------- + +void DerivedCRS::baseExportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + d->derivingConversion_->_exportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +void DerivedCRS::baseExportToWKT(io::WKTFormatter *&formatter, + const std::string &keyword, + const std::string &baseKeyword) const { + formatter->startNode(keyword, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + const auto &l_baseCRS = d->baseCRS_; + formatter->startNode(baseKeyword, !l_baseCRS->identifiers().empty()); + formatter->addQuotedString(l_baseCRS->nameStr()); + l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter); + formatter->endNode(); + + formatter->setUseDerivingConversion(true); + derivingConversionRef()->_exportToWKT(formatter); + formatter->setUseDerivingConversion(false); + + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ProjectedCRS::Private { + GeodeticCRSNNPtr baseCRS_; + cs::CartesianCSNNPtr cs_; + Private(const GeodeticCRSNNPtr &baseCRSIn, const cs::CartesianCSNNPtr &csIn) + : baseCRS_(baseCRSIn), cs_(csIn) {} + + inline const GeodeticCRSNNPtr &baseCRS() const { return baseCRS_; } + + inline const cs::CartesianCSNNPtr &coordinateSystem() const { return cs_; } +}; +//! @endcond + +// --------------------------------------------------------------------------- + +ProjectedCRS::ProjectedCRS( + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CartesianCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), + d(internal::make_unique<Private>(baseCRSIn, csIn)) {} + +// --------------------------------------------------------------------------- + +ProjectedCRS::ProjectedCRS(const ProjectedCRS &other) + : SingleCRS(other), DerivedCRS(other), + d(internal::make_unique<Private>(other.baseCRS(), + other.coordinateSystem())) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ProjectedCRS::~ProjectedCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr ProjectedCRS::_shallowClone() const { + auto crs(ProjectedCRS::nn_make_shared<ProjectedCRS>(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a GeodeticCRS, which is generally a + * GeographicCRS) of the ProjectedCRS. + * + * @return the base CRS. + */ +const GeodeticCRSNNPtr &ProjectedCRS::baseCRS() PROJ_CONST_DEFN { + return d->baseCRS(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::CartesianCS associated with the CRS. + * + * @return a CartesianCS + */ +const cs::CartesianCSNNPtr &ProjectedCRS::coordinateSystem() PROJ_CONST_DEFN { + return d->coordinateSystem(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + + if (!isWKT2 && !formatter->useESRIDialect() && + starts_with(nameStr(), "Popular Visualisation CRS / Mercator")) { + formatter->startNode(io::WKTConstants::PROJCS, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + formatter->setTOWGS84Parameters({0, 0, 0, 0, 0, 0, 0}); + baseCRS()->_exportToWKT(formatter); + formatter->setTOWGS84Parameters({}); + + formatter->startNode(io::WKTConstants::PROJECTION, false); + formatter->addQuotedString("Mercator_1SP"); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("central_meridian"); + formatter->add(0.0); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("scale_factor"); + formatter->add(1.0); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("false_easting"); + formatter->add(0.0); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("false_northing"); + formatter->add(0.0); + formatter->endNode(); + + const auto &axisList = d->coordinateSystem()->axisList(); + axisList[0]->unit()._exportToWKT(formatter); + d->coordinateSystem()->_exportToWKT(formatter); + derivingConversionRef()->addWKTExtensionNode(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); + return; + } + + formatter->startNode(isWKT2 ? io::WKTConstants::PROJCRS + : io::WKTConstants::PROJCS, + !identifiers().empty()); + auto l_name = nameStr(); + if (formatter->useESRIDialect()) { + bool aliasFound = false; + const auto &dbContext = formatter->databaseContext(); + if (dbContext) { + auto l_alias = dbContext->getAliasFromOfficialName( + l_name, "projected_crs", "ESRI"); + if (!l_alias.empty()) { + l_name = l_alias; + aliasFound = true; + } + } + if (!aliasFound) { + l_name = io::WKTFormatter::morphNameToESRI(l_name); + } + } + if (!isWKT2 && !formatter->useESRIDialect() && isDeprecated()) { + l_name += " (deprecated)"; + } + formatter->addQuotedString(l_name); + + const auto &l_baseCRS = d->baseCRS(); + const auto &geodeticCRSAxisList = l_baseCRS->coordinateSystem()->axisList(); + + if (isWKT2) { + formatter->startNode( + (formatter->use2018Keywords() && + dynamic_cast<const GeographicCRS *>(l_baseCRS.get())) + ? io::WKTConstants::BASEGEOGCRS + : io::WKTConstants::BASEGEODCRS, + !l_baseCRS->identifiers().empty()); + formatter->addQuotedString(l_baseCRS->nameStr()); + l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter); + // insert ellipsoidal cs unit when the units of the map + // projection angular parameters are not explicitly given within those + // parameters. See + // http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#61 + if (formatter->primeMeridianOrParameterUnitOmittedIfSameAsAxis()) { + geodeticCRSAxisList[0]->unit()._exportToWKT(formatter); + } + l_baseCRS->primeMeridian()->_exportToWKT(formatter); + formatter->endNode(); + } else { + l_baseCRS->_exportToWKT(formatter); + } + + const auto &axisList = d->coordinateSystem()->axisList(); + formatter->pushAxisLinearUnit( + common::UnitOfMeasure::create(axisList[0]->unit())); + + formatter->pushAxisAngularUnit( + common::UnitOfMeasure::create(geodeticCRSAxisList[0]->unit())); + + derivingConversionRef()->_exportToWKT(formatter); + + formatter->popAxisAngularUnit(); + + formatter->popAxisLinearUnit(); + + if (!isWKT2) { + axisList[0]->unit()._exportToWKT(formatter); + } + + d->coordinateSystem()->_exportToWKT(formatter); + + if (!isWKT2 && !formatter->useESRIDialect()) { + derivingConversionRef()->addWKTExtensionNode(formatter); + } + + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); + return; +} +//! @endcond + +// --------------------------------------------------------------------------- + +void ProjectedCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS from a base CRS, a deriving + * operation::Conversion + * and a coordinate system. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn The base CRS, a GeodeticCRS that is generally a + * GeographicCRS. + * @param derivingConversionIn The deriving operation::Conversion (typically + * using a map + * projection method) + * @param csIn The coordniate system. + * @return new ProjectedCRS. + */ +ProjectedCRSNNPtr +ProjectedCRS::create(const util::PropertyMap &properties, + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CartesianCSNNPtr &csIn) { + auto crs = ProjectedCRS::nn_make_shared<ProjectedCRS>( + baseCRSIn, derivingConversionIn, csIn); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +bool ProjectedCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherProjCRS = dynamic_cast<const ProjectedCRS *>(other); + return otherProjCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ProjectedCRS::addUnitConvertAndAxisSwap(io::PROJStringFormatter *formatter, + bool axisSpecFound) const { + const auto &axisList = d->coordinateSystem()->axisList(); + const auto &unit = axisList[0]->unit(); + if (unit != common::UnitOfMeasure::METRE) { + auto projUnit = unit.exportToPROJString(); + const double toSI = unit.conversionToSI(); + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + formatter->addStep("unitconvert"); + formatter->addParam("xy_in", "m"); + formatter->addParam("z_in", "m"); + if (projUnit.empty()) { + formatter->addParam("xy_out", toSI); + formatter->addParam("z_out", toSI); + } else { + formatter->addParam("xy_out", projUnit); + formatter->addParam("z_out", projUnit); + } + } else { + if (projUnit.empty()) { + formatter->addParam("to_meter", toSI); + } else { + formatter->addParam("units", projUnit); + } + } + } + + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5 && + !axisSpecFound) { + const auto &dir0 = axisList[0]->direction(); + const auto &dir1 = axisList[1]->direction(); + if (!(&dir0 == &cs::AxisDirection::EAST && + &dir1 == &cs::AxisDirection::NORTH) && + // For polar projections, that have south+south direction, + // we don't want to mess with axes. + dir0 != dir1) { + + const char *order[2] = {nullptr, nullptr}; + for (int i = 0; i < 2; i++) { + const auto &dir = axisList[i]->direction(); + if (&dir == &cs::AxisDirection::WEST) + order[i] = "-1"; + else if (&dir == &cs::AxisDirection::EAST) + order[i] = "1"; + else if (&dir == &cs::AxisDirection::SOUTH) + order[i] = "-2"; + else if (&dir == &cs::AxisDirection::NORTH) + order[i] = "2"; + } + + if (order[0] && order[1]) { + formatter->addStep("axisswap"); + char orderStr[10]; + strcpy(orderStr, order[0]); + strcat(orderStr, ","); + strcat(orderStr, order[1]); + formatter->addParam("order", orderStr); + } + } else { + const auto &name0 = axisList[0]->nameStr(); + const auto &name1 = axisList[1]->nameStr(); + const bool northingEasting = ci_starts_with(name0, "northing") && + ci_starts_with(name1, "easting"); + // case of EPSG:32661 ["WGS 84 / UPS North (N,E)]" + // case of EPSG:32761 ["WGS 84 / UPS South (N,E)]" + if (((&dir0 == &cs::AxisDirection::SOUTH && + &dir1 == &cs::AxisDirection::SOUTH) || + (&dir0 == &cs::AxisDirection::NORTH && + &dir1 == &cs::AxisDirection::NORTH)) && + northingEasting) { + formatter->addStep("axisswap"); + formatter->addParam("order", "2,1"); + } + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are either hard-coded, or looked in the database when + * authorityFactory is not null. + * + * 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 + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent (equivalent base CRS, conversion and + * coordinate system), but the names do not match at all. + * 50% means that CRS have similarity (equivalent base CRS and conversion), + * but the coordinate system do not match (e.g. different axis ordering or + * axis unit). + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * + * For the purpose of this function, equivalence is tested with the + * util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, that is + * to say that the axis order of the base GeographicCRS is ignored. + * + * @param authorityFactory Authority factory (or null, but degraded + * functionality) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list<std::pair<ProjectedCRSNNPtr, int>> +ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<ProjectedCRSNNPtr, int> Pair; + std::list<Pair> res; + + const auto &thisName(nameStr()); + + std::list<std::pair<GeodeticCRSNNPtr, int>> baseRes; + const auto &l_baseCRS(baseCRS()); + auto geogCRS = dynamic_cast<const GeographicCRS *>(l_baseCRS.get()); + if (geogCRS && + geogCRS->coordinateSystem()->axisOrder() == + cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH) { + baseRes = + GeographicCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + geogCRS->nameStr()), + geogCRS->datum(), geogCRS->datumEnsemble(), + cs::EllipsoidalCS::createLatitudeLongitude( + geogCRS->coordinateSystem()->axisList()[0]->unit())) + ->identify(authorityFactory); + } else { + baseRes = l_baseCRS->identify(authorityFactory); + } + + int zone = 0; + bool north = false; + + auto computeConfidence = [&thisName](const std::string &crsName) { + return crsName == thisName ? 100 + : metadata::Identifier::isEquivalentName( + crsName.c_str(), thisName.c_str()) + ? 90 + : 70; + }; + auto computeUTMCRSName = [](const char *base, int l_zone, bool l_north) { + return base + toString(l_zone) + (l_north ? "N" : "S"); + }; + + const auto &conv = derivingConversionRef(); + const auto &cs = coordinateSystem(); + if (baseRes.size() == 1 && baseRes.front().second >= 70 && + conv->isUTM(zone, north) && + cs->_isEquivalentTo( + cs::CartesianCS::createEastingNorthing(common::UnitOfMeasure::METRE) + .get())) { + if (baseRes.front().first->_isEquivalentTo( + GeographicCRS::EPSG_4326.get(), + util::IComparable::Criterion::EQUIVALENT)) { + std::string crsName( + computeUTMCRSName("WGS 84 / UTM zone ", zone, north)); + res.emplace_back( + ProjectedCRS::create( + createMapNameEPSGCode(crsName.c_str(), + (north ? 32600 : 32700) + zone), + GeographicCRS::EPSG_4326, conv->identify(), cs), + computeConfidence(crsName)); + return res; + } else if (((zone >= 1 && zone <= 22) || zone == 59 || zone == 60) && + north && + baseRes.front().first->_isEquivalentTo( + GeographicCRS::EPSG_4267.get(), + util::IComparable::Criterion::EQUIVALENT)) { + std::string crsName( + computeUTMCRSName("NAD27 / UTM zone ", zone, north)); + res.emplace_back( + ProjectedCRS::create( + createMapNameEPSGCode(crsName.c_str(), + (zone >= 59) ? 3370 + zone - 59 + : 26700 + zone), + GeographicCRS::EPSG_4267, conv->identify(), cs), + computeConfidence(crsName)); + return res; + } else if (((zone >= 1 && zone <= 23) || zone == 59 || zone == 60) && + north && + baseRes.front().first->_isEquivalentTo( + GeographicCRS::EPSG_4269.get(), + util::IComparable::Criterion::EQUIVALENT)) { + std::string crsName( + computeUTMCRSName("NAD83 / UTM zone ", zone, north)); + res.emplace_back( + ProjectedCRS::create( + createMapNameEPSGCode(crsName.c_str(), + (zone >= 59) ? 3372 + zone - 59 + : 26900 + zone), + GeographicCRS::EPSG_4269, conv->identify(), cs), + computeConfidence(crsName)); + return res; + } + } + + if (authorityFactory) { + + const bool unsignificantName = thisName.empty() || + ci_equal(thisName, "unknown") || + ci_equal(thisName, "unnamed"); + bool foundEquivalentName = false; + + if (!identifiers().empty()) { + // If the CRS has already an id, check in the database for the + // official object, and verify that they are equivalent. + for (const auto &id : identifiers()) { + try { + auto crs = io::AuthorityFactory::create( + authorityFactory->databaseContext(), + *id->codeSpace()) + ->createProjectedCRS(id->code()); + bool match = _isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT); + res.emplace_back(crs, match ? 100 : 25); + return res; + } catch (const std::exception &) { + } + } + } else if (!unsignificantName) { + for (int ipass = 0; ipass < 2; ipass++) { + const bool approximateMatch = ipass == 1; + auto objects = authorityFactory->createObjectsFromName( + thisName, {io::AuthorityFactory::ObjectType::PROJECTED_CRS}, + approximateMatch); + for (const auto &obj : objects) { + auto crs = util::nn_dynamic_pointer_cast<ProjectedCRS>(obj); + assert(crs); + auto crsNN = NN_NO_CHECK(crs); + const bool eqName = metadata::Identifier::isEquivalentName( + thisName.c_str(), crs->nameStr().c_str()); + foundEquivalentName |= eqName; + if (_isEquivalentTo( + crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (crs->nameStr() == thisName) { + res.clear(); + res.emplace_back(crsNN, 100); + return res; + } + res.emplace_back(crsNN, eqName ? 90 : 70); + } else { + res.emplace_back(crsNN, 25); + } + } + if (!res.empty()) { + break; + } + } + } + + const auto lambdaSort = [&thisName](const Pair &a, const Pair &b) { + // First consider confidence + if (a.second > b.second) { + return true; + } + if (a.second < b.second) { + return false; + } + + // Then consider exact name matching + const auto &aName(a.first->nameStr()); + const auto &bName(b.first->nameStr()); + if (aName == thisName && bName != thisName) { + return true; + } + if (bName == thisName && aName != thisName) { + return false; + } + + // Arbitrary final sorting criterion + return aName < bName; + }; + + // Sort results + res.sort(lambdaSort); + + if (identifiers().empty() && !foundEquivalentName && + (res.empty() || res.front().second < 50)) { + std::set<std::pair<std::string, std::string>> alreadyKnown; + for (const auto &pair : res) { + const auto &ids = pair.first->identifiers(); + assert(!ids.empty()); + alreadyKnown.insert(std::pair<std::string, std::string>( + *(ids[0]->codeSpace()), ids[0]->code())); + } + + auto self = NN_NO_CHECK(std::dynamic_pointer_cast<ProjectedCRS>( + shared_from_this().as_nullable())); + auto candidates = + authorityFactory->createProjectedCRSFromExisting(self); + const auto &ellipsoid = l_baseCRS->ellipsoid(); + for (const auto &crs : candidates) { + const auto &ids = crs->identifiers(); + assert(!ids.empty()); + if (alreadyKnown.find(std::pair<std::string, std::string>( + *(ids[0]->codeSpace()), ids[0]->code())) != + alreadyKnown.end()) { + continue; + } + + if (_isEquivalentTo(crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(crs, unsignificantName ? 90 : 70); + } else if (ellipsoid->_isEquivalentTo( + crs->baseCRS()->ellipsoid().get(), + util::IComparable::Criterion::EQUIVALENT) && + derivingConversionRef()->_isEquivalentTo( + crs->derivingConversionRef().get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (coordinateSystem()->_isEquivalentTo( + crs->coordinateSystem().get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(crs, 70); + } else { + res.emplace_back(crs, 50); + } + } else { + res.emplace_back(crs, 25); + } + } + + res.sort(lambdaSort); + } + + // Keep only results of the highest confidence + if (res.size() >= 2) { + const auto highestConfidence = res.front().second; + std::list<Pair> newRes; + for (const auto &pair : res) { + if (pair.second == highestConfidence) { + newRes.push_back(pair); + } else { + break; + } + } + return newRes; + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +ProjectedCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<CRSNNPtr, int> Pair; + std::list<Pair> res; + auto resTemp = identify(authorityFactory); + for (const auto &pair : resTemp) { + res.emplace_back(pair.first, pair.second); + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CompoundCRS::Private { + std::vector<CRSNNPtr> components_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +CompoundCRS::CompoundCRS(const std::vector<CRSNNPtr> &components) + : CRS(), d(internal::make_unique<Private>()) { + d->components_ = components; +} + +// --------------------------------------------------------------------------- + +CompoundCRS::CompoundCRS(const CompoundCRS &other) + : CRS(other), d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CompoundCRS::~CompoundCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr CompoundCRS::_shallowClone() const { + auto crs(CompoundCRS::nn_make_shared<CompoundCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the components of a CompoundCRS. + * + * @return the components. + */ +const std::vector<CRSNNPtr> & +CompoundCRS::componentReferenceSystems() PROJ_CONST_DEFN { + return d->components_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CompoundCRS from a vector of CRS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param components the component CRS of the CompoundCRS. + * @return new CompoundCRS. + */ +CompoundCRSNNPtr CompoundCRS::create(const util::PropertyMap &properties, + const std::vector<CRSNNPtr> &components) { + auto compoundCRS(CompoundCRS::nn_make_shared<CompoundCRS>(components)); + compoundCRS->assignSelf(compoundCRS); + compoundCRS->setProperties(properties); + if (properties.find(common::IdentifiedObject::NAME_KEY) == + properties.end()) { + std::string name; + for (const auto &crs : components) { + if (!name.empty()) { + name += " + "; + } + const auto &l_name = crs->nameStr(); + if (!l_name.empty()) { + name += l_name; + } else { + name += "unnamed"; + } + } + util::PropertyMap propertyName; + propertyName.set(common::IdentifiedObject::NAME_KEY, name); + compoundCRS->setProperties(propertyName); + } + + return compoundCRS; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void CompoundCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::COMPOUNDCRS + : io::WKTConstants::COMPD_CS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + for (const auto &crs : componentReferenceSystems()) { + crs->_exportToWKT(formatter); + } + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void CompoundCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + for (const auto &crs : componentReferenceSystems()) { + auto crs_exportable = + dynamic_cast<const IPROJStringExportable *>(crs.get()); + if (crs_exportable) { + crs_exportable->_exportToPROJString(formatter); + } + } +} + +// --------------------------------------------------------------------------- + +bool CompoundCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherCompoundCRS = dynamic_cast<const CompoundCRS *>(other); + if (otherCompoundCRS == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !ObjectUsage::_isEquivalentTo(other, criterion))) { + return false; + } + const auto &components = componentReferenceSystems(); + const auto &otherComponents = otherCompoundCRS->componentReferenceSystems(); + if (components.size() != otherComponents.size()) { + return false; + } + for (size_t i = 0; i < components.size(); i++) { + if (!components[i]->_isEquivalentTo(otherComponents[i].get(), + criterion)) { + return false; + } + } + return true; +} + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are looked in the database when + * authorityFactory is not null. + * + * 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 + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent (equivalent horizontal and vertical CRS), + * but the names do not match at all. + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * + * @param authorityFactory Authority factory (if null, will return an empty + * list) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list<std::pair<CompoundCRSNNPtr, int>> +CompoundCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<CompoundCRSNNPtr, int> Pair; + std::list<Pair> res; + + const auto &thisName(nameStr()); + + if (authorityFactory) { + + const bool unsignificantName = thisName.empty() || + ci_equal(thisName, "unknown") || + ci_equal(thisName, "unnamed"); + bool foundEquivalentName = false; + + if (!identifiers().empty()) { + // If the CRS has already an id, check in the database for the + // official object, and verify that they are equivalent. + for (const auto &id : identifiers()) { + try { + auto crs = io::AuthorityFactory::create( + authorityFactory->databaseContext(), + *id->codeSpace()) + ->createCompoundCRS(id->code()); + bool match = _isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT); + res.emplace_back(crs, match ? 100 : 25); + return res; + } catch (const std::exception &) { + } + } + } else if (!unsignificantName) { + for (int ipass = 0; ipass < 2; ipass++) { + const bool approximateMatch = ipass == 1; + auto objects = authorityFactory->createObjectsFromName( + thisName, {io::AuthorityFactory::ObjectType::COMPOUND_CRS}, + approximateMatch); + for (const auto &obj : objects) { + auto crs = util::nn_dynamic_pointer_cast<CompoundCRS>(obj); + assert(crs); + auto crsNN = NN_NO_CHECK(crs); + const bool eqName = metadata::Identifier::isEquivalentName( + thisName.c_str(), crs->nameStr().c_str()); + foundEquivalentName |= eqName; + if (_isEquivalentTo( + crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (crs->nameStr() == thisName) { + res.clear(); + res.emplace_back(crsNN, 100); + return res; + } + res.emplace_back(crsNN, eqName ? 90 : 70); + } else { + res.emplace_back(crsNN, 25); + } + } + if (!res.empty()) { + break; + } + } + } + + const auto lambdaSort = [&thisName](const Pair &a, const Pair &b) { + // First consider confidence + if (a.second > b.second) { + return true; + } + if (a.second < b.second) { + return false; + } + + // Then consider exact name matching + const auto &aName(a.first->nameStr()); + const auto &bName(b.first->nameStr()); + if (aName == thisName && bName != thisName) { + return true; + } + if (bName == thisName && aName != thisName) { + return false; + } + + // Arbitrary final sorting criterion + return aName < bName; + }; + + // Sort results + res.sort(lambdaSort); + + if (identifiers().empty() && !foundEquivalentName && + (res.empty() || res.front().second < 50)) { + std::set<std::pair<std::string, std::string>> alreadyKnown; + for (const auto &pair : res) { + const auto &ids = pair.first->identifiers(); + assert(!ids.empty()); + alreadyKnown.insert(std::pair<std::string, std::string>( + *(ids[0]->codeSpace()), ids[0]->code())); + } + + auto self = NN_NO_CHECK(std::dynamic_pointer_cast<CompoundCRS>( + shared_from_this().as_nullable())); + auto candidates = + authorityFactory->createCompoundCRSFromExisting(self); + for (const auto &crs : candidates) { + const auto &ids = crs->identifiers(); + assert(!ids.empty()); + if (alreadyKnown.find(std::pair<std::string, std::string>( + *(ids[0]->codeSpace()), ids[0]->code())) != + alreadyKnown.end()) { + continue; + } + + if (_isEquivalentTo(crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(crs, unsignificantName ? 90 : 70); + } else { + res.emplace_back(crs, 25); + } + } + + res.sort(lambdaSort); + } + + // Keep only results of the highest confidence + if (res.size() >= 2) { + const auto highestConfidence = res.front().second; + std::list<Pair> newRes; + for (const auto &pair : res) { + if (pair.second == highestConfidence) { + newRes.push_back(pair); + } else { + break; + } + } + return newRes; + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +CompoundCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<CRSNNPtr, int> Pair; + std::list<Pair> res; + auto resTemp = identify(authorityFactory); + for (const auto &pair : resTemp) { + res.emplace_back(pair.first, pair.second); + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct PROJ_INTERNAL BoundCRS::Private { + CRSNNPtr baseCRS_; + CRSNNPtr hubCRS_; + operation::TransformationNNPtr transformation_; + + Private(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, + const operation::TransformationNNPtr &transformationIn); + + inline const CRSNNPtr &baseCRS() const { return baseCRS_; } + inline const CRSNNPtr &hubCRS() const { return hubCRS_; } + inline const operation::TransformationNNPtr &transformation() const { + return transformation_; + } +}; + +BoundCRS::Private::Private( + const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, + const operation::TransformationNNPtr &transformationIn) + : baseCRS_(baseCRSIn), hubCRS_(hubCRSIn), + transformation_(transformationIn) {} + +//! @endcond + +// --------------------------------------------------------------------------- + +BoundCRS::BoundCRS(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, + const operation::TransformationNNPtr &transformationIn) + : d(internal::make_unique<Private>(baseCRSIn, hubCRSIn, transformationIn)) { +} + +// --------------------------------------------------------------------------- + +BoundCRS::BoundCRS(const BoundCRS &other) + : CRS(other), + d(internal::make_unique<Private>(other.d->baseCRS(), other.d->hubCRS(), + other.d->transformation())) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +BoundCRS::~BoundCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +BoundCRSNNPtr BoundCRS::shallowCloneAsBoundCRS() const { + auto crs(BoundCRS::nn_make_shared<BoundCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +CRSNNPtr BoundCRS::_shallowClone() const { return shallowCloneAsBoundCRS(); } + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS. + * + * This is the CRS into which coordinates of the BoundCRS are expressed. + * + * @return the base CRS. + */ +const CRSNNPtr &BoundCRS::baseCRS() PROJ_CONST_DEFN { return d->baseCRS_; } + +// --------------------------------------------------------------------------- + +// The only legit caller is BoundCRS::baseCRSWithCanonicalBoundCRS() +void CRS::setCanonicalBoundCRS(const BoundCRSNNPtr &boundCRS) { + + d->canonicalBoundCRS_ = boundCRS; +} + +// --------------------------------------------------------------------------- + +/** \brief Return a shallow clone of the base CRS that points to a + * shallow clone of this BoundCRS. + * + * The base CRS is the CRS into which coordinates of the BoundCRS are expressed. + * + * The returned CRS will actually be a shallow clone of the actual base CRS, + * with the extra property that CRS::canonicalBoundCRS() will point to a + * shallow clone of this BoundCRS. Use this only if you want to work with + * the base CRS object rather than the BoundCRS, but wanting to be able to + * retrieve the BoundCRS later. + * + * @return the base CRS. + */ +CRSNNPtr BoundCRS::baseCRSWithCanonicalBoundCRS() const { + auto baseCRSClone = baseCRS()->_shallowClone(); + baseCRSClone->setCanonicalBoundCRS(shallowCloneAsBoundCRS()); + return baseCRSClone; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the target / hub CRS. + * + * @return the hub CRS. + */ +const CRSNNPtr &BoundCRS::hubCRS() PROJ_CONST_DEFN { return d->hubCRS_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the transformation to the hub RS. + * + * @return transformation. + */ +const operation::TransformationNNPtr & +BoundCRS::transformation() PROJ_CONST_DEFN { + return d->transformation_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a BoundCRS from a base CRS, a hub CRS and a + * transformation. + * + * @param baseCRSIn base CRS. + * @param hubCRSIn hub CRS. + * @param transformationIn transformation from base CRS to hub CRS. + * @return new BoundCRS. + */ +BoundCRSNNPtr +BoundCRS::create(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, + const operation::TransformationNNPtr &transformationIn) { + auto crs = BoundCRS::nn_make_shared<BoundCRS>(baseCRSIn, hubCRSIn, + transformationIn); + crs->assignSelf(crs); + const auto &l_name = baseCRSIn->nameStr(); + if (!l_name.empty()) { + crs->setProperties(util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, l_name)); + } + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a BoundCRS from a base CRS and TOWGS84 parameters + * + * @param baseCRSIn base CRS. + * @param TOWGS84Parameters a vector of 3 or 7 double values representing WKT1 + * TOWGS84 parameter. + * @return new BoundCRS. + */ +BoundCRSNNPtr +BoundCRS::createFromTOWGS84(const CRSNNPtr &baseCRSIn, + const std::vector<double> &TOWGS84Parameters) { + return create( + baseCRSIn, GeographicCRS::EPSG_4326, + operation::Transformation::createTOWGS84(baseCRSIn, TOWGS84Parameters)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a BoundCRS from a base CRS and nadgrids parameters + * + * @param baseCRSIn base CRS. + * @param filename Horizontal grid filename + * @return new BoundCRS. + */ +BoundCRSNNPtr BoundCRS::createFromNadgrids(const CRSNNPtr &baseCRSIn, + const std::string &filename) { + const CRSPtr sourceGeographicCRS = baseCRSIn->extractGeographicCRS(); + auto transformationSourceCRS = + sourceGeographicCRS ? sourceGeographicCRS : baseCRSIn.as_nullable(); + std::string transformationName = transformationSourceCRS->nameStr(); + transformationName += " to WGS84"; + + return create( + baseCRSIn, GeographicCRS::EPSG_4326, + operation::Transformation::createNTv2( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + transformationName), + baseCRSIn, GeographicCRS::EPSG_4326, filename, + std::vector<metadata::PositionalAccuracyNNPtr>())); +} + +// --------------------------------------------------------------------------- + +bool BoundCRS::isTOWGS84Compatible() const { + return dynamic_cast<GeodeticCRS *>(d->hubCRS().get()) != nullptr && + ci_equal(d->hubCRS()->nameStr(), "WGS 84"); +} + +// --------------------------------------------------------------------------- + +std::string BoundCRS::getHDatumPROJ4GRIDS() const { + if (ci_equal(d->hubCRS()->nameStr(), "WGS 84")) { + return d->transformation()->getNTv2Filename(); + } + return std::string(); +} + +// --------------------------------------------------------------------------- + +std::string BoundCRS::getVDatumPROJ4GRIDS() const { + if (dynamic_cast<VerticalCRS *>(d->baseCRS().get()) && + ci_equal(d->hubCRS()->nameStr(), "WGS 84")) { + return d->transformation()->getHeightToGeographic3DFilename(); + } + return std::string(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void BoundCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (isWKT2) { + formatter->startNode(io::WKTConstants::BOUNDCRS, false); + formatter->startNode(io::WKTConstants::SOURCECRS, false); + d->baseCRS()->_exportToWKT(formatter); + formatter->endNode(); + formatter->startNode(io::WKTConstants::TARGETCRS, false); + d->hubCRS()->_exportToWKT(formatter); + formatter->endNode(); + formatter->setAbridgedTransformation(true); + d->transformation()->_exportToWKT(formatter); + formatter->setAbridgedTransformation(false); + formatter->endNode(); + } else { + + auto vdatumProj4GridName = getVDatumPROJ4GRIDS(); + if (!vdatumProj4GridName.empty()) { + formatter->setVDatumExtension(vdatumProj4GridName); + d->baseCRS()->_exportToWKT(formatter); + formatter->setVDatumExtension(std::string()); + return; + } + + auto hdatumProj4GridName = getHDatumPROJ4GRIDS(); + if (!hdatumProj4GridName.empty()) { + formatter->setHDatumExtension(hdatumProj4GridName); + d->baseCRS()->_exportToWKT(formatter); + formatter->setHDatumExtension(std::string()); + return; + } + + if (!isTOWGS84Compatible()) { + io::FormattingException::Throw( + "Cannot export BoundCRS with non-WGS 84 hub CRS in WKT1"); + } + auto params = d->transformation()->getTOWGS84Parameters(); + if (!formatter->useESRIDialect()) { + formatter->setTOWGS84Parameters(params); + } + d->baseCRS()->_exportToWKT(formatter); + formatter->setTOWGS84Parameters(std::vector<double>()); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +void BoundCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + io::FormattingException::Throw( + "BoundCRS cannot be exported as a PROJ.5 string, but its baseCRS " + "might"); + } + + auto crs_exportable = + dynamic_cast<const io::IPROJStringExportable *>(d->baseCRS_.get()); + if (!crs_exportable) { + io::FormattingException::Throw( + "baseCRS of BoundCRS cannot be exported as a PROJ string"); + } + + auto vdatumProj4GridName = getVDatumPROJ4GRIDS(); + if (!vdatumProj4GridName.empty()) { + formatter->setVDatumExtension(vdatumProj4GridName); + crs_exportable->_exportToPROJString(formatter); + formatter->setVDatumExtension(std::string()); + } else { + auto hdatumProj4GridName = getHDatumPROJ4GRIDS(); + if (!hdatumProj4GridName.empty()) { + formatter->setHDatumExtension(hdatumProj4GridName); + crs_exportable->_exportToPROJString(formatter); + formatter->setHDatumExtension(std::string()); + } else { + if (isTOWGS84Compatible()) { + auto params = transformation()->getTOWGS84Parameters(); + formatter->setTOWGS84Parameters(params); + } + crs_exportable->_exportToPROJString(formatter); + formatter->setTOWGS84Parameters(std::vector<double>()); + } + } +} + +// --------------------------------------------------------------------------- + +bool BoundCRS::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherBoundCRS = dynamic_cast<const BoundCRS *>(other); + if (otherBoundCRS == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !ObjectUsage::_isEquivalentTo(other, criterion))) { + return false; + } + return d->baseCRS_->_isEquivalentTo(otherBoundCRS->d->baseCRS_.get(), + criterion) && + d->hubCRS_->_isEquivalentTo(otherBoundCRS->d->hubCRS_.get(), + criterion) && + d->transformation_->_isEquivalentTo( + otherBoundCRS->d->transformation_.get(), criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +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)) { + auto resTemp = d->baseCRS_->identify(authorityFactory); + for (const auto &pair : resTemp) { + const auto &candidateBaseCRS = pair.first; + auto projCRS = + dynamic_cast<const ProjectedCRS *>(candidateBaseCRS.get()); + auto geodCRS = projCRS ? projCRS->baseCRS().as_nullable() + : util::nn_dynamic_pointer_cast<GeodeticCRS>( + candidateBaseCRS); + if (geodCRS) { + auto context = operation::CoordinateOperationContext::create( + authorityFactory, nullptr, 0.0); + 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 &) { + } + for (const auto &op : ops) { + std::string opTransfPROJString; + bool opTransfPROJStringValid = false; + try { + opTransfPROJString = op->exportToPROJString( + io::PROJStringFormatter::create().get()); + opTransfPROJStringValid = true; + } catch (const std::exception &) { + } + if ((refTransfPROJStringValid && opTransfPROJStringValid && + refTransfPROJString == opTransfPROJString) || + op->_isEquivalentTo( + d->transformation_.get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back( + create(candidateBaseCRS, d->hubCRS_, + NN_NO_CHECK(util::nn_dynamic_pointer_cast< + operation::Transformation>(op))), + pair.second); + break; + } + } + } + } + } + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedGeodeticCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedGeodeticCRS::~DerivedGeodeticCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DerivedGeodeticCRS::DerivedGeodeticCRS( + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CartesianCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + GeodeticCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedGeodeticCRS::DerivedGeodeticCRS( + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::SphericalCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + GeodeticCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedGeodeticCRS::DerivedGeodeticCRS(const DerivedGeodeticCRS &other) + : SingleCRS(other), GeodeticCRS(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr DerivedGeodeticCRS::_shallowClone() const { + auto crs(DerivedGeodeticCRS::nn_make_shared<DerivedGeodeticCRS>(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a GeodeticCRS) of a DerivedGeodeticCRS. + * + * @return the base CRS. + */ +const GeodeticCRSNNPtr DerivedGeodeticCRS::baseCRS() const { + return NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>( + DerivedCRS::getPrivate()->baseCRS_)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedGeodeticCRS from a base CRS, a deriving + * conversion and a cs::CartesianCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedGeodeticCRS. + */ +DerivedGeodeticCRSNNPtr DerivedGeodeticCRS::create( + const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CartesianCSNNPtr &csIn) { + auto crs(DerivedGeodeticCRS::nn_make_shared<DerivedGeodeticCRS>( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedGeodeticCRS from a base CRS, a deriving + * conversion and a cs::SphericalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedGeodeticCRS. + */ +DerivedGeodeticCRSNNPtr DerivedGeodeticCRS::create( + const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::SphericalCSNNPtr &csIn) { + auto crs(DerivedGeodeticCRS::nn_make_shared<DerivedGeodeticCRS>( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DerivedGeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "DerivedGeodeticCRS can only be exported to WKT2"); + } + formatter->startNode(io::WKTConstants::GEODCRS, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + auto l_baseCRS = baseCRS(); + formatter->startNode((formatter->use2018Keywords() && + dynamic_cast<const GeographicCRS *>(l_baseCRS.get())) + ? io::WKTConstants::BASEGEOGCRS + : io::WKTConstants::BASEGEODCRS, + !baseCRS()->identifiers().empty()); + formatter->addQuotedString(l_baseCRS->nameStr()); + auto l_datum = l_baseCRS->datum(); + if (l_datum) { + l_datum->_exportToWKT(formatter); + } else { + auto l_datumEnsemble = datumEnsemble(); + assert(l_datumEnsemble); + l_datumEnsemble->_exportToWKT(formatter); + } + l_baseCRS->primeMeridian()->_exportToWKT(formatter); + formatter->endNode(); + + formatter->setUseDerivingConversion(true); + derivingConversionRef()->_exportToWKT(formatter); + formatter->setUseDerivingConversion(false); + + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void DerivedGeodeticCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +bool DerivedGeodeticCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast<const DerivedGeodeticCRS *>(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +DerivedGeodeticCRS::_identify(const io::AuthorityFactoryPtr &factory) const { + return CRS::_identify(factory); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedGeographicCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedGeographicCRS::~DerivedGeographicCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DerivedGeographicCRS::DerivedGeographicCRS( + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::EllipsoidalCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + GeographicCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedGeographicCRS::DerivedGeographicCRS(const DerivedGeographicCRS &other) + : SingleCRS(other), GeographicCRS(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr DerivedGeographicCRS::_shallowClone() const { + auto crs(DerivedGeographicCRS::nn_make_shared<DerivedGeographicCRS>(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a GeodeticCRS) of a DerivedGeographicCRS. + * + * @return the base CRS. + */ +const GeodeticCRSNNPtr DerivedGeographicCRS::baseCRS() const { + return NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>( + DerivedCRS::getPrivate()->baseCRS_)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedGeographicCRS from a base CRS, a deriving + * conversion and a cs::EllipsoidalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedGeographicCRS. + */ +DerivedGeographicCRSNNPtr DerivedGeographicCRS::create( + const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::EllipsoidalCSNNPtr &csIn) { + auto crs(DerivedGeographicCRS::nn_make_shared<DerivedGeographicCRS>( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DerivedGeographicCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "DerivedGeographicCRS can only be exported to WKT2"); + } + formatter->startNode(formatter->use2018Keywords() + ? io::WKTConstants::GEOGCRS + : io::WKTConstants::GEODCRS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + auto l_baseCRS = baseCRS(); + formatter->startNode((formatter->use2018Keywords() && + dynamic_cast<const GeographicCRS *>(l_baseCRS.get())) + ? io::WKTConstants::BASEGEOGCRS + : io::WKTConstants::BASEGEODCRS, + !l_baseCRS->identifiers().empty()); + formatter->addQuotedString(l_baseCRS->nameStr()); + l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter); + l_baseCRS->primeMeridian()->_exportToWKT(formatter); + formatter->endNode(); + + formatter->setUseDerivingConversion(true); + derivingConversionRef()->_exportToWKT(formatter); + formatter->setUseDerivingConversion(false); + + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void DerivedGeographicCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +bool DerivedGeographicCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast<const DerivedGeographicCRS *>(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +DerivedGeographicCRS::_identify(const io::AuthorityFactoryPtr &factory) const { + return CRS::_identify(factory); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedProjectedCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedProjectedCRS::~DerivedProjectedCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DerivedProjectedCRS::DerivedProjectedCRS( + const ProjectedCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CoordinateSystemNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedProjectedCRS::DerivedProjectedCRS(const DerivedProjectedCRS &other) + : SingleCRS(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr DerivedProjectedCRS::_shallowClone() const { + auto crs(DerivedProjectedCRS::nn_make_shared<DerivedProjectedCRS>(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a ProjectedCRS) of a DerivedProjectedCRS. + * + * @return the base CRS. + */ +const ProjectedCRSNNPtr DerivedProjectedCRS::baseCRS() const { + return NN_NO_CHECK(util::nn_dynamic_pointer_cast<ProjectedCRS>( + DerivedCRS::getPrivate()->baseCRS_)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedProjectedCRS from a base CRS, a deriving + * conversion and a cs::CS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedProjectedCRS. + */ +DerivedProjectedCRSNNPtr DerivedProjectedCRS::create( + const util::PropertyMap &properties, const ProjectedCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CoordinateSystemNNPtr &csIn) { + auto crs(DerivedProjectedCRS::nn_make_shared<DerivedProjectedCRS>( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DerivedProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2 || !formatter->use2018Keywords()) { + io::FormattingException::Throw( + "DerivedProjectedCRS can only be exported to WKT2:2018"); + } + formatter->startNode(io::WKTConstants::DERIVEDPROJCRS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + { + auto l_baseProjCRS = baseCRS(); + formatter->startNode(io::WKTConstants::BASEPROJCRS, + !l_baseProjCRS->identifiers().empty()); + formatter->addQuotedString(l_baseProjCRS->nameStr()); + + auto l_baseGeodCRS = l_baseProjCRS->baseCRS(); + auto &geodeticCRSAxisList = + l_baseGeodCRS->coordinateSystem()->axisList(); + + formatter->startNode( + dynamic_cast<const GeographicCRS *>(l_baseGeodCRS.get()) + ? io::WKTConstants::BASEGEOGCRS + : io::WKTConstants::BASEGEODCRS, + !l_baseGeodCRS->identifiers().empty()); + formatter->addQuotedString(l_baseGeodCRS->nameStr()); + l_baseGeodCRS->exportDatumOrDatumEnsembleToWkt(formatter); + // insert ellipsoidal cs unit when the units of the map + // projection angular parameters are not explicitly given within those + // parameters. See + // http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#61 + if (formatter->primeMeridianOrParameterUnitOmittedIfSameAsAxis() && + !geodeticCRSAxisList.empty()) { + geodeticCRSAxisList[0]->unit()._exportToWKT(formatter); + } + l_baseGeodCRS->primeMeridian()->_exportToWKT(formatter); + formatter->endNode(); + + l_baseProjCRS->derivingConversionRef()->_exportToWKT(formatter); + formatter->endNode(); + } + + formatter->setUseDerivingConversion(true); + derivingConversionRef()->_exportToWKT(formatter); + formatter->setUseDerivingConversion(false); + + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void DerivedProjectedCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +bool DerivedProjectedCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast<const DerivedProjectedCRS *>(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct TemporalCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalCRS::~TemporalCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +TemporalCRS::TemporalCRS(const datum::TemporalDatumNNPtr &datumIn, + const cs::TemporalCSNNPtr &csIn) + : SingleCRS(datumIn.as_nullable(), nullptr, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +TemporalCRS::TemporalCRS(const TemporalCRS &other) + : SingleCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr TemporalCRS::_shallowClone() const { + auto crs(TemporalCRS::nn_make_shared<TemporalCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::TemporalDatum associated with the CRS. + * + * @return a TemporalDatum + */ +const datum::TemporalDatumNNPtr TemporalCRS::datum() const { + return NN_NO_CHECK(std::static_pointer_cast<datum::TemporalDatum>( + SingleCRS::getPrivate()->datum)); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::TemporalCS associated with the CRS. + * + * @return a TemporalCS + */ +const cs::TemporalCSNNPtr TemporalCRS::coordinateSystem() const { + return util::nn_static_pointer_cast<cs::TemporalCS>( + SingleCRS::getPrivate()->coordinateSystem); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a TemporalCRS from a datum and a coordinate system. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn the datum. + * @param csIn the coordinate system. + * @return new TemporalCRS. + */ +TemporalCRSNNPtr TemporalCRS::create(const util::PropertyMap &properties, + const datum::TemporalDatumNNPtr &datumIn, + const cs::TemporalCSNNPtr &csIn) { + auto crs(TemporalCRS::nn_make_shared<TemporalCRS>(datumIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void TemporalCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "TemporalCRS can only be exported to WKT2"); + } + formatter->startNode(io::WKTConstants::TIMECRS, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + datum()->_exportToWKT(formatter); + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool TemporalCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherTemporalCRS = dynamic_cast<const TemporalCRS *>(other); + return otherTemporalCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct EngineeringCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +EngineeringCRS::~EngineeringCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +EngineeringCRS::EngineeringCRS(const datum::EngineeringDatumNNPtr &datumIn, + const cs::CoordinateSystemNNPtr &csIn) + : SingleCRS(datumIn.as_nullable(), nullptr, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +EngineeringCRS::EngineeringCRS(const EngineeringCRS &other) + : SingleCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr EngineeringCRS::_shallowClone() const { + auto crs(EngineeringCRS::nn_make_shared<EngineeringCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::EngineeringDatum associated with the CRS. + * + * @return a EngineeringDatum + */ +const datum::EngineeringDatumNNPtr EngineeringCRS::datum() const { + return NN_NO_CHECK(std::static_pointer_cast<datum::EngineeringDatum>( + SingleCRS::getPrivate()->datum)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EngineeringCRS from a datum and a coordinate system. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn the datum. + * @param csIn the coordinate system. + * @return new EngineeringCRS. + */ +EngineeringCRSNNPtr +EngineeringCRS::create(const util::PropertyMap &properties, + const datum::EngineeringDatumNNPtr &datumIn, + const cs::CoordinateSystemNNPtr &csIn) { + auto crs(EngineeringCRS::nn_make_shared<EngineeringCRS>(datumIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void EngineeringCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::ENGCRS + : io::WKTConstants::LOCAL_CS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + if (isWKT2 || !datum()->nameStr().empty()) { + datum()->_exportToWKT(formatter); + coordinateSystem()->_exportToWKT(formatter); + } + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool EngineeringCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherEngineeringCRS = dynamic_cast<const EngineeringCRS *>(other); + return otherEngineeringCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ParametricCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ParametricCRS::~ParametricCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +ParametricCRS::ParametricCRS(const datum::ParametricDatumNNPtr &datumIn, + const cs::ParametricCSNNPtr &csIn) + : SingleCRS(datumIn.as_nullable(), nullptr, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +ParametricCRS::ParametricCRS(const ParametricCRS &other) + : SingleCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr ParametricCRS::_shallowClone() const { + auto crs(ParametricCRS::nn_make_shared<ParametricCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::ParametricDatum associated with the CRS. + * + * @return a ParametricDatum + */ +const datum::ParametricDatumNNPtr ParametricCRS::datum() const { + return NN_NO_CHECK(std::static_pointer_cast<datum::ParametricDatum>( + SingleCRS::getPrivate()->datum)); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::TemporalCS associated with the CRS. + * + * @return a TemporalCS + */ +const cs::ParametricCSNNPtr ParametricCRS::coordinateSystem() const { + return util::nn_static_pointer_cast<cs::ParametricCS>( + SingleCRS::getPrivate()->coordinateSystem); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ParametricCRS from a datum and a coordinate system. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn the datum. + * @param csIn the coordinate system. + * @return new ParametricCRS. + */ +ParametricCRSNNPtr +ParametricCRS::create(const util::PropertyMap &properties, + const datum::ParametricDatumNNPtr &datumIn, + const cs::ParametricCSNNPtr &csIn) { + auto crs(ParametricCRS::nn_make_shared<ParametricCRS>(datumIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ParametricCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "ParametricCRS can only be exported to WKT2"); + } + formatter->startNode(io::WKTConstants::PARAMETRICCRS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + datum()->_exportToWKT(formatter); + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool ParametricCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherParametricCRS = dynamic_cast<const ParametricCRS *>(other); + return otherParametricCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedVerticalCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedVerticalCRS::~DerivedVerticalCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DerivedVerticalCRS::DerivedVerticalCRS( + const VerticalCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::VerticalCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + VerticalCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedVerticalCRS::DerivedVerticalCRS(const DerivedVerticalCRS &other) + : SingleCRS(other), VerticalCRS(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr DerivedVerticalCRS::_shallowClone() const { + auto crs(DerivedVerticalCRS::nn_make_shared<DerivedVerticalCRS>(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a VerticalCRS) of a DerivedVerticalCRS. + * + * @return the base CRS. + */ +const VerticalCRSNNPtr DerivedVerticalCRS::baseCRS() const { + return NN_NO_CHECK(util::nn_dynamic_pointer_cast<VerticalCRS>( + DerivedCRS::getPrivate()->baseCRS_)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedVerticalCRS from a base CRS, a deriving + * conversion and a cs::VerticalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedVerticalCRS. + */ +DerivedVerticalCRSNNPtr DerivedVerticalCRS::create( + const util::PropertyMap &properties, const VerticalCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::VerticalCSNNPtr &csIn) { + auto crs(DerivedVerticalCRS::nn_make_shared<DerivedVerticalCRS>( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DerivedVerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "DerivedVerticalCRS can only be exported to WKT2"); + } + baseExportToWKT(formatter, io::WKTConstants::VERTCRS, + io::WKTConstants::BASEVERTCRS); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void DerivedVerticalCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +bool DerivedVerticalCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast<const DerivedVerticalCRS *>(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +DerivedVerticalCRS::_identify(const io::AuthorityFactoryPtr &factory) const { + return CRS::_identify(factory); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +template <class DerivedCRSTraits> +struct DerivedCRSTemplate<DerivedCRSTraits>::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +template <class DerivedCRSTraits> +DerivedCRSTemplate<DerivedCRSTraits>::~DerivedCRSTemplate() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +template <class DerivedCRSTraits> +DerivedCRSTemplate<DerivedCRSTraits>::DerivedCRSTemplate( + const BaseNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, const CSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum().as_nullable(), nullptr, csIn), + BaseType(baseCRSIn->datum(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +template <class DerivedCRSTraits> +DerivedCRSTemplate<DerivedCRSTraits>::DerivedCRSTemplate( + const DerivedCRSTemplate &other) + : SingleCRS(other), BaseType(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +template <class DerivedCRSTraits> +const typename DerivedCRSTemplate<DerivedCRSTraits>::BaseNNPtr +DerivedCRSTemplate<DerivedCRSTraits>::baseCRS() const { + auto l_baseCRS = DerivedCRS::getPrivate()->baseCRS_; + return NN_NO_CHECK(util::nn_dynamic_pointer_cast<BaseType>(l_baseCRS)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +template <class DerivedCRSTraits> +CRSNNPtr DerivedCRSTemplate<DerivedCRSTraits>::_shallowClone() const { + auto crs(DerivedCRSTemplate::nn_make_shared<DerivedCRSTemplate>(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +template <class DerivedCRSTraits> +typename DerivedCRSTemplate<DerivedCRSTraits>::NNPtr +DerivedCRSTemplate<DerivedCRSTraits>::create( + const util::PropertyMap &properties, const BaseNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const CSNNPtr &csIn) { + auto crs(DerivedCRSTemplate::nn_make_shared<DerivedCRSTemplate>( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +static void DerivedCRSTemplateCheckExportToWKT(io::WKTFormatter *&formatter, + const std::string &crsName, + bool wkt2_2018_only) { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2 || (wkt2_2018_only && !formatter->use2018Keywords())) { + io::FormattingException::Throw(crsName + + " can only be exported to WKT2" + + (wkt2_2018_only ? ":2018" : "")); + } +} + +// --------------------------------------------------------------------------- + +template <class DerivedCRSTraits> +void DerivedCRSTemplate<DerivedCRSTraits>::_exportToWKT( + io::WKTFormatter *formatter) const { + DerivedCRSTemplateCheckExportToWKT(formatter, DerivedCRSTraits::CRSName(), + DerivedCRSTraits::wkt2_2018_only); + baseExportToWKT(formatter, DerivedCRSTraits::WKTKeyword(), + DerivedCRSTraits::WKTBaseKeyword()); +} + +// --------------------------------------------------------------------------- + +template <class DerivedCRSTraits> +bool DerivedCRSTemplate<DerivedCRSTraits>::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast<const DerivedCRSTemplate *>(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string STRING_DerivedEngineeringCRS("DerivedEngineeringCRS"); +const std::string &DerivedEngineeringCRSTraits::CRSName() { + return STRING_DerivedEngineeringCRS; +} +const std::string &DerivedEngineeringCRSTraits::WKTKeyword() { + return io::WKTConstants::ENGCRS; +} +const std::string &DerivedEngineeringCRSTraits::WKTBaseKeyword() { + return io::WKTConstants::BASEENGCRS; +} + +template class DerivedCRSTemplate<DerivedEngineeringCRSTraits>; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string STRING_DerivedParametricCRS("DerivedParametricCRS"); +const std::string &DerivedParametricCRSTraits::CRSName() { + return STRING_DerivedParametricCRS; +} +const std::string &DerivedParametricCRSTraits::WKTKeyword() { + return io::WKTConstants::PARAMETRICCRS; +} +const std::string &DerivedParametricCRSTraits::WKTBaseKeyword() { + return io::WKTConstants::BASEPARAMCRS; +} + +template class DerivedCRSTemplate<DerivedParametricCRSTraits>; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string STRING_DerivedTemporalCRS("DerivedTemporalCRS"); +const std::string &DerivedTemporalCRSTraits::CRSName() { + return STRING_DerivedTemporalCRS; +} +const std::string &DerivedTemporalCRSTraits::WKTKeyword() { + return io::WKTConstants::TIMECRS; +} +const std::string &DerivedTemporalCRSTraits::WKTBaseKeyword() { + return io::WKTConstants::BASETIMECRS; +} + +template class DerivedCRSTemplate<DerivedTemporalCRSTraits>; +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace crs +NS_PROJ_END diff --git a/src/datum.cpp b/src/datum.cpp new file mode 100644 index 00000000..b3edb152 --- /dev/null +++ b/src/datum.cpp @@ -0,0 +1,1983 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/datum.hpp" +#include "proj/common.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +// PROJ include order is sensitive +// clang-format off +#include "proj.h" +#include "projects.h" +#include "proj_api.h" +// clang-format on + +#include <cmath> +#include <cstdlib> +#include <memory> +#include <string> + +using namespace NS_PROJ::internal; + +#if 0 +namespace dropbox{ namespace oxygen { +template<> nn<NS_PROJ::datum::DatumPtr>::~nn() = default; +template<> nn<NS_PROJ::datum::DatumEnsemblePtr>::~nn() = default; +template<> nn<NS_PROJ::datum::PrimeMeridianPtr>::~nn() = default; +template<> nn<NS_PROJ::datum::EllipsoidPtr>::~nn() = default; +template<> nn<NS_PROJ::datum::GeodeticReferenceFramePtr>::~nn() = default; +template<> nn<NS_PROJ::datum::DynamicGeodeticReferenceFramePtr>::~nn() = default; +template<> nn<NS_PROJ::datum::VerticalReferenceFramePtr>::~nn() = default; +template<> nn<NS_PROJ::datum::DynamicVerticalReferenceFramePtr>::~nn() = default; +template<> nn<NS_PROJ::datum::EngineeringDatumPtr>::~nn() = default; +template<> nn<NS_PROJ::datum::TemporalDatumPtr>::~nn() = default; +template<> nn<NS_PROJ::datum::ParametricDatumPtr>::~nn() = default; +}} +#endif + +NS_PROJ_START +namespace datum { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static util::PropertyMap createMapNameEPSGCode(const char *name, int code) { + return util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, name) + .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, code); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Datum::Private { + util::optional<std::string> anchorDefinition{}; + util::optional<common::DateTime> publicationDate{}; + common::IdentifiedObjectPtr conventionalRS{}; + + // cppcheck-suppress functionStatic + void exportAnchorDefinition(io::WKTFormatter *formatter) const; +}; + +// --------------------------------------------------------------------------- + +void Datum::Private::exportAnchorDefinition(io::WKTFormatter *formatter) const { + if (anchorDefinition) { + formatter->startNode(io::WKTConstants::ANCHOR, false); + formatter->addQuotedString(*anchorDefinition); + formatter->endNode(); + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + +Datum::Datum() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +Datum::Datum(const Datum &other) + : ObjectUsage(other), d(internal::make_unique<Private>(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Datum::~Datum() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the anchor definition. + * + * A description - possibly including coordinates of an identified point or + * points - of the relationship used to anchor a coordinate system to the + * Earth or alternate object. + * <ul> + * <li>For modern geodetic reference frames the anchor may be a set of station + * coordinates; if the reference frame is dynamic it will also include + * coordinate velocities. For a traditional geodetic datum, this anchor may be + * a point known as the fundamental point, which is traditionally the point + * where the relationship between geoid and ellipsoid is defined, together + * with a direction from that point.</li> + * <li>For a vertical reference frame the anchor may be the zero level at one + * or more defined locations or a conventionally defined surface.</li> + * <li>For an engineering datum, the anchor may be an identified physical point + * with the orientation defined relative to the object.</li> + * </ul> + * + * @return the anchor definition, or empty. + */ +const util::optional<std::string> &Datum::anchorDefinition() const { + return d->anchorDefinition; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the date on which the datum definition was published. + * + * \note Departure from \ref ISO_19111_2018 : we return a DateTime instead of + * a Citation::Date. + * + * @return the publication date, or empty. + */ +const util::optional<common::DateTime> &Datum::publicationDate() const { + return d->publicationDate; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the conventional reference system. + * + * This is the name, identifier, alias and remarks for the terrestrial + * reference system or vertical reference system realized by this reference + * frame, for example "ITRS" for ITRF88 through ITRF2008 and ITRF2014, or + * "EVRS" for EVRF2000 and EVRF2007. + * + * @return the conventional reference system, or nullptr. + */ +const common::IdentifiedObjectPtr &Datum::conventionalRS() const { + return d->conventionalRS; +} + +// --------------------------------------------------------------------------- + +void Datum::setAnchor(const util::optional<std::string> &anchor) { + d->anchorDefinition = anchor; +} + +// --------------------------------------------------------------------------- + +bool Datum::__isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDatum = dynamic_cast<const Datum *>(other); + if (otherDatum == nullptr || + !ObjectUsage::_isEquivalentTo(other, criterion)) { + return false; + } + if (criterion == util::IComparable::Criterion::STRICT) { + if ((anchorDefinition().has_value() ^ + otherDatum->anchorDefinition().has_value())) { + return false; + } + if (anchorDefinition().has_value() && + otherDatum->anchorDefinition().has_value() && + *anchorDefinition() != *otherDatum->anchorDefinition()) { + return false; + } + + if ((publicationDate().has_value() ^ + otherDatum->publicationDate().has_value())) { + return false; + } + if (publicationDate().has_value() && + otherDatum->publicationDate().has_value() && + publicationDate()->toString() != + otherDatum->publicationDate()->toString()) { + return false; + } + + if (((conventionalRS() != nullptr) ^ + (otherDatum->conventionalRS() != nullptr))) { + return false; + } + if (conventionalRS() && otherDatum->conventionalRS() && + conventionalRS()->_isEquivalentTo( + otherDatum->conventionalRS().get(), criterion)) { + return false; + } + } + return true; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct PrimeMeridian::Private { + common::Angle longitude_{}; + + explicit Private(const common::Angle &longitude) : longitude_(longitude) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +PrimeMeridian::PrimeMeridian(const common::Angle &longitudeIn) + : d(internal::make_unique<Private>(longitudeIn)) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +PrimeMeridian::PrimeMeridian(const PrimeMeridian &other) + : common::IdentifiedObject(other), + d(internal::make_unique<Private>(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PrimeMeridian::~PrimeMeridian() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the longitude of the prime meridian. + * + * It is measured from the internationally-recognised reference meridian + * ('Greenwich meridian'), positive eastward. + * The default value is 0 degrees. + * + * @return the longitude of the prime meridian. + */ +const common::Angle &PrimeMeridian::longitude() PROJ_CONST_DEFN { + return d->longitude_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a PrimeMeridian. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param longitudeIn the longitude of the prime meridian. + * @return new PrimeMeridian. + */ +PrimeMeridianNNPtr PrimeMeridian::create(const util::PropertyMap &properties, + const common::Angle &longitudeIn) { + auto pm(PrimeMeridian::nn_make_shared<PrimeMeridian>(longitudeIn)); + pm->setProperties(properties); + return pm; +} + +// --------------------------------------------------------------------------- + +const PrimeMeridianNNPtr PrimeMeridian::createGREENWICH() { + return create(createMapNameEPSGCode("Greenwich", 8901), common::Angle(0)); +} + +// --------------------------------------------------------------------------- + +const PrimeMeridianNNPtr PrimeMeridian::createPARIS() { + return create(createMapNameEPSGCode("Paris", 8903), + common::Angle(2.5969213, common::UnitOfMeasure::GRAD)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void PrimeMeridian::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + std::string l_name = + name()->description().has_value() ? nameStr() : "Greenwich"; + if (!(isWKT2 && formatter->primeMeridianOmittedIfGreenwich() && + l_name == "Greenwich")) { + formatter->startNode(io::WKTConstants::PRIMEM, !identifiers().empty()); + formatter->addQuotedString(l_name); + const auto &l_long = longitude(); + if (formatter->primeMeridianInDegree()) { + formatter->add(l_long.convertToUnit(common::UnitOfMeasure::DEGREE)); + } else { + formatter->add(l_long.value()); + } + const auto &unit = l_long.unit(); + if (isWKT2) { + if (!(formatter + ->primeMeridianOrParameterUnitOmittedIfSameAsAxis() && + unit == *(formatter->axisAngularUnit()))) { + unit._exportToWKT(formatter, io::WKTConstants::ANGLEUNIT); + } + } else if (!formatter->primeMeridianInDegree()) { + unit._exportToWKT(formatter); + } + if (formatter->outputId()) { + formatID(formatter); + } + formatter->endNode(); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::string +PrimeMeridian::getPROJStringWellKnownName(const common::Angle &angle) { + const double valRad = angle.getSIValue(); + std::string projPMName; + projCtx ctxt = pj_ctx_alloc(); + auto proj_pm = proj_list_prime_meridians(); + for (int i = 0; proj_pm[i].id != nullptr; ++i) { + double valRefRad = dmstor_ctx(ctxt, proj_pm[i].defn, nullptr); + if (::fabs(valRad - valRefRad) < 1e-10) { + projPMName = proj_pm[i].id; + break; + } + } + pj_ctx_free(ctxt); + return projPMName; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void PrimeMeridian::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(FormattingException) +{ + if (longitude().getSIValue() != 0) { + std::string projPMName(getPROJStringWellKnownName(longitude())); + if (!projPMName.empty()) { + formatter->addParam("pm", projPMName); + } else { + const double valDeg = + longitude().convertToUnit(common::UnitOfMeasure::DEGREE); + formatter->addParam("pm", valDeg); + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool PrimeMeridian::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherPM = dynamic_cast<const PrimeMeridian *>(other); + if (otherPM == nullptr || + !IdentifiedObject::_isEquivalentTo(other, criterion)) { + return false; + } + return longitude()._isEquivalentTo(otherPM->longitude(), criterion); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Ellipsoid::Private { + common::Length semiMajorAxis_{}; + util::optional<common::Scale> inverseFlattening_{}; + util::optional<common::Length> semiMinorAxis_{}; + util::optional<common::Length> semiMedianAxis_{}; + std::string celestialBody_{}; + + explicit Private(const common::Length &radius, + const std::string &celestialBody) + : semiMajorAxis_(radius), celestialBody_(celestialBody) {} + + Private(const common::Length &semiMajorAxisIn, + const common::Scale &invFlattening, + const std::string &celestialBody) + : semiMajorAxis_(semiMajorAxisIn), inverseFlattening_(invFlattening), + celestialBody_(celestialBody) {} + + Private(const common::Length &semiMajorAxisIn, + const common::Length &semiMinorAxisIn, + const std::string &celestialBody) + : semiMajorAxis_(semiMajorAxisIn), semiMinorAxis_(semiMinorAxisIn), + celestialBody_(celestialBody) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +Ellipsoid::Ellipsoid(const common::Length &radius, + const std::string &celestialBodyIn) + : d(internal::make_unique<Private>(radius, celestialBodyIn)) {} + +// --------------------------------------------------------------------------- + +Ellipsoid::Ellipsoid(const common::Length &semiMajorAxisIn, + const common::Scale &invFlattening, + const std::string &celestialBodyIn) + : d(internal::make_unique<Private>(semiMajorAxisIn, invFlattening, + celestialBodyIn)) {} + +// --------------------------------------------------------------------------- + +Ellipsoid::Ellipsoid(const common::Length &semiMajorAxisIn, + const common::Length &semiMinorAxisIn, + const std::string &celestialBodyIn) + : d(internal::make_unique<Private>(semiMajorAxisIn, semiMinorAxisIn, + celestialBodyIn)) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +Ellipsoid::Ellipsoid(const Ellipsoid &other) + : common::IdentifiedObject(other), + d(internal::make_unique<Private>(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Ellipsoid::~Ellipsoid() = default; + +Ellipsoid::Ellipsoid(const Ellipsoid &other) + : IdentifiedObject(other), d(internal::make_unique<Private>(*(other.d))) {} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the length of the semi-major axis of the ellipsoid. + * + * @return the semi-major axis. + */ +const common::Length &Ellipsoid::semiMajorAxis() PROJ_CONST_DEFN { + return d->semiMajorAxis_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the inverse flattening value of the ellipsoid, if the + * ellipsoid + * has been defined with this value. + * + * @see computeInverseFlattening() that will always return a valid value of the + * inverse flattening, whether the ellipsoid has been defined through inverse + * flattening or semi-minor axis. + * + * @return the inverse flattening value of the ellipsoid, or empty. + */ +const util::optional<common::Scale> & +Ellipsoid::inverseFlattening() PROJ_CONST_DEFN { + return d->inverseFlattening_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the length of the semi-minor axis of the ellipsoid, if the + * ellipsoid + * has been defined with this value. + * + * @see computeSemiMinorAxis() that will always return a valid value of the + * inverse flattening, whether the ellipsoid has been defined through inverse + * flattening or semi-minor axis. + * + * @return the semi-minor axis of the ellipsoid, or empty. + */ +const util::optional<common::Length> & +Ellipsoid::semiMinorAxis() PROJ_CONST_DEFN { + return d->semiMinorAxis_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether the ellipsoid is spherical. + * + * That is to say is semiMajorAxis() == computeSemiMinorAxis(). + * + * A sphere is completely defined by the semi-major axis, which is the radius + * of the sphere. + * + * @return true if the ellipsoid is spherical. + */ +bool Ellipsoid::isSphere() PROJ_CONST_DEFN { + if (d->inverseFlattening_.has_value()) { + return d->inverseFlattening_->value() == 0; + } + + if (semiMinorAxis().has_value()) { + return semiMajorAxis() == *semiMinorAxis(); + } + + return true; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the length of the semi-median axis of a triaxial ellipsoid + * + * This parameter is not required for a biaxial ellipsoid. + * + * @return the semi-median axis of the ellipsoid, or empty. + */ +const util::optional<common::Length> & +Ellipsoid::semiMedianAxis() PROJ_CONST_DEFN { + return d->semiMedianAxis_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return or compute the inverse flattening value of the ellipsoid. + * + * If computed, the inverse flattening is the result of a / (a - b), + * where a is the semi-major axis and b the semi-minor axis. + * + * @return the inverse flattening value of the ellipsoid, or 0 for a sphere. + */ +double Ellipsoid::computedInverseFlattening() PROJ_CONST_DEFN { + if (d->inverseFlattening_.has_value()) { + return d->inverseFlattening_->getSIValue(); + } + + if (d->semiMinorAxis_.has_value()) { + const double a = d->semiMajorAxis_.getSIValue(); + const double b = d->semiMinorAxis_->getSIValue(); + return (a == b) ? 0.0 : a / (a - b); + } + + return 0.0; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the squared eccentricity of the ellipsoid. + * + * @return the squared eccentricity, or a negative value if invalid. + */ +double Ellipsoid::squaredEccentricity() PROJ_CONST_DEFN { + const double rf = computedInverseFlattening(); + const double f = rf != 0.0 ? 1. / rf : 0.0; + const double e2 = f * (2 - f); + return e2; +} + +// --------------------------------------------------------------------------- + +/** \brief Return or compute the length of the semi-minor axis of the ellipsoid. + * + * If computed, the semi-minor axis is the result of a * (1 - 1 / rf) + * where a is the semi-major axis and rf the reverse/inverse flattening. + + * @return the semi-minor axis of the ellipsoid. + */ +common::Length Ellipsoid::computeSemiMinorAxis() const { + if (d->semiMinorAxis_.has_value()) { + return *d->semiMinorAxis_; + } + + if (inverseFlattening().has_value()) { + return common::Length( + (1.0 - 1.0 / d->inverseFlattening_->getSIValue()) * + d->semiMajorAxis_.value(), + d->semiMajorAxis_.unit()); + } + + return d->semiMajorAxis_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the name of the celestial body on which the ellipsoid refers + * to. + */ +const std::string &Ellipsoid::celestialBody() PROJ_CONST_DEFN { + return d->celestialBody_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Ellipsoid as a sphere. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param radius the sphere radius (semi-major axis). + * @param celestialBody Name of the celestial body on which the ellipsoid refers + * to. + * @return new Ellipsoid. + */ +EllipsoidNNPtr Ellipsoid::createSphere(const util::PropertyMap &properties, + const common::Length &radius, + const std::string &celestialBody) { + auto ellipsoid(Ellipsoid::nn_make_shared<Ellipsoid>(radius, celestialBody)); + ellipsoid->setProperties(properties); + return ellipsoid; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Ellipsoid from its inverse/reverse flattening. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param semiMajorAxisIn the semi-major axis. + * @param invFlattening the inverse/reverse flattening. + * @param celestialBody Name of the celestial body on which the ellipsoid refers + * to. + * @return new Ellipsoid. + */ +EllipsoidNNPtr Ellipsoid::createFlattenedSphere( + const util::PropertyMap &properties, const common::Length &semiMajorAxisIn, + const common::Scale &invFlattening, const std::string &celestialBody) { + auto ellipsoid(Ellipsoid::nn_make_shared<Ellipsoid>( + semiMajorAxisIn, invFlattening, celestialBody)); + ellipsoid->setProperties(properties); + return ellipsoid; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Ellipsoid from the value of its two semi axis. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param semiMajorAxisIn the semi-major axis. + * @param semiMinorAxisIn the semi-minor axis. + * @param celestialBody Name of the celestial body on which the ellipsoid refers + * to. + * @return new Ellipsoid. + */ +EllipsoidNNPtr Ellipsoid::createTwoAxis(const util::PropertyMap &properties, + const common::Length &semiMajorAxisIn, + const common::Length &semiMinorAxisIn, + const std::string &celestialBody) { + auto ellipsoid(Ellipsoid::nn_make_shared<Ellipsoid>( + semiMajorAxisIn, semiMinorAxisIn, celestialBody)); + ellipsoid->setProperties(properties); + return ellipsoid; +} + +// --------------------------------------------------------------------------- + +const EllipsoidNNPtr Ellipsoid::createCLARKE_1866() { + return createTwoAxis(createMapNameEPSGCode("Clarke 1866", 7008), + common::Length(6378206.4), common::Length(6356583.8)); +} + +// --------------------------------------------------------------------------- + +const EllipsoidNNPtr Ellipsoid::createWGS84() { + return createFlattenedSphere(createMapNameEPSGCode("WGS 84", 7030), + common::Length(6378137), + common::Scale(298.257223563)); +} + +// --------------------------------------------------------------------------- + +const EllipsoidNNPtr Ellipsoid::createGRS1980() { + return createFlattenedSphere(createMapNameEPSGCode("GRS 1980", 7019), + common::Length(6378137), + common::Scale(298.257222101)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Ellipsoid::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::ELLIPSOID + : io::WKTConstants::SPHEROID, + !identifiers().empty()); + { + auto l_name = nameStr(); + if (l_name.empty()) { + formatter->addQuotedString("unnamed"); + } else { + if (formatter->useESRIDialect()) { + if (l_name == "WGS 84") { + l_name = "WGS_1984"; + } else { + bool aliasFound = false; + const auto &dbContext = formatter->databaseContext(); + if (dbContext) { + auto l_alias = dbContext->getAliasFromOfficialName( + l_name, "ellipsoid", "ESRI"); + if (!l_alias.empty()) { + l_name = l_alias; + aliasFound = true; + } + } + if (!aliasFound) { + l_name = io::WKTFormatter::morphNameToESRI(l_name); + } + } + } + formatter->addQuotedString(l_name); + } + const auto &semiMajor = semiMajorAxis(); + if (isWKT2) { + formatter->add(semiMajor.value()); + } else { + formatter->add(semiMajor.getSIValue()); + } + formatter->add(computedInverseFlattening()); + const auto &unit = semiMajor.unit(); + if (isWKT2 && + !(formatter->ellipsoidUnitOmittedIfMetre() && + unit == common::UnitOfMeasure::METRE)) { + unit._exportToWKT(formatter, io::WKTConstants::LENGTHUNIT); + } + if (formatter->outputId()) { + formatID(formatter); + } + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool Ellipsoid::lookForProjWellKnownEllps(std::string &projEllpsName, + std::string &ellpsName) const { + const double a = semiMajorAxis().getSIValue(); + const double b = computeSemiMinorAxis().getSIValue(); + const double rf = computedInverseFlattening(); + auto proj_ellps = proj_list_ellps(); + for (int i = 0; proj_ellps[i].id != nullptr; i++) { + assert(strncmp(proj_ellps[i].major, "a=", 2) == 0); + const double a_iter = c_locale_stod(proj_ellps[i].major + 2); + if (::fabs(a - a_iter) < 1e-10 * a_iter) { + if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) { + const double b_iter = c_locale_stod(proj_ellps[i].ell + 2); + if (::fabs(b - b_iter) < 1e-10 * b_iter) { + projEllpsName = proj_ellps[i].id; + ellpsName = proj_ellps[i].name; + if (ellpsName.find("GRS 1980") == 0) { + ellpsName = "GRS 1980"; + } + return true; + } + } else { + assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0); + const double rf_iter = c_locale_stod(proj_ellps[i].ell + 3); + if (::fabs(rf - rf_iter) < 1e-10 * rf_iter) { + projEllpsName = proj_ellps[i].id; + ellpsName = proj_ellps[i].name; + if (ellpsName.find("GRS 1980") == 0) { + ellpsName = "GRS 1980"; + } + return true; + } + } + } + } + return false; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Ellipsoid::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(FormattingException) +{ + const double a = semiMajorAxis().getSIValue(); + + std::string projEllpsName; + std::string ellpsName; + if (lookForProjWellKnownEllps(projEllpsName, ellpsName)) { + formatter->addParam("ellps", projEllpsName); + return; + } + + if (isSphere()) { + formatter->addParam("R", a); + } else { + formatter->addParam("a", a); + if (inverseFlattening().has_value()) { + const double rf = computedInverseFlattening(); + formatter->addParam("rf", rf); + } else { + const double b = computeSemiMinorAxis().getSIValue(); + formatter->addParam("b", b); + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return a Ellipsoid object where some parameters are better + * identified. + * + * @return a new Ellipsoid. + */ +EllipsoidNNPtr Ellipsoid::identify() const { + auto newEllipsoid = Ellipsoid::nn_make_shared<Ellipsoid>(*this); + newEllipsoid->assignSelf( + util::nn_static_pointer_cast<util::BaseObject>(newEllipsoid)); + + if (name()->description()->empty() || nameStr() == "unknown") { + std::string projEllpsName; + std::string ellpsName; + if (lookForProjWellKnownEllps(projEllpsName, ellpsName)) { + newEllipsoid->setProperties( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, ellpsName)); + } + } + + return newEllipsoid; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool Ellipsoid::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherEllipsoid = dynamic_cast<const Ellipsoid *>(other); + if (otherEllipsoid == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !IdentifiedObject::_isEquivalentTo(other, criterion))) { + return false; + } + + // PROJ "clrk80" name is "Clarke 1880 mod." and GDAL tends to + // export to it a number of Clarke 1880 variants, so be lax + if (criterion != util::IComparable::Criterion::STRICT && + (nameStr() == "Clarke 1880 mod." || + otherEllipsoid->nameStr() == "Clarke 1880 mod.")) { + return std::fabs(semiMajorAxis().getSIValue() - + otherEllipsoid->semiMajorAxis().getSIValue()) < + 1e-8 * semiMajorAxis().getSIValue() && + std::fabs(computedInverseFlattening() - + otherEllipsoid->computedInverseFlattening()) < + 1e-5 * computedInverseFlattening(); + } + + if (!semiMajorAxis()._isEquivalentTo(otherEllipsoid->semiMajorAxis(), + criterion)) { + return false; + } + + const auto &l_semiMinorAxis = semiMinorAxis(); + const auto &l_other_semiMinorAxis = otherEllipsoid->semiMinorAxis(); + if (l_semiMinorAxis.has_value() && l_other_semiMinorAxis.has_value()) { + if (!l_semiMinorAxis->_isEquivalentTo(*l_other_semiMinorAxis, + criterion)) { + return false; + } + } + + const auto &l_inverseFlattening = inverseFlattening(); + const auto &l_other_sinverseFlattening = + otherEllipsoid->inverseFlattening(); + if (l_inverseFlattening.has_value() && + l_other_sinverseFlattening.has_value()) { + if (!l_inverseFlattening->_isEquivalentTo(*l_other_sinverseFlattening, + criterion)) { + return false; + } + } + + if (criterion == util::IComparable::Criterion::STRICT) { + if ((l_semiMinorAxis.has_value() ^ l_other_semiMinorAxis.has_value())) { + return false; + } + + if ((l_inverseFlattening.has_value() ^ + l_other_sinverseFlattening.has_value())) { + return false; + } + + } else { + if (!otherEllipsoid->computeSemiMinorAxis()._isEquivalentTo( + otherEllipsoid->computeSemiMinorAxis(), criterion)) { + return false; + } + } + + const auto &l_semiMedianAxis = semiMedianAxis(); + const auto &l_other_semiMedianAxis = otherEllipsoid->semiMedianAxis(); + if ((l_semiMedianAxis.has_value() ^ l_other_semiMedianAxis.has_value())) { + return false; + } + if (l_semiMedianAxis.has_value() && l_other_semiMedianAxis.has_value()) { + if (!l_semiMedianAxis->_isEquivalentTo(*l_other_semiMedianAxis, + criterion)) { + return false; + } + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +std::string Ellipsoid::guessBodyName(const io::DatabaseContextPtr &dbContext, + double a) { + constexpr double relError = 0.005; + constexpr double earthMeanRadius = 6375000.0; + if (std::fabs(a - earthMeanRadius) < relError * earthMeanRadius) { + return Ellipsoid::EARTH; + } + if (dbContext) { + try { + auto factory = io::AuthorityFactory::create(NN_NO_CHECK(dbContext), + std::string()); + return factory->identifyBodyFromSemiMajorAxis(a, relError); + } catch (const std::exception &) { + } + } + return "Non-Earth body"; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeodeticReferenceFrame::Private { + PrimeMeridianNNPtr primeMeridian_; + EllipsoidNNPtr ellipsoid_; + + Private(const EllipsoidNNPtr &ellipsoidIn, + const PrimeMeridianNNPtr &primeMeridianIn) + : primeMeridian_(primeMeridianIn), ellipsoid_(ellipsoidIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +GeodeticReferenceFrame::GeodeticReferenceFrame( + const EllipsoidNNPtr &ellipsoidIn, + const PrimeMeridianNNPtr &primeMeridianIn) + : d(internal::make_unique<Private>(ellipsoidIn, primeMeridianIn)) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +GeodeticReferenceFrame::GeodeticReferenceFrame( + const GeodeticReferenceFrame &other) + : Datum(other), d(internal::make_unique<Private>(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeodeticReferenceFrame::~GeodeticReferenceFrame() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the PrimeMeridian associated with a GeodeticReferenceFrame. + * + * @return the PrimeMeridian. + */ +const PrimeMeridianNNPtr & +GeodeticReferenceFrame::primeMeridian() PROJ_CONST_DEFN { + return d->primeMeridian_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the Ellipsoid associated with a GeodeticReferenceFrame. + * + * \note The \ref ISO_19111_2018 modelling allows (but discourages) a + * GeodeticReferenceFrame + * to not be associated with a Ellipsoid in the case where it is used by a + * geocentric crs::GeodeticCRS. We have made the choice of making the ellipsoid + * specification compulsory. + * + * @return the Ellipsoid. + */ +const EllipsoidNNPtr &GeodeticReferenceFrame::ellipsoid() PROJ_CONST_DEFN { + return d->ellipsoid_; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeodeticReferenceFrame + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param ellipsoid the Ellipsoid. + * @param anchor the anchor definition, or empty. + * @param primeMeridian the PrimeMeridian. + * @return new GeodeticReferenceFrame. + */ +GeodeticReferenceFrameNNPtr +GeodeticReferenceFrame::create(const util::PropertyMap &properties, + const EllipsoidNNPtr &ellipsoid, + const util::optional<std::string> &anchor, + const PrimeMeridianNNPtr &primeMeridian) { + GeodeticReferenceFrameNNPtr grf( + GeodeticReferenceFrame::nn_make_shared<GeodeticReferenceFrame>( + ellipsoid, primeMeridian)); + grf->setAnchor(anchor); + grf->setProperties(properties); + return grf; +} + +// --------------------------------------------------------------------------- + +const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::createEPSG_6267() { + return create(createMapNameEPSGCode("North American Datum 1927", 6267), + Ellipsoid::CLARKE_1866, util::optional<std::string>(), + PrimeMeridian::GREENWICH); +} + +// --------------------------------------------------------------------------- + +const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::createEPSG_6269() { + return create(createMapNameEPSGCode("North American Datum 1983", 6269), + Ellipsoid::GRS1980, util::optional<std::string>(), + PrimeMeridian::GREENWICH); +} + +// --------------------------------------------------------------------------- + +const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::createEPSG_6326() { + return create(createMapNameEPSGCode("World Geodetic System 1984", 6326), + Ellipsoid::WGS84, util::optional<std::string>(), + PrimeMeridian::GREENWICH); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +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()); + auto l_name = nameStr(); + if (l_name.empty()) { + l_name = "unnamed"; + } + if (!isWKT2) { + if (formatter->useESRIDialect()) { + if (l_name == "World Geodetic System 1984") { + l_name = "D_WGS_1984"; + } else { + bool aliasFound = false; + const auto &dbContext = formatter->databaseContext(); + if (dbContext) { + auto l_alias = dbContext->getAliasFromOfficialName( + l_name, "geodetic_datum", "ESRI"); + size_t pos; + if (!l_alias.empty()) { + l_name = l_alias; + aliasFound = true; + } else if ((pos = l_name.find(" (")) != std::string::npos) { + l_alias = dbContext->getAliasFromOfficialName( + l_name.substr(0, pos), "geodetic_datum", "ESRI"); + if (!l_alias.empty()) { + l_name = l_alias; + aliasFound = true; + } + } + } + if (!aliasFound) { + l_name = io::WKTFormatter::morphNameToESRI(l_name); + if (!starts_with(l_name, "D_")) { + l_name = "D_" + l_name; + } + } + } + } else { + l_name = io::WKTFormatter::morphNameToESRI(l_name); + if (l_name == "World_Geodetic_System_1984") { + l_name = "WGS_1984"; + } + } + } + formatter->addQuotedString(l_name); + + ellipsoid()->_exportToWKT(formatter); + if (isWKT2) { + Datum::getPrivate()->exportAnchorDefinition(formatter); + } else { + const auto &TOWGS84Params = formatter->getTOWGS84Parameters(); + if (TOWGS84Params.size() == 7) { + formatter->startNode(io::WKTConstants::TOWGS84, false); + for (const auto &val : TOWGS84Params) { + formatter->add(val); + } + formatter->endNode(); + } + std::string extension = formatter->getHDatumExtension(); + if (!extension.empty()) { + formatter->startNode(io::WKTConstants::EXTENSION, false); + formatter->addQuotedString("PROJ4_GRIDS"); + formatter->addQuotedString(extension); + formatter->endNode(); + } + } + if (formatter->outputId()) { + formatID(formatter); + } + // the PRIMEM is exported as a child of the CRS + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool GeodeticReferenceFrame::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherGRF = dynamic_cast<const GeodeticReferenceFrame *>(other); + if (otherGRF == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + return false; + } + return primeMeridian()->_isEquivalentTo(otherGRF->primeMeridian().get(), + criterion) && + ellipsoid()->_isEquivalentTo(otherGRF->ellipsoid().get(), criterion); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DynamicGeodeticReferenceFrame::Private { + common::Measure frameReferenceEpoch{}; + util::optional<std::string> deformationModelName{}; + + explicit Private(const common::Measure &frameReferenceEpochIn) + : frameReferenceEpoch(frameReferenceEpochIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +DynamicGeodeticReferenceFrame::DynamicGeodeticReferenceFrame( + const EllipsoidNNPtr &ellipsoidIn, + const PrimeMeridianNNPtr &primeMeridianIn, + const common::Measure &frameReferenceEpochIn, + const util::optional<std::string> &deformationModelNameIn) + : GeodeticReferenceFrame(ellipsoidIn, primeMeridianIn), + d(internal::make_unique<Private>(frameReferenceEpochIn)) { + d->deformationModelName = deformationModelNameIn; +} + +// --------------------------------------------------------------------------- + +#ifdef notdef +DynamicGeodeticReferenceFrame::DynamicGeodeticReferenceFrame( + const DynamicGeodeticReferenceFrame &other) + : GeodeticReferenceFrame(other), + d(internal::make_unique<Private>(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DynamicGeodeticReferenceFrame::~DynamicGeodeticReferenceFrame() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the epoch to which the coordinates of stations defining the + * dynamic geodetic reference frame are referenced. + * + * Usually given as a decimal year e.g. 2016.47. + * + * @return the frame reference epoch. + */ +const common::Measure & +DynamicGeodeticReferenceFrame::frameReferenceEpoch() const { + return d->frameReferenceEpoch; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the name of the deformation model. + * + * @note This is an extension to the \ref ISO_19111_2018 modeling, to + * hold the content of the DYNAMIC.MODEL WKT2 node. + * + * @return the name of the deformation model. + */ +const util::optional<std::string> & +DynamicGeodeticReferenceFrame::deformationModelName() const { + return d->deformationModelName; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool DynamicGeodeticReferenceFrame::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDGRF = dynamic_cast<const DynamicGeodeticReferenceFrame *>(other); + if (otherDGRF == nullptr || + !GeodeticReferenceFrame::_isEquivalentTo(other, criterion)) { + return false; + } + return frameReferenceEpoch()._isEquivalentTo( + otherDGRF->frameReferenceEpoch(), criterion) && + metadata::Identifier::isEquivalentName( + deformationModelName()->c_str(), + otherDGRF->deformationModelName()->c_str()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DynamicGeodeticReferenceFrame::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (isWKT2 && formatter->use2018Keywords()) { + formatter->startNode(io::WKTConstants::DYNAMIC, false); + formatter->startNode(io::WKTConstants::FRAMEEPOCH, false); + formatter->add( + frameReferenceEpoch().convertToUnit(common::UnitOfMeasure::YEAR)); + formatter->endNode(); + if (deformationModelName().has_value() && + !deformationModelName()->empty()) { + formatter->startNode(io::WKTConstants::MODEL, false); + formatter->addQuotedString(*deformationModelName()); + formatter->endNode(); + } + formatter->endNode(); + } + GeodeticReferenceFrame::_exportToWKT(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DyanmicGeodeticReferenceFrame + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param ellipsoid the Ellipsoid. + * @param anchor the anchor definition, or empty. + * @param primeMeridian the PrimeMeridian. + * @param frameReferenceEpochIn the frame reference epoch. + * @param deformationModelNameIn deformation model name, or empty + * @return new DyanmicGeodeticReferenceFrame. + */ +DynamicGeodeticReferenceFrameNNPtr DynamicGeodeticReferenceFrame::create( + const util::PropertyMap &properties, const EllipsoidNNPtr &ellipsoid, + const util::optional<std::string> &anchor, + const PrimeMeridianNNPtr &primeMeridian, + const common::Measure &frameReferenceEpochIn, + const util::optional<std::string> &deformationModelNameIn) { + DynamicGeodeticReferenceFrameNNPtr grf( + DynamicGeodeticReferenceFrame::nn_make_shared< + DynamicGeodeticReferenceFrame>(ellipsoid, primeMeridian, + frameReferenceEpochIn, + deformationModelNameIn)); + grf->setAnchor(anchor); + grf->setProperties(properties); + return grf; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DatumEnsemble::Private { + std::vector<DatumNNPtr> datums{}; + metadata::PositionalAccuracyNNPtr positionalAccuracy; + + Private(const std::vector<DatumNNPtr> &datumsIn, + const metadata::PositionalAccuracyNNPtr &accuracy) + : datums(datumsIn), positionalAccuracy(accuracy) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +DatumEnsemble::DatumEnsemble(const std::vector<DatumNNPtr> &datumsIn, + const metadata::PositionalAccuracyNNPtr &accuracy) + : d(internal::make_unique<Private>(datumsIn, accuracy)) {} + +// --------------------------------------------------------------------------- + +#ifdef notdef +DatumEnsemble::DatumEnsemble(const DatumEnsemble &other) + : common::IdentifiedObject(other), + d(internal::make_unique<Private>(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DatumEnsemble::~DatumEnsemble() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the set of datums which may be considered to be + * insignificantly different from each other. + * + * @return the set of datums of the DatumEnsemble. + */ +const std::vector<DatumNNPtr> &DatumEnsemble::datums() const { + return d->datums; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the inaccuracy introduced through use of this collection of + * datums. + * + * It is an indication of the differences in coordinate values at all points + * between the various realizations that have been grouped into this datum + * ensemble. + * + * @return the accuracy. + */ +const metadata::PositionalAccuracyNNPtr & +DatumEnsemble::positionalAccuracy() const { + return d->positionalAccuracy; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DatumEnsemble::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2 || !formatter->use2018Keywords()) { + throw io::FormattingException( + "DatumEnsemble can only be exported to WKT2:2018"); + } + + auto l_datums = datums(); + assert(!l_datums.empty()); + + formatter->startNode(io::WKTConstants::ENSEMBLE, false); + const auto &l_name = nameStr(); + if (!l_name.empty()) { + formatter->addQuotedString(l_name); + } else { + formatter->addQuotedString("unnamed"); + } + + for (const auto &datum : l_datums) { + formatter->startNode(io::WKTConstants::MEMBER, + !datum->identifiers().empty()); + const auto &l_datum_name = datum->nameStr(); + if (!l_datum_name.empty()) { + formatter->addQuotedString(l_datum_name); + } else { + formatter->addQuotedString("unnamed"); + } + if (formatter->outputId()) { + datum->formatID(formatter); + } + formatter->endNode(); + } + + auto grfFirst = std::dynamic_pointer_cast<GeodeticReferenceFrame>( + l_datums[0].as_nullable()); + if (grfFirst) { + grfFirst->ellipsoid()->_exportToWKT(formatter); + } + + formatter->startNode(io::WKTConstants::ENSEMBLEACCURACY, false); + formatter->add(positionalAccuracy()->value()); + formatter->endNode(); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DatumEnsemble. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumsIn Array of at least 2 datums. + * @param accuracy Accuracy of the datum ensemble + * @return new DatumEnsemble. + * @throw util::Exception + */ +DatumEnsembleNNPtr DatumEnsemble::create( + const util::PropertyMap &properties, + const std::vector<DatumNNPtr> &datumsIn, + const metadata::PositionalAccuracyNNPtr &accuracy) // throw(Exception) +{ + if (datumsIn.size() < 2) { + throw util::Exception("ensemble should have at least 2 datums"); + } + if (auto grfFirst = + dynamic_cast<const GeodeticReferenceFrame *>(datumsIn[0].get())) { + for (size_t i = 1; i < datumsIn.size(); i++) { + auto grf = + dynamic_cast<const GeodeticReferenceFrame *>(datumsIn[i].get()); + if (!grf) { + throw util::Exception( + "ensemble should have consistent datum types"); + } + if (!grfFirst->ellipsoid()->_isEquivalentTo( + grf->ellipsoid().get())) { + throw util::Exception( + "ensemble should have datums with identical ellipsoid"); + } + if (!grfFirst->primeMeridian()->_isEquivalentTo( + grf->primeMeridian().get())) { + throw util::Exception( + "ensemble should have datums with identical " + "prime meridian"); + } + } + } else if (dynamic_cast<VerticalReferenceFrame *>(datumsIn[0].get())) { + for (size_t i = 1; i < datumsIn.size(); i++) { + if (!dynamic_cast<VerticalReferenceFrame *>(datumsIn[i].get())) { + throw util::Exception( + "ensemble should have consistent datum types"); + } + } + } + auto ensemble( + DatumEnsemble::nn_make_shared<DatumEnsemble>(datumsIn, accuracy)); + ensemble->setProperties(properties); + return ensemble; +} + +// --------------------------------------------------------------------------- + +RealizationMethod::RealizationMethod(const std::string &nameIn) + : CodeList(nameIn) {} + +// --------------------------------------------------------------------------- + +RealizationMethod::RealizationMethod(const RealizationMethod &) = default; + +// --------------------------------------------------------------------------- + +RealizationMethod &RealizationMethod:: +operator=(const RealizationMethod &other) { + CodeList::operator=(other); + return *this; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct VerticalReferenceFrame::Private { + util::optional<RealizationMethod> realizationMethod_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +VerticalReferenceFrame::VerticalReferenceFrame( + const util::optional<RealizationMethod> &realizationMethodIn) + : d(internal::make_unique<Private>()) { + if (!realizationMethodIn->toString().empty()) { + d->realizationMethod_ = *realizationMethodIn; + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +VerticalReferenceFrame::~VerticalReferenceFrame() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the method through which this vertical reference frame is + * realized. + * + * @return the realization method. + */ +const util::optional<RealizationMethod> & +VerticalReferenceFrame::realizationMethod() const { + return d->realizationMethod_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a VerticalReferenceFrame + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param anchor the anchor definition, or empty. + * @param realizationMethodIn the realization method, or empty. + * @return new VerticalReferenceFrame. + */ +VerticalReferenceFrameNNPtr VerticalReferenceFrame::create( + const util::PropertyMap &properties, + const util::optional<std::string> &anchor, + const util::optional<RealizationMethod> &realizationMethodIn) { + auto rf(VerticalReferenceFrame::nn_make_shared<VerticalReferenceFrame>( + realizationMethodIn)); + rf->setAnchor(anchor); + rf->setProperties(properties); + return rf; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void VerticalReferenceFrame::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::VDATUM + : io::WKTConstants::VERT_DATUM, + !identifiers().empty()); + const auto &l_name = nameStr(); + if (!l_name.empty()) { + formatter->addQuotedString(l_name); + } else { + formatter->addQuotedString("unnamed"); + } + if (isWKT2) { + Datum::getPrivate()->exportAnchorDefinition(formatter); + } else { + formatter->add(2005); // CS_VD_GeoidModelDerived from OGC 01-009 + const auto &extension = formatter->getVDatumExtension(); + if (!extension.empty()) { + formatter->startNode(io::WKTConstants::EXTENSION, false); + formatter->addQuotedString("PROJ4_GRIDS"); + formatter->addQuotedString(extension); + formatter->endNode(); + } + } + if (formatter->outputId()) { + formatID(formatter); + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool VerticalReferenceFrame::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherVRF = dynamic_cast<const VerticalReferenceFrame *>(other); + if (otherVRF == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + return false; + } + if ((realizationMethod().has_value() ^ + otherVRF->realizationMethod().has_value())) { + return false; + } + if (realizationMethod().has_value() && + otherVRF->realizationMethod().has_value()) { + if (*(realizationMethod()) != *(otherVRF->realizationMethod())) { + return false; + } + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DynamicVerticalReferenceFrame::Private { + common::Measure frameReferenceEpoch{}; + util::optional<std::string> deformationModelName{}; + + explicit Private(const common::Measure &frameReferenceEpochIn) + : frameReferenceEpoch(frameReferenceEpochIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +DynamicVerticalReferenceFrame::DynamicVerticalReferenceFrame( + const util::optional<RealizationMethod> &realizationMethodIn, + const common::Measure &frameReferenceEpochIn, + const util::optional<std::string> &deformationModelNameIn) + : VerticalReferenceFrame(realizationMethodIn), + d(internal::make_unique<Private>(frameReferenceEpochIn)) { + d->deformationModelName = deformationModelNameIn; +} + +// --------------------------------------------------------------------------- + +#ifdef notdef +DynamicVerticalReferenceFrame::DynamicVerticalReferenceFrame( + const DynamicVerticalReferenceFrame &other) + : VerticalReferenceFrame(other), + d(internal::make_unique<Private>(*other.d)) {} +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DynamicVerticalReferenceFrame::~DynamicVerticalReferenceFrame() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the epoch to which the coordinates of stations defining the + * dynamic geodetic reference frame are referenced. + * + * Usually given as a decimal year e.g. 2016.47. + * + * @return the frame reference epoch. + */ +const common::Measure & +DynamicVerticalReferenceFrame::frameReferenceEpoch() const { + return d->frameReferenceEpoch; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the name of the deformation model. + * + * @note This is an extension to the \ref ISO_19111_2018 modeling, to + * hold the content of the DYNAMIC.MODEL WKT2 node. + * + * @return the name of the deformation model. + */ +const util::optional<std::string> & +DynamicVerticalReferenceFrame::deformationModelName() const { + return d->deformationModelName; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool DynamicVerticalReferenceFrame::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDGRF = dynamic_cast<const DynamicVerticalReferenceFrame *>(other); + if (otherDGRF == nullptr || + !VerticalReferenceFrame::_isEquivalentTo(other, criterion)) { + return false; + } + return frameReferenceEpoch()._isEquivalentTo( + otherDGRF->frameReferenceEpoch(), criterion) && + metadata::Identifier::isEquivalentName( + deformationModelName()->c_str(), + otherDGRF->deformationModelName()->c_str()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DynamicVerticalReferenceFrame::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (isWKT2 && formatter->use2018Keywords()) { + formatter->startNode(io::WKTConstants::DYNAMIC, false); + formatter->startNode(io::WKTConstants::FRAMEEPOCH, false); + formatter->add( + frameReferenceEpoch().convertToUnit(common::UnitOfMeasure::YEAR)); + formatter->endNode(); + if (!deformationModelName()->empty()) { + formatter->startNode(io::WKTConstants::MODEL, false); + formatter->addQuotedString(*deformationModelName()); + formatter->endNode(); + } + formatter->endNode(); + } + VerticalReferenceFrame::_exportToWKT(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DyanmicVerticalReferenceFrame + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param anchor the anchor definition, or empty. + * @param realizationMethodIn the realization method, or empty. + * @param frameReferenceEpochIn the frame reference epoch. + * @param deformationModelNameIn deformation model name, or empty + * @return new DyanmicVerticalReferenceFrame. + */ +DynamicVerticalReferenceFrameNNPtr DynamicVerticalReferenceFrame::create( + const util::PropertyMap &properties, + const util::optional<std::string> &anchor, + const util::optional<RealizationMethod> &realizationMethodIn, + const common::Measure &frameReferenceEpochIn, + const util::optional<std::string> &deformationModelNameIn) { + DynamicVerticalReferenceFrameNNPtr grf( + DynamicVerticalReferenceFrame::nn_make_shared< + DynamicVerticalReferenceFrame>(realizationMethodIn, + frameReferenceEpochIn, + deformationModelNameIn)); + grf->setAnchor(anchor); + grf->setProperties(properties); + return grf; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct TemporalDatum::Private { + common::DateTime temporalOrigin_; + std::string calendar_; + + Private(const common::DateTime &temporalOriginIn, + const std::string &calendarIn) + : temporalOrigin_(temporalOriginIn), calendar_(calendarIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +TemporalDatum::TemporalDatum(const common::DateTime &temporalOriginIn, + const std::string &calendarIn) + : d(internal::make_unique<Private>(temporalOriginIn, calendarIn)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalDatum::~TemporalDatum() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the date and time to which temporal coordinates are + * referenced, expressed in conformance with ISO 8601. + * + * @return the temporal origin. + */ +const common::DateTime &TemporalDatum::temporalOrigin() const { + return d->temporalOrigin_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the calendar to which the temporal origin is referenced + * + * Default value: TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN. + * + * @return the calendar. + */ +const std::string &TemporalDatum::calendar() const { return d->calendar_; } + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a TemporalDatum + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param temporalOriginIn the temporal origin into which temporal coordinates + * are referenced. + * @param calendarIn the calendar (generally + * TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN) + * @return new TemporalDatum. + */ +TemporalDatumNNPtr +TemporalDatum::create(const util::PropertyMap &properties, + const common::DateTime &temporalOriginIn, + const std::string &calendarIn) { + auto datum(TemporalDatum::nn_make_shared<TemporalDatum>(temporalOriginIn, + calendarIn)); + datum->setProperties(properties); + return datum; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void TemporalDatum::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + throw io::FormattingException( + "TemporalDatum can only be exported to WKT2"); + } + formatter->startNode(io::WKTConstants::TDATUM, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + if (formatter->use2018Keywords()) { + formatter->startNode(io::WKTConstants::CALENDAR, false); + formatter->addQuotedString(calendar()); + formatter->endNode(); + } + + const auto &timeOriginStr = temporalOrigin().toString(); + if (!timeOriginStr.empty()) { + formatter->startNode(io::WKTConstants::TIMEORIGIN, false); + if (temporalOrigin().isISO_8601()) { + formatter->add(timeOriginStr); + } else { + formatter->addQuotedString(timeOriginStr); + } + formatter->endNode(); + } + + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool TemporalDatum::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherTD = dynamic_cast<const TemporalDatum *>(other); + if (otherTD == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + return false; + } + return temporalOrigin().toString() == + otherTD->temporalOrigin().toString() && + calendar() == otherTD->calendar(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct EngineeringDatum::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +EngineeringDatum::EngineeringDatum() : d(nullptr) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +EngineeringDatum::~EngineeringDatum() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EngineeringDatum + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param anchor the anchor definition, or empty. + * @return new EngineeringDatum. + */ +EngineeringDatumNNPtr +EngineeringDatum::create(const util::PropertyMap &properties, + const util::optional<std::string> &anchor) { + auto datum(EngineeringDatum::nn_make_shared<EngineeringDatum>()); + datum->setAnchor(anchor); + datum->setProperties(properties); + return datum; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void EngineeringDatum::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::EDATUM + : io::WKTConstants::LOCAL_DATUM, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + if (isWKT2) { + Datum::getPrivate()->exportAnchorDefinition(formatter); + } else { + // Somewhat picked up arbitrarily from OGC 01-009: + // CS_LD_Max (Attribute) : 32767 + // Highest possible value for local datum types. + formatter->add(32767); + } + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool EngineeringDatum::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherTD = dynamic_cast<const EngineeringDatum *>(other); + if (otherTD == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + return false; + } + return true; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ParametricDatum::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +ParametricDatum::ParametricDatum() : d(nullptr) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ParametricDatum::~ParametricDatum() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ParametricDatum + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param anchor the anchor definition, or empty. + * @return new ParametricDatum. + */ +ParametricDatumNNPtr +ParametricDatum::create(const util::PropertyMap &properties, + const util::optional<std::string> &anchor) { + auto datum(ParametricDatum::nn_make_shared<ParametricDatum>()); + datum->setAnchor(anchor); + datum->setProperties(properties); + return datum; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ParametricDatum::_exportToWKT( + io::WKTFormatter *formatter) const // throw(FormattingException) +{ + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + throw io::FormattingException( + "ParametricDatum can only be exported to WKT2"); + } + formatter->startNode(io::WKTConstants::PDATUM, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + Datum::getPrivate()->exportAnchorDefinition(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool ParametricDatum::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherTD = dynamic_cast<const ParametricDatum *>(other); + if (otherTD == nullptr || !Datum::_isEquivalentTo(other, criterion)) { + return false; + } + return true; +} +//! @endcond + +} // namespace datum +NS_PROJ_END diff --git a/src/emess.h b/src/emess.h index 4a6f7587..82526776 100644 --- a/src/emess.h +++ b/src/emess.h @@ -11,7 +11,7 @@ struct EMESS { #ifdef EMESS_ROUTINE /* use type */ /* for emess procedure */ -struct EMESS emess_dat = { (char *)0, (char *)0, 0 }; +struct EMESS PROJ_DLL emess_dat = { (char *)0, (char *)0, 0 }; #ifdef sun /* Archaic SunOs 4.1.1, etc. */ extern char *sys_errlist[]; @@ -20,10 +20,10 @@ extern char *sys_errlist[]; #else /* for for calling procedures */ -extern struct EMESS emess_dat; +extern struct EMESS PROJ_DLL emess_dat; #endif /* use type */ -void emess(int, const char *, ...); +void PROJ_DLL emess(int, const char *, ...); #endif /* end EMESS_H */ diff --git a/src/factory.cpp b/src/factory.cpp new file mode 100644 index 00000000..3c360d13 --- /dev/null +++ b/src/factory.cpp @@ -0,0 +1,4350 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/common.hpp" +#include "proj/coordinateoperation.hpp" +#include "proj/coordinatesystem.hpp" +#include "proj/crs.hpp" +#include "proj/datum.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/coordinateoperation_internal.hpp" +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" +#include "proj/internal/lru_cache.hpp" + +#include <cmath> +#include <cstdlib> +#include <cstring> +#include <iomanip> +#include <limits> +#include <map> +#include <memory> +#include <sstream> // std::ostringstream +#include <string> + +#include "proj_constants.h" + +// PROJ include order is sensitive +// clang-format off +#include "proj.h" +#include "projects.h" +// clang-format on + +#include <sqlite3.h> + +// Custom SQLite VFS as our database is not supposed to be modified in +// parallel. This is slightly faster +#define ENABLE_CUSTOM_LOCKLESS_VFS + +using namespace NS_PROJ::internal; +using namespace NS_PROJ::common; + +NS_PROJ_START +namespace io { + +//! @cond Doxygen_Suppress + +#define GEOG_2D "'geographic 2D'" +#define GEOG_3D "'geographic 3D'" +#define GEOCENTRIC "'geocentric'" + +// --------------------------------------------------------------------------- + +struct SQLValues { + enum class Type { STRING, DOUBLE }; + + // cppcheck-suppress noExplicitConstructor + SQLValues(const std::string &value) : type_(Type::STRING), str_(value) {} + + // cppcheck-suppress noExplicitConstructor + SQLValues(double value) : type_(Type::DOUBLE), double_(value) {} + + const Type &type() const { return type_; } + + // cppcheck-suppress functionStatic + const std::string &stringValue() const { return str_; } + + // cppcheck-suppress functionStatic + double doubleValue() const { return double_; } + + private: + Type type_; + std::string str_{}; + double double_ = 0.0; +}; + +// --------------------------------------------------------------------------- + +using SQLRow = std::vector<std::string>; +using SQLResultSet = std::vector<SQLRow>; + +// --------------------------------------------------------------------------- + +struct DatabaseContext::Private { + Private(); + ~Private(); + + void open(const std::string &databasePath = std::string()); + void setHandle(sqlite3 *sqlite_handle); + + sqlite3 *handle() const { return sqlite_handle_; } + + PJ_CONTEXT *pjCtxt() const { return pjCtxt_; } + void setPjCtxt(PJ_CONTEXT *ctxt) { pjCtxt_ = ctxt; } + + SQLResultSet + run(const std::string &sql, + const std::vector<SQLValues> ¶meters = std::vector<SQLValues>()); + + std::vector<std::string> getDatabaseStructure(); + + // cppcheck-suppress functionStatic + const std::string &getPath() const { return databasePath_; } + + void attachExtraDatabases( + const std::vector<std::string> &auxiliaryDatabasePaths); + + // Mechanism to detect recursion in calls from + // AuthorityFactory::createXXX() -> createFromUserInput() -> + // AuthorityFactory::createXXX() + struct RecursionDetector { + explicit RecursionDetector(const DatabaseContextNNPtr &context) + : dbContext_(context) { + if (dbContext_->getPrivate()->recLevel_ == 2) { + // Throw exception before incrementing, since the destructor + // will not be called + throw FactoryException("Too many recursive calls"); + } + ++dbContext_->getPrivate()->recLevel_; + } + + ~RecursionDetector() { --dbContext_->getPrivate()->recLevel_; } + + private: + DatabaseContextNNPtr dbContext_; + }; + + private: + std::string databasePath_{}; + bool close_handle_ = true; + sqlite3 *sqlite_handle_{}; + std::map<std::string, sqlite3_stmt *> mapSqlToStatement_{}; + PJ_CONTEXT *pjCtxt_ = nullptr; + int recLevel_ = 0; + bool detach_ = false; + + void closeDB(); + + // cppcheck-suppress functionStatic + void registerFunctions(); + +#ifdef ENABLE_CUSTOM_LOCKLESS_VFS + std::string thisNamePtr_{}; + sqlite3_vfs *vfs_{}; + bool createCustomVFS(); +#endif + + Private(const Private &) = delete; + Private &operator=(const Private &) = delete; +}; + +// --------------------------------------------------------------------------- + +DatabaseContext::Private::Private() = default; + +// --------------------------------------------------------------------------- + +DatabaseContext::Private::~Private() { + assert(recLevel_ == 0); + + closeDB(); + +#ifdef ENABLE_CUSTOM_LOCKLESS_VFS + if (vfs_) { + sqlite3_vfs_unregister(vfs_); + delete vfs_; + } +#endif +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::closeDB() { + + if (detach_) { + // Workaround a bug visible in SQLite 3.8.1 and 3.8.2 that causes + // a crash in TEST(factory, attachExtraDatabases_auxiliary) + // due to possible wrong caching of key info. + // The bug is specific to using a memory file with shared cache as an + // auxiliary DB. + // The efinitive fix was likely in 3.8.8 + // https://github.com/mackyle/sqlite/commit/d412d4b8731991ecbd8811874aa463d0821673eb + // But just after 3.8.2, + // https://github.com/mackyle/sqlite/commit/ccf328c4318eacedab9ed08c404bc4f402dcad19 + // also seemed to hide the issue. + // Detaching a database hides the issue, not sure if it is by chance... + run("DETACH DATABASE db_0"); + detach_ = false; + } + + for (auto &pair : mapSqlToStatement_) { + sqlite3_finalize(pair.second); + } + mapSqlToStatement_.clear(); + + if (close_handle_ && sqlite_handle_ != nullptr) { + sqlite3_close(sqlite_handle_); + sqlite_handle_ = nullptr; + } +} + +// --------------------------------------------------------------------------- + +#ifdef ENABLE_CUSTOM_LOCKLESS_VFS + +typedef int (*ClosePtr)(sqlite3_file *); + +static int VFSClose(sqlite3_file *file) { + sqlite3_vfs *defaultVFS = sqlite3_vfs_find(nullptr); + assert(defaultVFS); + ClosePtr defaultClosePtr; + std::memcpy(&defaultClosePtr, + reinterpret_cast<char *>(file) + defaultVFS->szOsFile, + sizeof(ClosePtr)); + void *methods = const_cast<sqlite3_io_methods *>(file->pMethods); + int ret = defaultClosePtr(file); + std::free(methods); + return ret; +} + +// No-lock implementation +static int VSFLock(sqlite3_file *, int) { return SQLITE_OK; } + +static int VSFUnlock(sqlite3_file *, int) { return SQLITE_OK; } + +static int VFSOpen(sqlite3_vfs *vfs, const char *name, sqlite3_file *file, + int flags, int *outFlags) { + sqlite3_vfs *defaultVFS = static_cast<sqlite3_vfs *>(vfs->pAppData); + int ret = defaultVFS->xOpen(defaultVFS, name, file, flags, outFlags); + if (ret == SQLITE_OK) { + ClosePtr defaultClosePtr = file->pMethods->xClose; + assert(defaultClosePtr); + sqlite3_io_methods *methods = static_cast<sqlite3_io_methods *>( + std::malloc(sizeof(sqlite3_io_methods))); + if (!methods) { + file->pMethods->xClose(file); + return SQLITE_NOMEM; + } + memcpy(methods, file->pMethods, sizeof(sqlite3_io_methods)); + methods->xClose = VFSClose; + methods->xLock = VSFLock; + methods->xUnlock = VSFUnlock; + file->pMethods = methods; + // Save original xClose pointer at end of file structure + std::memcpy(reinterpret_cast<char *>(file) + defaultVFS->szOsFile, + &defaultClosePtr, sizeof(ClosePtr)); + } + return ret; +} + +static int VFSAccess(sqlite3_vfs *vfs, const char *zName, int flags, + int *pResOut) { + sqlite3_vfs *defaultVFS = static_cast<sqlite3_vfs *>(vfs->pAppData); + // Do not bother stat'ing for journal or wal files + if (std::strstr(zName, "-journal") || std::strstr(zName, "-wal")) { + *pResOut = false; + return SQLITE_OK; + } + return defaultVFS->xAccess(defaultVFS, zName, flags, pResOut); +} + +// --------------------------------------------------------------------------- + +bool DatabaseContext::Private::createCustomVFS() { + + sqlite3_vfs *defaultVFS = sqlite3_vfs_find(nullptr); + assert(defaultVFS); + + std::ostringstream buffer; + buffer << this; + thisNamePtr_ = buffer.str(); + + vfs_ = new sqlite3_vfs(); + vfs_->iVersion = 1; + vfs_->szOsFile = defaultVFS->szOsFile + sizeof(ClosePtr); + vfs_->mxPathname = defaultVFS->mxPathname; + vfs_->zName = thisNamePtr_.c_str(); + vfs_->pAppData = defaultVFS; + vfs_->xOpen = VFSOpen; + vfs_->xDelete = defaultVFS->xDelete; + vfs_->xAccess = VFSAccess; + vfs_->xFullPathname = defaultVFS->xFullPathname; + vfs_->xDlOpen = defaultVFS->xDlOpen; + vfs_->xDlError = defaultVFS->xDlError; + vfs_->xDlSym = defaultVFS->xDlSym; + vfs_->xDlClose = defaultVFS->xDlClose; + vfs_->xRandomness = defaultVFS->xRandomness; + vfs_->xSleep = defaultVFS->xSleep; + vfs_->xCurrentTime = defaultVFS->xCurrentTime; + vfs_->xGetLastError = defaultVFS->xGetLastError; + vfs_->xCurrentTimeInt64 = defaultVFS->xCurrentTimeInt64; + return sqlite3_vfs_register(vfs_, false) == SQLITE_OK; +} + +#endif // ENABLE_CUSTOM_LOCKLESS_VFS + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::open(const std::string &databasePath) { + std::string path(databasePath); + if (path.empty()) { + const char *proj_lib = std::getenv("PROJ_LIB"); +#ifdef PROJ_LIB + if (!proj_lib) { + proj_lib = PROJ_LIB; + } +#endif + if (!proj_lib) { + throw FactoryException( + "Cannot find proj.db due to missing PROJ_LIB"); + } + path = std::string(proj_lib) + DIR_CHAR + "proj.db"; + } + + if ( +#ifdef ENABLE_CUSTOM_LOCKLESS_VFS + !createCustomVFS() || +#endif + sqlite3_open_v2(path.c_str(), &sqlite_handle_, + SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX, +#ifdef ENABLE_CUSTOM_LOCKLESS_VFS + thisNamePtr_.c_str() +#else + nullptr +#endif + ) != SQLITE_OK || + !sqlite_handle_) { + throw FactoryException("Open of " + path + " failed"); + } + + databasePath_ = path; + registerFunctions(); +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::setHandle(sqlite3 *sqlite_handle) { + + assert(sqlite_handle); + assert(!sqlite_handle_); + sqlite_handle_ = sqlite_handle; + close_handle_ = false; + + registerFunctions(); +} + +// --------------------------------------------------------------------------- + +std::vector<std::string> DatabaseContext::Private::getDatabaseStructure() { + auto sqlRes = run("SELECT sql FROM sqlite_master WHERE type " + "IN ('table', 'trigger', 'view') ORDER BY type"); + std::vector<std::string> res; + for (const auto &row : sqlRes) { + res.emplace_back(row[0]); + } + return res; +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::attachExtraDatabases( + const std::vector<std::string> &auxiliaryDatabasePaths) { + assert(close_handle_); + assert(sqlite_handle_); + + auto tables = + run("SELECT name FROM sqlite_master WHERE type IN ('table', 'view')"); + std::map<std::string, std::vector<std::string>> tableStructure; + for (const auto &rowTable : tables) { + auto tableName = rowTable[0]; + auto tableInfo = run("PRAGMA table_info(\"" + + replaceAll(tableName, "\"", "\"\"") + "\")"); + for (const auto &rowCol : tableInfo) { + const auto &colName = rowCol[1]; + tableStructure[tableName].push_back(colName); + } + } + + closeDB(); + + sqlite3_open_v2(":memory:", &sqlite_handle_, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX +#ifdef SQLITE_OPEN_URI + | SQLITE_OPEN_URI +#endif + , + nullptr); + if (!sqlite_handle_) { + throw FactoryException("cannot create in memory database"); + } + + run("ATTACH DATABASE '" + replaceAll(databasePath_, "'", "''") + + "' AS db_0"); + detach_ = true; + int count = 1; + for (const auto &otherDb : auxiliaryDatabasePaths) { + std::string sql = "ATTACH DATABASE '"; + sql += replaceAll(otherDb, "'", "''"); + sql += "' AS db_"; + sql += toString(static_cast<int>(count)); + count++; + run(sql); + } + + for (const auto &pair : tableStructure) { + std::string sql("CREATE TEMP VIEW "); + sql += pair.first; + sql += " AS "; + for (size_t i = 0; i <= auxiliaryDatabasePaths.size(); ++i) { + std::string selectFromAux("SELECT "); + bool firstCol = true; + for (const auto &colName : pair.second) { + if (!firstCol) { + selectFromAux += ", "; + } + firstCol = false; + selectFromAux += colName; + } + selectFromAux += " FROM db_"; + selectFromAux += toString(static_cast<int>(i)); + selectFromAux += "."; + selectFromAux += pair.first; + + try { + // Check that the request will succeed. In case of 'sparse' + // databases... + run(selectFromAux + " LIMIT 0"); + + if (i > 0) { + sql += " UNION ALL "; + } + sql += selectFromAux; + } catch (const std::exception &) { + } + } + run(sql); + } + + registerFunctions(); +} + +// --------------------------------------------------------------------------- + +static double PROJ_SQLITE_GetValAsDouble(sqlite3_value *val, bool &gotVal) { + switch (sqlite3_value_type(val)) { + case SQLITE_FLOAT: + gotVal = true; + return sqlite3_value_double(val); + + case SQLITE_INTEGER: + gotVal = true; + return static_cast<double>(sqlite3_value_int64(val)); + + default: + gotVal = false; + return 0.0; + } +} + +// --------------------------------------------------------------------------- + +static void PROJ_SQLITE_pseudo_area_from_swne(sqlite3_context *pContext, + int /* argc */, + sqlite3_value **argv) { + bool b0, b1, b2, b3; + double south_lat = PROJ_SQLITE_GetValAsDouble(argv[0], b0); + double west_lon = PROJ_SQLITE_GetValAsDouble(argv[1], b1); + double north_lat = PROJ_SQLITE_GetValAsDouble(argv[2], b2); + double east_lon = PROJ_SQLITE_GetValAsDouble(argv[3], b3); + if (!b0 || !b1 || !b2 || !b3) { + sqlite3_result_null(pContext); + return; + } + // Deal with area crossing antimeridian + if (east_lon < west_lon) { + east_lon += 360.0; + } + // Integrate cos(lat) between south_lat and north_lat + double pseudo_area = (east_lon - west_lon) * + (std::sin(common::Angle(north_lat).getSIValue()) - + std::sin(common::Angle(south_lat).getSIValue())); + sqlite3_result_double(pContext, pseudo_area); +} + +// --------------------------------------------------------------------------- + +static void PROJ_SQLITE_intersects_bbox(sqlite3_context *pContext, + int /* argc */, sqlite3_value **argv) { + bool b0, b1, b2, b3, b4, b5, b6, b7; + double south_lat1 = PROJ_SQLITE_GetValAsDouble(argv[0], b0); + double west_lon1 = PROJ_SQLITE_GetValAsDouble(argv[1], b1); + double north_lat1 = PROJ_SQLITE_GetValAsDouble(argv[2], b2); + double east_lon1 = PROJ_SQLITE_GetValAsDouble(argv[3], b3); + double south_lat2 = PROJ_SQLITE_GetValAsDouble(argv[4], b4); + double west_lon2 = PROJ_SQLITE_GetValAsDouble(argv[5], b5); + double north_lat2 = PROJ_SQLITE_GetValAsDouble(argv[6], b6); + double east_lon2 = PROJ_SQLITE_GetValAsDouble(argv[7], b7); + if (!b0 || !b1 || !b2 || !b3 || !b4 || !b5 || !b6 || !b7) { + sqlite3_result_null(pContext); + return; + } + auto bbox1 = metadata::GeographicBoundingBox::create(west_lon1, south_lat1, + east_lon1, north_lat1); + auto bbox2 = metadata::GeographicBoundingBox::create(west_lon2, south_lat2, + east_lon2, north_lat2); + sqlite3_result_int(pContext, bbox1->intersects(bbox2) ? 1 : 0); +} + +// --------------------------------------------------------------------------- + +#ifndef SQLITE_DETERMINISTIC +#define SQLITE_DETERMINISTIC 0 +#endif + +void DatabaseContext::Private::registerFunctions() { + sqlite3_create_function(sqlite_handle_, "pseudo_area_from_swne", 4, + SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, + PROJ_SQLITE_pseudo_area_from_swne, nullptr, + nullptr); + + sqlite3_create_function(sqlite_handle_, "intersects_bbox", 8, + SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, + PROJ_SQLITE_intersects_bbox, nullptr, nullptr); +} + +// --------------------------------------------------------------------------- + +SQLResultSet +DatabaseContext::Private::run(const std::string &sql, + const std::vector<SQLValues> ¶meters) { + + sqlite3_stmt *stmt = nullptr; + auto iter = mapSqlToStatement_.find(sql); + if (iter != mapSqlToStatement_.end()) { + stmt = iter->second; + sqlite3_reset(stmt); + } else { + if (sqlite3_prepare_v2(sqlite_handle_, sql.c_str(), + static_cast<int>(sql.size()), &stmt, + nullptr) != SQLITE_OK) { + throw FactoryException("SQLite error on " + sql + ": " + + sqlite3_errmsg(sqlite_handle_)); + } + mapSqlToStatement_.insert( + std::pair<std::string, sqlite3_stmt *>(sql, stmt)); + } + + int nBindField = 1; + for (const auto ¶m : parameters) { + if (param.type() == SQLValues::Type::STRING) { + auto strValue = param.stringValue(); + sqlite3_bind_text(stmt, nBindField, strValue.c_str(), + static_cast<int>(strValue.size()), + SQLITE_TRANSIENT); + } else { + assert(param.type() == SQLValues::Type::DOUBLE); + sqlite3_bind_double(stmt, nBindField, param.doubleValue()); + } + nBindField++; + } + + SQLResultSet result; + const int column_count = sqlite3_column_count(stmt); + while (true) { + int ret = sqlite3_step(stmt); + if (ret == SQLITE_ROW) { + SQLRow row; + for (int i = 0; i < column_count; i++) { + const char *txt = reinterpret_cast<const char *>( + sqlite3_column_text(stmt, i)); + row.emplace_back(txt ? txt : std::string()); + } + result.emplace_back(row); + } else if (ret == SQLITE_DONE) { + break; + } else { + throw FactoryException("SQLite error on " + sql + ": " + + sqlite3_errmsg(sqlite_handle_)); + } + } + return result; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DatabaseContext::~DatabaseContext() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DatabaseContext::DatabaseContext() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a database context, using the default proj.db file + * + * It will be searched in the directory pointed by the PROJ_LIB environment + * variable. If not found, on Unix builds, it will be then searched first in + * the pkgdatadir directory of the installation prefix. + * + * This database context should be used only by one thread at a time. + * @throw FactoryException + */ +DatabaseContextNNPtr DatabaseContext::create() { + return create(std::string(), {}); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a database context from a full filename. + * + * This database context should be used only by one thread at a time. + * @param databasePath Path and filename of the database. Might be empty + * string for the default rules to locate the default proj.db + * @throw FactoryException + */ +DatabaseContextNNPtr DatabaseContext::create(const std::string &databasePath) { + return create(databasePath, {}); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a database context from a full filename, and attach + * auxiliary databases to it. + * + * This database context should be used only by one thread at a time. + * @param databasePath Path and filename of the database. Might be empty + * string for the default rules to locate the default proj.db + * @param auxiliaryDatabasePaths Path and filename of auxiliary databases; + * @throw FactoryException + */ +DatabaseContextNNPtr DatabaseContext::create( + const std::string &databasePath, + const std::vector<std::string> &auxiliaryDatabasePaths) { + auto ctxt = DatabaseContext::nn_make_shared<DatabaseContext>(); + ctxt->getPrivate()->open(databasePath); + if (!auxiliaryDatabasePaths.empty()) { + ctxt->getPrivate()->attachExtraDatabases(auxiliaryDatabasePaths); + } + return ctxt; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the list of authorities used in the database. + */ +std::set<std::string> DatabaseContext::getAuthorities() const { + auto res = d->run("SELECT auth_name FROM authority_list"); + std::set<std::string> list; + for (const auto &row : res) { + list.insert(row[0]); + } + return list; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the list of SQL commands (CREATE TABLE, CREATE TRIGGER, + * CREATE VIEW) needed to initialize a new database. + */ +std::vector<std::string> DatabaseContext::getDatabaseStructure() const { + return d->getDatabaseStructure(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the path to the database. + */ +const std::string &DatabaseContext::getPath() const { return d->getPath(); } + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +DatabaseContextNNPtr DatabaseContext::create(void *sqlite_handle) { + auto ctxt = DatabaseContext::nn_make_shared<DatabaseContext>(); + ctxt->getPrivate()->setHandle(static_cast<sqlite3 *>(sqlite_handle)); + return ctxt; +} + +// --------------------------------------------------------------------------- + +void *DatabaseContext::getSqliteHandle() const { + return getPrivate()->handle(); +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::attachPJContext(void *pjCtxt) { + d->setPjCtxt(static_cast<PJ_CONTEXT *>(pjCtxt)); +} + +// --------------------------------------------------------------------------- + +bool DatabaseContext::lookForGridAlternative(const std::string &officialName, + std::string &projFilename, + std::string &projFormat, + bool &inverse) const { + auto res = d->run( + "SELECT proj_grid_name, proj_grid_format, inverse_direction FROM " + "grid_alternatives WHERE original_grid_name = ?", + {officialName}); + if (res.empty()) { + return false; + } + projFilename = res[0][0]; + projFormat = res[0][1]; + inverse = res[0][2] == "1"; + return true; +} + +// --------------------------------------------------------------------------- + +bool DatabaseContext::lookForGridInfo(const std::string &projFilename, + std::string &fullFilename, + std::string &packageName, + std::string &url, bool &directDownload, + bool &openLicense, + bool &gridAvailable) const { + fullFilename.clear(); + packageName.clear(); + url.clear(); + openLicense = false; + directDownload = false; + + fullFilename.resize(2048); + if (d->pjCtxt() == nullptr) { + d->setPjCtxt(pj_get_default_ctx()); + } + gridAvailable = + pj_find_file(d->pjCtxt(), projFilename.c_str(), &fullFilename[0], + fullFilename.size() - 1) != 0; + fullFilename.resize(strlen(fullFilename.c_str())); + + auto res = + d->run("SELECT " + "grid_packages.package_name, " + "grid_alternatives.url, " + "grid_packages.url AS package_url, " + "grid_alternatives.open_license, " + "grid_packages.open_license AS package_open_license, " + "grid_alternatives.direct_download, " + "grid_packages.direct_download AS package_direct_download " + "FROM grid_alternatives " + "LEFT JOIN grid_packages ON " + "grid_alternatives.package_name = grid_packages.package_name " + "WHERE proj_grid_name = ?", + {projFilename}); + if (res.empty()) { + return false; + } + packageName = std::move(res[0][0]); + url = res[0][1].empty() ? std::move(res[0][2]) : std::move(res[0][1]); + openLicense = (res[0][3].empty() ? res[0][4] : res[0][3]) == "1"; + directDownload = (res[0][5].empty() ? res[0][6] : res[0][5]) == "1"; + return true; +} + +// --------------------------------------------------------------------------- + +bool DatabaseContext::isKnownName(const std::string &name, + const std::string &tableName) const { + std::string sql("SELECT 1 FROM \""); + sql += replaceAll(tableName, "\"", "\"\""); + sql += "\" WHERE name = ? LIMIT 1"; + return !d->run(sql, {name}).empty(); +} + +// --------------------------------------------------------------------------- + +/** \brief Gets the alias name from an official name. + * + * @param officialName Official name. + * @param tableName Table name/category. Mandatory + * @param source Source of the alias. Mandatory + * @return Alias name (or empty if not found). + * @throw FactoryException + */ +std::string +DatabaseContext::getAliasFromOfficialName(const std::string &officialName, + const std::string &tableName, + const std::string &source) const { + std::string sql("SELECT auth_name, code FROM \""); + sql += replaceAll(tableName, "\"", "\"\""); + sql += "\" WHERE name = ?"; + if (tableName == "geodetic_crs") { + sql += " AND type = " GEOG_2D; + } + auto res = d->run(sql, {officialName}); + if (res.empty()) { + return std::string(); + } + res = d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND " + "auth_name = ? AND code = ? AND source = ?", + {tableName, res[0][0], res[0][1], source}); + if (res.empty()) { + return std::string(); + } + return res[0][0]; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct AuthorityFactory::Private { + Private(const DatabaseContextNNPtr &contextIn, + const std::string &authorityName) + : context_(contextIn), authority_(authorityName) {} + + inline const std::string &authority() PROJ_CONST_DEFN { return authority_; } + + inline const DatabaseContextNNPtr &context() PROJ_CONST_DEFN { + return context_; + } + + // cppcheck-suppress functionStatic + void setThis(AuthorityFactoryNNPtr factory) { + thisFactory_ = factory.as_nullable(); + } + + // cppcheck-suppress functionStatic + AuthorityFactoryPtr getSharedFromThis() { return thisFactory_.lock(); } + + AuthorityFactoryNNPtr createFactory(const std::string &auth_name); + + // cppcheck-suppress functionStatic + common::UnitOfMeasurePtr getUOMFromCache(const std::string &code); + // cppcheck-suppress functionStatic + void cache(const std::string &code, const common::UnitOfMeasureNNPtr &uom); + + // cppcheck-suppress functionStatic + crs::CRSPtr getCRSFromCache(const std::string &code); + // cppcheck-suppress functionStatic + void cache(const std::string &code, const crs::CRSNNPtr &crs); + + datum::GeodeticReferenceFramePtr + // cppcheck-suppress functionStatic + getGeodeticDatumFromCache(const std::string &code); + // cppcheck-suppress functionStatic + void cache(const std::string &code, + const datum::GeodeticReferenceFrameNNPtr &datum); + + bool rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op, + bool discardIfMissingGrid); + + UnitOfMeasure createUnitOfMeasure(const std::string &auth_name, + const std::string &code); + + util::PropertyMap createProperties(const std::string &code, + const std::string &name, bool deprecated, + const metadata::ExtentPtr &extent); + + util::PropertyMap createProperties(const std::string &code, + const std::string &name, bool deprecated, + const std::string &area_of_use_auth_name, + const std::string &area_of_use_code); + + SQLResultSet + run(const std::string &sql, + const std::vector<SQLValues> ¶meters = std::vector<SQLValues>()); + + SQLResultSet runWithCodeParam(const std::string &sql, + const std::string &code); + + SQLResultSet runWithCodeParam(const char *sql, const std::string &code); + + private: + DatabaseContextNNPtr context_; + std::string authority_; + std::weak_ptr<AuthorityFactory> thisFactory_{}; + std::weak_ptr<AuthorityFactory> parentFactory_{}; + std::map<std::string, AuthorityFactoryNNPtr> mapFactory_{}; + lru11::Cache<std::string, util::BaseObjectPtr> cacheUOM_{}; + lru11::Cache<std::string, util::BaseObjectPtr> cacheCRS_{}; + lru11::Cache<std::string, util::BaseObjectPtr> cacheGeodeticDatum_{}; + + static void + insertIntoCache(lru11::Cache<std::string, util::BaseObjectPtr> &cache, + const std::string &code, const util::BaseObjectPtr &obj); + + static void + getFromCache(lru11::Cache<std::string, util::BaseObjectPtr> &cache, + const std::string &code, util::BaseObjectPtr &obj); +}; + +// --------------------------------------------------------------------------- + +AuthorityFactoryNNPtr +AuthorityFactory::Private::createFactory(const std::string &auth_name) { + + // If we are a child factory, then create new factory on the parent + auto parentFactoryLocked(parentFactory_.lock()); + if (parentFactoryLocked) { + return parentFactoryLocked->d->createFactory(auth_name); + } + + // If asked for a factory with our name, return ourselves. + auto lockedThisFactory(thisFactory_.lock()); + assert(lockedThisFactory); + if (auth_name == lockedThisFactory->getAuthority()) { + return NN_NO_CHECK(lockedThisFactory); + } + + // Find if there is already a child factory with the passed name. + auto iter = mapFactory_.find(auth_name); + if (iter == mapFactory_.end()) { + auto newFactory = AuthorityFactory::create(context_, auth_name); + newFactory->d->parentFactory_ = thisFactory_; + mapFactory_.insert(std::pair<std::string, AuthorityFactoryNNPtr>( + auth_name, newFactory)); + return newFactory; + } + return iter->second; +} + +// --------------------------------------------------------------------------- + +SQLResultSet +AuthorityFactory::Private::run(const std::string &sql, + const std::vector<SQLValues> ¶meters) { + return context()->getPrivate()->run(sql, parameters); +} + +// --------------------------------------------------------------------------- + +SQLResultSet +AuthorityFactory::Private::runWithCodeParam(const std::string &sql, + const std::string &code) { + return run(sql, {authority(), code}); +} + +// --------------------------------------------------------------------------- + +SQLResultSet +AuthorityFactory::Private::runWithCodeParam(const char *sql, + const std::string &code) { + return runWithCodeParam(std::string(sql), code); +} + +// --------------------------------------------------------------------------- + +UnitOfMeasure +AuthorityFactory::Private::createUnitOfMeasure(const std::string &auth_name, + const std::string &code) { + return *(createFactory(auth_name)->createUnitOfMeasure(code)); +} + +// --------------------------------------------------------------------------- + +util::PropertyMap AuthorityFactory::Private::createProperties( + const std::string &code, const std::string &name, bool deprecated, + const metadata::ExtentPtr &extent) { + auto props = util::PropertyMap() + .set(metadata::Identifier::CODESPACE_KEY, authority()) + .set(metadata::Identifier::CODE_KEY, code) + .set(common::IdentifiedObject::NAME_KEY, name) + .set(common::IdentifiedObject::DEPRECATED_KEY, deprecated); + if (extent) { + props.set( + common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + NN_NO_CHECK(std::static_pointer_cast<util::BaseObject>(extent))); + } + return props; +} + +// --------------------------------------------------------------------------- + +util::PropertyMap AuthorityFactory::Private::createProperties( + const std::string &code, const std::string &name, bool deprecated, + const std::string &area_of_use_auth_name, + const std::string &area_of_use_code) { + return createProperties(code, name, deprecated, + area_of_use_auth_name.empty() + ? nullptr + : createFactory(area_of_use_auth_name) + ->createExtent(area_of_use_code) + .as_nullable()); +} + +// --------------------------------------------------------------------------- + +void AuthorityFactory::Private::insertIntoCache( + lru11::Cache<std::string, util::BaseObjectPtr> &cache, + const std::string &code, const util::BaseObjectPtr &obj) { + cache.insert(code, obj); +} + +// --------------------------------------------------------------------------- + +void AuthorityFactory::Private::getFromCache( + lru11::Cache<std::string, util::BaseObjectPtr> &cache, + const std::string &code, util::BaseObjectPtr &obj) { + cache.tryGet(code, obj); +} + +// --------------------------------------------------------------------------- + +crs::CRSPtr +AuthorityFactory::Private::getCRSFromCache(const std::string &code) { + util::BaseObjectPtr obj; + getFromCache(cacheCRS_, code, obj); + return std::static_pointer_cast<crs::CRS>(obj); +} + +// --------------------------------------------------------------------------- + +void AuthorityFactory::Private::cache(const std::string &code, + const crs::CRSNNPtr &crs) { + insertIntoCache(cacheCRS_, code, crs.as_nullable()); +} + +// --------------------------------------------------------------------------- + +common::UnitOfMeasurePtr +AuthorityFactory::Private::getUOMFromCache(const std::string &code) { + util::BaseObjectPtr obj; + getFromCache(cacheUOM_, code, obj); + return std::static_pointer_cast<common::UnitOfMeasure>(obj); +} + +// --------------------------------------------------------------------------- + +void AuthorityFactory::Private::cache(const std::string &code, + const common::UnitOfMeasureNNPtr &uom) { + insertIntoCache(cacheUOM_, code, uom.as_nullable()); +} + +// --------------------------------------------------------------------------- + +datum::GeodeticReferenceFramePtr +AuthorityFactory::Private::getGeodeticDatumFromCache(const std::string &code) { + util::BaseObjectPtr obj; + getFromCache(cacheGeodeticDatum_, code, obj); + return std::static_pointer_cast<datum::GeodeticReferenceFrame>(obj); +} + +// --------------------------------------------------------------------------- + +void AuthorityFactory::Private::cache( + const std::string &code, const datum::GeodeticReferenceFrameNNPtr &datum) { + insertIntoCache(cacheGeodeticDatum_, code, datum.as_nullable()); +} + +// --------------------------------------------------------------------------- + +bool AuthorityFactory::Private::rejectOpDueToMissingGrid( + const operation::CoordinateOperationNNPtr &op, bool discardIfMissingGrid) { + if (discardIfMissingGrid) { + for (const auto &gridDesc : op->gridsNeeded(context())) { + if (!gridDesc.available) { + return true; + } + } + } + return false; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +AuthorityFactory::~AuthorityFactory() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +AuthorityFactory::AuthorityFactory(const DatabaseContextNNPtr &context, + const std::string &authorityName) + : d(internal::make_unique<Private>(context, authorityName)) {} + +// --------------------------------------------------------------------------- + +// clang-format off +/** \brief Instanciate a AuthorityFactory. + * + * The authority name might be set to the empty string in the particular case + * where createFromCoordinateReferenceSystemCodes(const std::string&,const std::string&,const std::string&,const std::string&) const + * is called. + * + * @param context Contexte. + * @param authorityName Authority name. + * @return new AuthorityFactory. + */ +// clang-format on + +AuthorityFactoryNNPtr +AuthorityFactory::create(const DatabaseContextNNPtr &context, + const std::string &authorityName) { + auto factory = AuthorityFactory::nn_make_shared<AuthorityFactory>( + context, authorityName); + factory->d->setThis(factory); + return factory; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the database context. */ +const DatabaseContextNNPtr &AuthorityFactory::databaseContext() const { + return d->context(); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns an arbitrary object from a code. + * + * The returned object will typically be an instance of Datum, + * CoordinateSystem, ReferenceSystem or CoordinateOperation. If the type of + * the object is know at compile time, it is recommended to invoke the most + * precise method instead of this one (for example + * createCoordinateReferenceSystem(code) instead of createObject(code) + * if the caller know he is asking for a coordinate reference system). + * + * If there are several objects with the same code, a FactoryException is + * thrown. + * + * @param code Object code allocated by authority. (e.g. "4326") + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +util::BaseObjectNNPtr +AuthorityFactory::createObject(const std::string &code) const { + + auto res = d->runWithCodeParam( + "SELECT table_name FROM object_view WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("not found", getAuthority(), code); + } + if (res.size() != 1) { + std::string msg( + "More than one object matching specified code. Objects found in "); + bool first = true; + for (const auto &row : res) { + if (!first) + msg += ", "; + msg += row[0]; + first = false; + } + throw FactoryException(msg); + } + const auto &table_name = res[0][0]; + if (table_name == "area") { + return util::nn_static_pointer_cast<util::BaseObject>( + createExtent(code)); + } + if (table_name == "unit_of_measure") { + return util::nn_static_pointer_cast<util::BaseObject>( + createUnitOfMeasure(code)); + } + if (table_name == "prime_meridian") { + return util::nn_static_pointer_cast<util::BaseObject>( + createPrimeMeridian(code)); + } + if (table_name == "ellipsoid") { + return util::nn_static_pointer_cast<util::BaseObject>( + createEllipsoid(code)); + } + if (table_name == "geodetic_datum") { + return util::nn_static_pointer_cast<util::BaseObject>( + createGeodeticDatum(code)); + } + if (table_name == "vertical_datum") { + return util::nn_static_pointer_cast<util::BaseObject>( + createVerticalDatum(code)); + } + if (table_name == "geodetic_crs") { + return util::nn_static_pointer_cast<util::BaseObject>( + createGeodeticCRS(code)); + } + if (table_name == "vertical_crs") { + return util::nn_static_pointer_cast<util::BaseObject>( + createVerticalCRS(code)); + } + if (table_name == "projected_crs") { + return util::nn_static_pointer_cast<util::BaseObject>( + createProjectedCRS(code)); + } + if (table_name == "compound_crs") { + return util::nn_static_pointer_cast<util::BaseObject>( + createCompoundCRS(code)); + } + if (table_name == "conversion") { + return util::nn_static_pointer_cast<util::BaseObject>( + createConversion(code)); + } + if (table_name == "helmert_transformation" || + table_name == "grid_transformation" || + table_name == "other_transformation" || + table_name == "concatenated_operation") { + return util::nn_static_pointer_cast<util::BaseObject>( + createCoordinateOperation(code, false)); + } + throw FactoryException("unimplemented factory for " + res[0][0]); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static FactoryException buildFactoryException(const char *type, + const std::string &code, + const std::exception &ex) { + return FactoryException(std::string("cannot build ") + type + " " + code + + ": " + ex.what()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns a metadata::Extent from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +metadata::ExtentNNPtr +AuthorityFactory::createExtent(const std::string &code) const { + auto sql = "SELECT name, south_lat, north_lat, west_lon, east_lon, " + "deprecated FROM area WHERE auth_name = ? AND code = ?"; + auto res = d->runWithCodeParam(sql, code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("area not found", getAuthority(), + code); + } + try { + const auto &row = res[0]; + const auto &name = row[0]; + double south_lat = c_locale_stod(row[1]); + double north_lat = c_locale_stod(row[2]); + double west_lon = c_locale_stod(row[3]); + double east_lon = c_locale_stod(row[4]); + auto bbox = metadata::GeographicBoundingBox::create( + west_lon, south_lat, east_lon, north_lat); + + return metadata::Extent::create( + util::optional<std::string>(name), + std::vector<metadata::GeographicExtentNNPtr>{bbox}, + std::vector<metadata::VerticalExtentNNPtr>(), + std::vector<metadata::TemporalExtentNNPtr>()); + + } catch (const std::exception &ex) { + throw buildFactoryException("area", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a common::UnitOfMeasure from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +UnitOfMeasureNNPtr +AuthorityFactory::createUnitOfMeasure(const std::string &code) const { + { + auto uom = d->getUOMFromCache(code); + if (uom) { + return NN_NO_CHECK(uom); + } + } + auto res = d->runWithCodeParam( + "SELECT name, conv_factor, type, deprecated FROM unit_of_measure WHERE " + "auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("unit of measure not found", + getAuthority(), code); + } + try { + const auto &row = res[0]; + const auto &name = + (row[0] == "degree (supplier to define representation)") + ? UnitOfMeasure::DEGREE.name() + : row[0]; + double conv_factor = (code == "9107" || code == "9108") + ? UnitOfMeasure::DEGREE.conversionToSI() + : c_locale_stod(row[1]); + constexpr double EPS = 1e-10; + if (std::fabs(conv_factor - UnitOfMeasure::DEGREE.conversionToSI()) < + EPS * UnitOfMeasure::DEGREE.conversionToSI()) { + conv_factor = UnitOfMeasure::DEGREE.conversionToSI(); + } + if (std::fabs(conv_factor - + UnitOfMeasure::ARC_SECOND.conversionToSI()) < + EPS * UnitOfMeasure::ARC_SECOND.conversionToSI()) { + conv_factor = UnitOfMeasure::ARC_SECOND.conversionToSI(); + } + const auto &type_str = row[2]; + UnitOfMeasure::Type unitType = UnitOfMeasure::Type::UNKNOWN; + if (type_str == "length") + unitType = UnitOfMeasure::Type::LINEAR; + else if (type_str == "angle") + unitType = UnitOfMeasure::Type::ANGULAR; + else if (type_str == "scale") + unitType = UnitOfMeasure::Type::SCALE; + else if (type_str == "time") + unitType = UnitOfMeasure::Type::TIME; + auto uom = util::nn_make_shared<UnitOfMeasure>( + name, conv_factor, unitType, getAuthority(), code); + d->cache(code, uom); + return uom; + } catch (const std::exception &ex) { + throw buildFactoryException("unit of measure", code, ex); + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static void normalizeMeasure(const std::string &uom_code, + const std::string &value, + std::string &normalized_uom_code, + double &normalized_value) { + normalized_uom_code = uom_code; + normalized_value = c_locale_stod(value); + if (uom_code == "9110") // DDD.MMSSsss..... + { + std::ostringstream buffer; + buffer.imbue(std::locale::classic()); + constexpr size_t precision = 12; + buffer << std::fixed << std::setprecision(precision) + << normalized_value; + auto formatted = buffer.str(); + size_t dotPos = formatted.find('.'); + assert(dotPos + 1 + precision == formatted.size()); + auto minutes = formatted.substr(dotPos + 1, 2); + auto seconds = formatted.substr(dotPos + 3); + assert(seconds.size() == precision - 2); + normalized_value = + (normalized_value < 0 ? -1.0 : 1.0) * + (int(std::fabs(normalized_value)) + c_locale_stod(minutes) / 60. + + (c_locale_stod(seconds) / std::pow(10, seconds.size() - 2)) / + 3600.); + normalized_uom_code = common::UnitOfMeasure::DEGREE.code(); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns a datum::PrimeMeridian from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +datum::PrimeMeridianNNPtr +AuthorityFactory::createPrimeMeridian(const std::string &code) const { + auto res = d->runWithCodeParam( + "SELECT name, longitude, uom_auth_name, uom_code, deprecated FROM " + "prime_meridian WHERE " + "auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("prime meridian not found", + getAuthority(), code); + } + try { + const auto &row = res[0]; + const auto &name = row[0]; + const auto &longitude = row[1]; + const auto &uom_auth_name = row[2]; + const auto &uom_code = row[3]; + const bool deprecated = row[4] == "1"; + + std::string normalized_uom_code(uom_code); + double normalized_value(c_locale_stod(longitude)); + normalizeMeasure(uom_code, longitude, normalized_uom_code, + normalized_value); + + auto uom = d->createUnitOfMeasure(uom_auth_name, normalized_uom_code); + auto props = d->createProperties(code, name, deprecated, nullptr); + return datum::PrimeMeridian::create( + props, common::Angle(normalized_value, uom)); + } catch (const std::exception &ex) { + throw buildFactoryException("prime meridian", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Identify a celestial body from an approximate radius. + * + * @param semi_major_axis Approximate semi-major axis. + * @param tolerance Relative error allowed. + * @return celestial body name if one single match found. + * @throw FactoryException + */ + +std::string +AuthorityFactory::identifyBodyFromSemiMajorAxis(double semi_major_axis, + double tolerance) const { + auto res = + d->run("SELECT name, (ABS(semi_major_axis - ?) / semi_major_axis ) " + "AS rel_error FROM celestial_body WHERE rel_error <= ?", + {semi_major_axis, tolerance}); + if (res.empty()) { + throw FactoryException("no match found"); + } + if (res.size() > 1) { + throw FactoryException("more than one match found"); + } + return res[0][0]; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a datum::Ellipsoid from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +datum::EllipsoidNNPtr +AuthorityFactory::createEllipsoid(const std::string &code) const { + auto res = d->runWithCodeParam( + "SELECT ellipsoid.name, ellipsoid.semi_major_axis, " + "ellipsoid.uom_auth_name, ellipsoid.uom_code, " + "ellipsoid.inv_flattening, ellipsoid.semi_minor_axis, " + "celestial_body.name AS body_name, ellipsoid.deprecated FROM " + "ellipsoid JOIN celestial_body " + "ON ellipsoid.celestial_body_auth_name = celestial_body.auth_name AND " + "ellipsoid.celestial_body_code = celestial_body.code WHERE " + "ellipsoid.auth_name = ? AND ellipsoid.code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("ellipsoid not found", + getAuthority(), code); + } + try { + const auto &row = res[0]; + const auto &name = row[0]; + const auto &semi_major_axis_str = row[1]; + double semi_major_axis = c_locale_stod(semi_major_axis_str); + const auto &uom_auth_name = row[2]; + const auto &uom_code = row[3]; + const auto &inv_flattening_str = row[4]; + const auto &semi_minor_axis_str = row[5]; + const auto &body = row[6]; + const bool deprecated = row[7] == "1"; + auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code); + auto props = d->createProperties(code, name, deprecated, nullptr); + if (!inv_flattening_str.empty()) { + return datum::Ellipsoid::createFlattenedSphere( + props, common::Length(semi_major_axis, uom), + common::Scale(c_locale_stod(inv_flattening_str)), body); + } else if (semi_major_axis_str == semi_minor_axis_str) { + return datum::Ellipsoid::createSphere( + props, common::Length(semi_major_axis, uom), body); + } else { + return datum::Ellipsoid::createTwoAxis( + props, common::Length(semi_major_axis, uom), + common::Length(c_locale_stod(semi_minor_axis_str), uom), body); + } + } catch (const std::exception &ex) { + throw buildFactoryException("elllipsoid", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a datum::GeodeticReferenceFrame from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +datum::GeodeticReferenceFrameNNPtr +AuthorityFactory::createGeodeticDatum(const std::string &code) const { + { + auto datum = d->getGeodeticDatumFromCache(code); + if (datum) { + return NN_NO_CHECK(datum); + } + } + auto res = d->runWithCodeParam( + "SELECT name, ellipsoid_auth_name, ellipsoid_code, " + "prime_meridian_auth_name, prime_meridian_code, area_of_use_auth_name, " + "area_of_use_code, deprecated FROM geodetic_datum WHERE " + "auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("geodetic datum not found", + getAuthority(), code); + } + try { + const auto &row = res[0]; + const auto &name = row[0]; + const auto &ellipsoid_auth_name = row[1]; + const auto &ellipsoid_code = row[2]; + const auto &prime_meridian_auth_name = row[3]; + const auto &prime_meridian_code = row[4]; + const auto &area_of_use_auth_name = row[5]; + const auto &area_of_use_code = row[6]; + const bool deprecated = row[7] == "1"; + auto ellipsoid = d->createFactory(ellipsoid_auth_name) + ->createEllipsoid(ellipsoid_code); + auto pm = d->createFactory(prime_meridian_auth_name) + ->createPrimeMeridian(prime_meridian_code); + auto props = d->createProperties( + code, name, deprecated, area_of_use_auth_name, area_of_use_code); + auto anchor = util::optional<std::string>(); + auto datum = + datum::GeodeticReferenceFrame::create(props, ellipsoid, anchor, pm); + d->cache(code, datum); + return datum; + } catch (const std::exception &ex) { + throw buildFactoryException("geodetic reference frame", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a datum::VerticalReferenceFrame from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +datum::VerticalReferenceFrameNNPtr +AuthorityFactory::createVerticalDatum(const std::string &code) const { + auto res = d->runWithCodeParam( + "SELECT name, area_of_use_auth_name, area_of_use_code, deprecated FROM " + "vertical_datum WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("vertical datum not found", + getAuthority(), code); + } + try { + const auto &row = res[0]; + const auto &name = row[0]; + const auto &area_of_use_auth_name = row[1]; + const auto &area_of_use_code = row[2]; + const bool deprecated = row[3] == "1"; + auto props = d->createProperties( + code, name, deprecated, area_of_use_auth_name, area_of_use_code); + auto anchor = util::optional<std::string>(); + return datum::VerticalReferenceFrame::create(props, anchor); + } catch (const std::exception &ex) { + throw buildFactoryException("vertical reference frame", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a datum::Datum from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +datum::DatumNNPtr AuthorityFactory::createDatum(const std::string &code) const { + auto res = + d->run("SELECT 'geodetic_datum' FROM geodetic_datum WHERE " + "auth_name = ? AND code = ? " + "UNION ALL SELECT 'vertical_datum' FROM vertical_datum WHERE " + "auth_name = ? AND code = ?", + {getAuthority(), code, getAuthority(), code}); + if (res.empty()) { + throw NoSuchAuthorityCodeException("datum not found", getAuthority(), + code); + } + if (res[0][0] == "geodetic_datum") { + return createGeodeticDatum(code); + } + return createVerticalDatum(code); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static cs::MeridianPtr createMeridian(const std::string &val) { + try { + const std::string degW(std::string("\xC2\xB0") + "W"); + if (ends_with(val, degW)) { + return cs::Meridian::create(common::Angle( + -c_locale_stod(val.substr(0, val.size() - degW.size())))); + } + const std::string degE(std::string("\xC2\xB0") + "E"); + if (ends_with(val, degE)) { + return cs::Meridian::create(common::Angle( + c_locale_stod(val.substr(0, val.size() - degE.size())))); + } + } catch (const std::exception &) { + } + return nullptr; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns a cs::CoordinateSystem from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +cs::CoordinateSystemNNPtr +AuthorityFactory::createCoordinateSystem(const std::string &code) const { + auto res = d->runWithCodeParam( + "SELECT axis.name, abbrev, orientation, uom_auth_name, uom_code, " + "cs.type FROM " + "axis LEFT JOIN coordinate_system cs ON " + "axis.coordinate_system_auth_name = cs.auth_name AND " + "axis.coordinate_system_code = cs.code WHERE " + "coordinate_system_auth_name = ? AND coordinate_system_code = ? ORDER " + "BY coordinate_system_order", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("coordinate system not found", + getAuthority(), code); + } + + const auto &csType = res[0][5]; + std::vector<cs::CoordinateSystemAxisNNPtr> axisList; + for (const auto &row : res) { + const auto &name = row[0]; + const auto &abbrev = row[1]; + const auto &orientation = row[2]; + const auto &uom_auth_name = row[3]; + const auto &uom_code = row[4]; + auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code); + auto props = + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name); + const cs::AxisDirection *direction = + cs::AxisDirection::valueOf(orientation); + cs::MeridianPtr meridian; + if (direction == nullptr) { + if (orientation == "Geocentre > equator/0" + "\xC2\xB0" + "E") { + direction = &(cs::AxisDirection::GEOCENTRIC_X); + } else if (orientation == "Geocentre > equator/90" + "\xC2\xB0" + "E") { + direction = &(cs::AxisDirection::GEOCENTRIC_Y); + } else if (orientation == "Geocentre > north pole") { + direction = &(cs::AxisDirection::GEOCENTRIC_Z); + } else if (starts_with(orientation, "North along ")) { + direction = &(cs::AxisDirection::NORTH); + meridian = + createMeridian(orientation.substr(strlen("North along "))); + } else if (starts_with(orientation, "South along ")) { + direction = &(cs::AxisDirection::SOUTH); + meridian = + createMeridian(orientation.substr(strlen("South along "))); + } else { + throw FactoryException("unknown axis direction: " + + orientation); + } + } + axisList.emplace_back(cs::CoordinateSystemAxis::create( + props, abbrev, *direction, uom, meridian)); + } + auto props = util::PropertyMap() + .set(metadata::Identifier::CODESPACE_KEY, getAuthority()) + .set(metadata::Identifier::CODE_KEY, code); + if (csType == "ellipsoidal") { + if (axisList.size() == 2) { + return cs::EllipsoidalCS::create(props, axisList[0], axisList[1]); + } + if (axisList.size() == 3) { + return cs::EllipsoidalCS::create(props, axisList[0], axisList[1], + axisList[2]); + } + throw FactoryException("invalid number of axis for EllipsoidalCS"); + } + if (csType == "Cartesian") { + if (axisList.size() == 2) { + return cs::CartesianCS::create(props, axisList[0], axisList[1]); + } + if (axisList.size() == 3) { + return cs::CartesianCS::create(props, axisList[0], axisList[1], + axisList[2]); + } + throw FactoryException("invalid number of axis for CartesianCS"); + } + if (csType == "vertical") { + if (axisList.size() == 1) { + return cs::VerticalCS::create(props, axisList[0]); + } + throw FactoryException("invalid number of axis for VerticalCS"); + } + throw FactoryException("unhandled coordinate system type: " + csType); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a crs::GeodeticCRS from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +crs::GeodeticCRSNNPtr +AuthorityFactory::createGeodeticCRS(const std::string &code) const { + return createGeodeticCRS(code, false); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a crs::GeographicCRS from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +crs::GeographicCRSNNPtr +AuthorityFactory::createGeographicCRS(const std::string &code) const { + return NN_NO_CHECK(util::nn_dynamic_pointer_cast<crs::GeographicCRS>( + createGeodeticCRS(code, true))); +} + +// --------------------------------------------------------------------------- + +static crs::GeodeticCRSNNPtr +cloneWithProps(const crs::GeodeticCRSNNPtr &geodCRS, + const util::PropertyMap &props) { + auto cs = geodCRS->coordinateSystem(); + auto datum = geodCRS->datum(); + if (!datum) { + return geodCRS; + } + auto ellipsoidalCS = util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs); + if (ellipsoidalCS) { + return crs::GeographicCRS::create(props, NN_NO_CHECK(datum), + NN_NO_CHECK(ellipsoidalCS)); + } + auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs); + if (geocentricCS) { + return crs::GeodeticCRS::create(props, NN_NO_CHECK(datum), + NN_NO_CHECK(geocentricCS)); + } + return geodCRS; +} + +// --------------------------------------------------------------------------- + +crs::GeodeticCRSNNPtr +AuthorityFactory::createGeodeticCRS(const std::string &code, + bool geographicOnly) const { + auto crs = + std::dynamic_pointer_cast<crs::GeodeticCRS>(d->getCRSFromCache(code)); + if (crs) { + return NN_NO_CHECK(crs); + } + std::string sql("SELECT name, type, coordinate_system_auth_name, " + "coordinate_system_code, datum_auth_name, datum_code, " + "area_of_use_auth_name, area_of_use_code, text_definition, " + "deprecated FROM " + "geodetic_crs WHERE auth_name = ? AND code = ?"); + if (geographicOnly) { + sql += " AND type in (" GEOG_2D "," GEOG_3D ")"; + } + auto res = d->runWithCodeParam(sql, code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("geodeticCRS not found", + getAuthority(), code); + } + try { + const auto &row = res[0]; + const auto &name = row[0]; + const auto &type = row[1]; + const auto &cs_auth_name = row[2]; + const auto &cs_code = row[3]; + const auto &datum_auth_name = row[4]; + const auto &datum_code = row[5]; + const auto &area_of_use_auth_name = row[6]; + const auto &area_of_use_code = row[7]; + const auto &text_definition = row[8]; + const bool deprecated = row[9] == "1"; + + auto props = d->createProperties( + code, name, deprecated, area_of_use_auth_name, area_of_use_code); + + if (!text_definition.empty()) { + DatabaseContext::Private::RecursionDetector detector(d->context()); + auto obj = createFromUserInput(text_definition, d->context()); + auto geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(obj); + if (geodCRS) { + return cloneWithProps(NN_NO_CHECK(geodCRS), props); + } + + auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get()); + if (boundCRS) { + geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>( + boundCRS->baseCRS()); + if (geodCRS) { + auto newBoundCRS = crs::BoundCRS::create( + cloneWithProps(NN_NO_CHECK(geodCRS), props), + boundCRS->hubCRS(), boundCRS->transformation()); + return NN_NO_CHECK( + util::nn_dynamic_pointer_cast<crs::GeodeticCRS>( + newBoundCRS->baseCRSWithCanonicalBoundCRS())); + } + } + + throw FactoryException( + "text_definition does not define a GeodeticCRS"); + } + + auto cs = + d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code); + auto datum = + d->createFactory(datum_auth_name)->createGeodeticDatum(datum_code); + + auto ellipsoidalCS = + util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs); + if ((type == "geographic 2D" || type == "geographic 3D") && + ellipsoidalCS) { + auto crsRet = crs::GeographicCRS::create( + props, datum, NN_NO_CHECK(ellipsoidalCS)); + d->cache(code, crsRet); + return crsRet; + } + auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs); + if (type == "geocentric" && geocentricCS) { + auto crsRet = crs::GeodeticCRS::create(props, datum, + NN_NO_CHECK(geocentricCS)); + d->cache(code, crsRet); + return crsRet; + } + throw FactoryException("unsupported (type, CS type) for geodeticCRS: " + + type + ", " + cs->getWKT2Type(true)); + } catch (const std::exception &ex) { + throw buildFactoryException("geodeticCRS", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a crs::VerticalCRS from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +crs::VerticalCRSNNPtr +AuthorityFactory::createVerticalCRS(const std::string &code) const { + auto res = d->runWithCodeParam( + "SELECT name, coordinate_system_auth_name, " + "coordinate_system_code, datum_auth_name, datum_code, " + "area_of_use_auth_name, area_of_use_code, deprecated FROM " + "vertical_crs WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("verticalCRS not found", + getAuthority(), code); + } + try { + const auto &row = res[0]; + const auto &name = row[0]; + const auto &cs_auth_name = row[1]; + const auto &cs_code = row[2]; + const auto &datum_auth_name = row[3]; + const auto &datum_code = row[4]; + const auto &area_of_use_auth_name = row[5]; + const auto &area_of_use_code = row[6]; + const bool deprecated = row[7] == "1"; + auto cs = + d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code); + auto datum = + d->createFactory(datum_auth_name)->createVerticalDatum(datum_code); + + auto props = d->createProperties( + code, name, deprecated, area_of_use_auth_name, area_of_use_code); + + auto verticalCS = util::nn_dynamic_pointer_cast<cs::VerticalCS>(cs); + if (verticalCS) { + return crs::VerticalCRS::create(props, datum, + NN_NO_CHECK(verticalCS)); + } + throw FactoryException("unsupported CS type for verticalCRS: " + + cs->getWKT2Type(true)); + } catch (const std::exception &ex) { + throw buildFactoryException("verticalCRS", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a operation::Conversion from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +operation::ConversionNNPtr +AuthorityFactory::createConversion(const std::string &code) const { + std::ostringstream buffer; + buffer.imbue(std::locale::classic()); + buffer << "SELECT name, area_of_use_auth_name, area_of_use_code, " + "method_auth_name, method_code, method_name"; + constexpr int N_MAX_PARAMS = 7; + for (int i = 1; i <= N_MAX_PARAMS; ++i) { + buffer << ", param" << i << "_auth_name"; + buffer << ", param" << i << "_code"; + buffer << ", param" << i << "_name"; + buffer << ", param" << i << "_value"; + buffer << ", param" << i << "_uom_auth_name"; + buffer << ", param" << i << "_uom_code"; + } + buffer << ", deprecated FROM conversion WHERE auth_name = ? AND code = ?"; + + auto res = d->runWithCodeParam(buffer.str(), code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("conversion not found", + getAuthority(), code); + } + try { + const auto &row = res[0]; + size_t idx = 0; + const auto &name = row[idx++]; + const auto &area_of_use_auth_name = row[idx++]; + const auto &area_of_use_code = row[idx++]; + const auto &method_auth_name = row[idx++]; + const auto &method_code = row[idx++]; + const auto &method_name = row[idx++]; + const size_t base_param_idx = idx; + std::vector<operation::OperationParameterNNPtr> parameters; + std::vector<operation::ParameterValueNNPtr> values; + for (int i = 0; i < N_MAX_PARAMS; ++i) { + const auto ¶m_auth_name = row[base_param_idx + i * 6 + 0]; + if (param_auth_name.empty()) { + break; + } + const auto ¶m_code = row[base_param_idx + i * 6 + 1]; + const auto ¶m_name = row[base_param_idx + i * 6 + 2]; + const auto ¶m_value = row[base_param_idx + i * 6 + 3]; + const auto ¶m_uom_auth_name = row[base_param_idx + i * 6 + 4]; + const auto ¶m_uom_code = row[base_param_idx + i * 6 + 5]; + parameters.emplace_back(operation::OperationParameter::create( + util::PropertyMap() + .set(metadata::Identifier::CODESPACE_KEY, param_auth_name) + .set(metadata::Identifier::CODE_KEY, param_code) + .set(common::IdentifiedObject::NAME_KEY, param_name))); + std::string normalized_uom_code(param_uom_code); + double normalized_value(c_locale_stod(param_value)); + normalizeMeasure(param_uom_code, param_value, normalized_uom_code, + normalized_value); + auto uom = d->createUnitOfMeasure(param_uom_auth_name, + normalized_uom_code); + values.emplace_back(operation::ParameterValue::create( + common::Measure(normalized_value, uom))); + } + const bool deprecated = row[base_param_idx + N_MAX_PARAMS * 6] == "1"; + + auto propConversion = d->createProperties( + code, name, deprecated, area_of_use_auth_name, area_of_use_code); + + auto propMethod = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, method_name); + if (!method_auth_name.empty()) { + propMethod + .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) + .set(metadata::Identifier::CODE_KEY, method_code); + } + + return operation::Conversion::create(propConversion, propMethod, + parameters, values); + } catch (const std::exception &ex) { + throw buildFactoryException("conversion", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a crs::ProjectedCRS from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +crs::ProjectedCRSNNPtr +AuthorityFactory::createProjectedCRS(const std::string &code) const { + auto res = d->runWithCodeParam( + "SELECT name, coordinate_system_auth_name, " + "coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, " + "conversion_auth_name, conversion_code, " + "area_of_use_auth_name, area_of_use_code, text_definition, " + "deprecated FROM projected_crs WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("projectedCRS not found", + getAuthority(), code); + } + try { + const auto &row = res[0]; + const auto &name = row[0]; + const auto &cs_auth_name = row[1]; + const auto &cs_code = row[2]; + const auto &geodetic_crs_auth_name = row[3]; + const auto &geodetic_crs_code = row[4]; + const auto &conversion_auth_name = row[5]; + const auto &conversion_code = row[6]; + const auto &area_of_use_auth_name = row[7]; + const auto &area_of_use_code = row[8]; + const auto &text_definition = row[9]; + const bool deprecated = row[10] == "1"; + + auto props = d->createProperties( + code, name, deprecated, area_of_use_auth_name, area_of_use_code); + + if (!text_definition.empty()) { + DatabaseContext::Private::RecursionDetector detector(d->context()); + auto obj = createFromUserInput(text_definition, d->context()); + auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(obj.get()); + if (projCRS) { + const auto &conv = projCRS->derivingConversionRef(); + auto newConv = + (conv->nameStr() == "unnamed") + ? operation::Conversion::create( + util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, name), + conv->method(), conv->parameterValues()) + : conv; + return crs::ProjectedCRS::create(props, projCRS->baseCRS(), + newConv, + projCRS->coordinateSystem()); + } + + auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get()); + if (boundCRS) { + projCRS = dynamic_cast<const crs::ProjectedCRS *>( + boundCRS->baseCRS().get()); + if (projCRS) { + auto newBoundCRS = crs::BoundCRS::create( + crs::ProjectedCRS::create( + props, projCRS->baseCRS(), + projCRS->derivingConversionRef(), + projCRS->coordinateSystem()), + boundCRS->hubCRS(), boundCRS->transformation()); + return NN_NO_CHECK( + util::nn_dynamic_pointer_cast<crs::ProjectedCRS>( + newBoundCRS->baseCRSWithCanonicalBoundCRS())); + } + } + + throw FactoryException( + "text_definition does not define a ProjectedCRS"); + } + + auto cs = + d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code); + + auto baseCRS = d->createFactory(geodetic_crs_auth_name) + ->createGeodeticCRS(geodetic_crs_code); + + auto conv = d->createFactory(conversion_auth_name) + ->createConversion(conversion_code); + + auto cartesianCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs); + if (cartesianCS) { + return crs::ProjectedCRS::create(props, baseCRS, conv, + NN_NO_CHECK(cartesianCS)); + } + throw FactoryException("unsupported CS type for projectedCRS: " + + cs->getWKT2Type(true)); + } catch (const std::exception &ex) { + throw buildFactoryException("projectedCRS", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a crs::CompoundCRS from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +crs::CompoundCRSNNPtr +AuthorityFactory::createCompoundCRS(const std::string &code) const { + auto res = d->runWithCodeParam( + "SELECT name, horiz_crs_auth_name, horiz_crs_code, " + "vertical_crs_auth_name, vertical_crs_code, " + "area_of_use_auth_name, area_of_use_code, deprecated FROM " + "compound_crs WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("compoundCRS not found", + getAuthority(), code); + } + try { + const auto &row = res[0]; + const auto &name = row[0]; + const auto &horiz_crs_auth_name = row[1]; + const auto &horiz_crs_code = row[2]; + const auto &vertical_crs_auth_name = row[3]; + const auto &vertical_crs_code = row[4]; + const auto &area_of_use_auth_name = row[5]; + const auto &area_of_use_code = row[6]; + const bool deprecated = row[7] == "1"; + + auto horizCRS = + d->createFactory(horiz_crs_auth_name) + ->createCoordinateReferenceSystem(horiz_crs_code, false); + auto vertCRS = d->createFactory(vertical_crs_auth_name) + ->createVerticalCRS(vertical_crs_code); + + auto props = d->createProperties( + code, name, deprecated, area_of_use_auth_name, area_of_use_code); + return crs::CompoundCRS::create( + props, std::vector<crs::CRSNNPtr>{horizCRS, vertCRS}); + } catch (const std::exception &ex) { + throw buildFactoryException("compoundCRS", code, ex); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a crs::CRS from the specified code. + * + * @param code Object code allocated by authority. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +crs::CRSNNPtr AuthorityFactory::createCoordinateReferenceSystem( + const std::string &code) const { + return createCoordinateReferenceSystem(code, true); +} + +crs::CRSNNPtr +AuthorityFactory::createCoordinateReferenceSystem(const std::string &code, + bool allowCompound) const { + auto crs = d->getCRSFromCache(code); + if (crs) { + return NN_NO_CHECK(crs); + } + auto res = d->runWithCodeParam( + "SELECT type FROM crs_view WHERE auth_name = ? AND code = ?", code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("crs not found", getAuthority(), + code); + } + const auto &type = res[0][0]; + if (type == "geographic 2D" || type == "geographic 3D" || + type == "geocentric") { + return createGeodeticCRS(code); + } + if (type == "vertical") { + return createVerticalCRS(code); + } + if (type == "projected") { + return createProjectedCRS(code); + } + if (allowCompound && type == "compound") { + return createCompoundCRS(code); + } + throw FactoryException("unhandled CRS type: " + type); +} +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static util::PropertyMap createMapNameEPSGCode(const std::string &name, + int code) { + return util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, name) + .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, code); +} + +// --------------------------------------------------------------------------- + +static operation::OperationParameterNNPtr createOpParamNameEPSGCode(int code) { + const char *name = operation::OperationParameter::getNameForEPSGCode(code); + assert(name); + return operation::OperationParameter::create( + createMapNameEPSGCode(name, code)); +} + +static operation::ParameterValueNNPtr createLength(const std::string &value, + const UnitOfMeasure &uom) { + return operation::ParameterValue::create( + common::Length(c_locale_stod(value), uom)); +} + +static operation::ParameterValueNNPtr createAngle(const std::string &value, + const UnitOfMeasure &uom) { + return operation::ParameterValue::create( + common::Angle(c_locale_stod(value), uom)); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns a operation::CoordinateOperation from the specified code. + * + * @param code Object code allocated by authority. + * @param usePROJAlternativeGridNames Whether PROJ alternative grid names + * should be substituted to the official grid names. + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( + const std::string &code, bool usePROJAlternativeGridNames) const { + return createCoordinateOperation(code, true, usePROJAlternativeGridNames); +} + +operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( + const std::string &code, bool allowConcatenated, + bool usePROJAlternativeGridNames) const { + auto res = d->runWithCodeParam( + "SELECT type FROM coordinate_operation_with_conversion_view " + "WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("coordinate operation not found", + getAuthority(), code); + } + const auto type = res[0][0]; + if (type == "conversion") { + return createConversion(code); + } + + if (type == "helmert_transformation") { + + res = d->runWithCodeParam( + "SELECT name, method_auth_name, method_code, method_name, " + "source_crs_auth_name, source_crs_code, target_crs_auth_name, " + "target_crs_code, area_of_use_auth_name, area_of_use_code, " + "accuracy, tx, ty, tz, translation_uom_auth_name, " + "translation_uom_code, rx, ry, rz, rotation_uom_auth_name, " + "rotation_uom_code, scale_difference, " + "scale_difference_uom_auth_name, scale_difference_uom_code, " + "rate_tx, rate_ty, rate_tz, rate_translation_uom_auth_name, " + "rate_translation_uom_code, rate_rx, rate_ry, rate_rz, " + "rate_rotation_uom_auth_name, rate_rotation_uom_code, " + "rate_scale_difference, rate_scale_difference_uom_auth_name, " + "rate_scale_difference_uom_code, epoch, epoch_uom_auth_name, " + "epoch_uom_code, px, py, pz, pivot_uom_auth_name, pivot_uom_code, " + "deprecated FROM " + "helmert_transformation WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + // shouldn't happen if foreign keys are OK + throw NoSuchAuthorityCodeException( + "helmert_transformation not found", getAuthority(), code); + } + try { + const auto &row = res[0]; + size_t idx = 0; + const auto &name = row[idx++]; + const auto &method_auth_name = row[idx++]; + const auto &method_code = row[idx++]; + const auto &method_name = row[idx++]; + const auto &source_crs_auth_name = row[idx++]; + const auto &source_crs_code = row[idx++]; + const auto &target_crs_auth_name = row[idx++]; + const auto &target_crs_code = row[idx++]; + const auto &area_of_use_auth_name = row[idx++]; + const auto &area_of_use_code = row[idx++]; + const auto &accuracy = row[idx++]; + + const auto &tx = row[idx++]; + const auto &ty = row[idx++]; + const auto &tz = row[idx++]; + const auto &translation_uom_auth_name = row[idx++]; + const auto &translation_uom_code = row[idx++]; + const auto &rx = row[idx++]; + const auto &ry = row[idx++]; + const auto &rz = row[idx++]; + const auto &rotation_uom_auth_name = row[idx++]; + const auto &rotation_uom_code = row[idx++]; + const auto &scale_difference = row[idx++]; + const auto &scale_difference_uom_auth_name = row[idx++]; + const auto &scale_difference_uom_code = row[idx++]; + + const auto &rate_tx = row[idx++]; + const auto &rate_ty = row[idx++]; + const auto &rate_tz = row[idx++]; + const auto &rate_translation_uom_auth_name = row[idx++]; + const auto &rate_translation_uom_code = row[idx++]; + const auto &rate_rx = row[idx++]; + const auto &rate_ry = row[idx++]; + const auto &rate_rz = row[idx++]; + const auto &rate_rotation_uom_auth_name = row[idx++]; + const auto &rate_rotation_uom_code = row[idx++]; + const auto &rate_scale_difference = row[idx++]; + const auto &rate_scale_difference_uom_auth_name = row[idx++]; + const auto &rate_scale_difference_uom_code = row[idx++]; + + const auto &epoch = row[idx++]; + const auto &epoch_uom_auth_name = row[idx++]; + const auto &epoch_uom_code = row[idx++]; + + const auto &px = row[idx++]; + const auto &py = row[idx++]; + const auto &pz = row[idx++]; + const auto &pivot_uom_auth_name = row[idx++]; + const auto &pivot_uom_code = row[idx++]; + + const auto &deprecated_str = row[idx++]; + const bool deprecated = deprecated_str == "1"; + assert(idx == row.size()); + + auto uom_translation = d->createUnitOfMeasure( + translation_uom_auth_name, translation_uom_code); + + auto uom_epoch = epoch_uom_auth_name.empty() + ? common::UnitOfMeasure::NONE + : d->createUnitOfMeasure(epoch_uom_auth_name, + epoch_uom_code); + + auto sourceCRS = + d->createFactory(source_crs_auth_name) + ->createCoordinateReferenceSystem(source_crs_code); + auto targetCRS = + d->createFactory(target_crs_auth_name) + ->createCoordinateReferenceSystem(target_crs_code); + + std::vector<operation::OperationParameterNNPtr> parameters; + std::vector<operation::ParameterValueNNPtr> values; + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION)); + values.emplace_back(createLength(tx, uom_translation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION)); + values.emplace_back(createLength(ty, uom_translation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION)); + values.emplace_back(createLength(tz, uom_translation)); + + if (!rx.empty()) { + // Helmert 7-, 8-, 10- or 15- parameter cases + auto uom_rotation = d->createUnitOfMeasure( + rotation_uom_auth_name, rotation_uom_code); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_X_AXIS_ROTATION)); + values.emplace_back(createAngle(rx, uom_rotation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_Y_AXIS_ROTATION)); + values.emplace_back(createAngle(ry, uom_rotation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_Z_AXIS_ROTATION)); + values.emplace_back(createAngle(rz, uom_rotation)); + + auto uom_scale_difference = + scale_difference_uom_auth_name.empty() + ? common::UnitOfMeasure::NONE + : d->createUnitOfMeasure(scale_difference_uom_auth_name, + scale_difference_uom_code); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_SCALE_DIFFERENCE)); + values.emplace_back(operation::ParameterValue::create( + common::Scale(c_locale_stod(scale_difference), + uom_scale_difference))); + } + + if (!rate_tx.empty()) { + // Helmert 15-parameter + + auto uom_rate_translation = d->createUnitOfMeasure( + rate_translation_uom_auth_name, rate_translation_uom_code); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION)); + values.emplace_back( + createLength(rate_tx, uom_rate_translation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION)); + values.emplace_back( + createLength(rate_ty, uom_rate_translation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION)); + values.emplace_back( + createLength(rate_tz, uom_rate_translation)); + + auto uom_rate_rotation = d->createUnitOfMeasure( + rate_rotation_uom_auth_name, rate_rotation_uom_code); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION)); + values.emplace_back(createAngle(rate_rx, uom_rate_rotation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION)); + values.emplace_back(createAngle(rate_ry, uom_rate_rotation)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION)); + values.emplace_back(createAngle(rate_rz, uom_rate_rotation)); + + auto uom_rate_scale_difference = + d->createUnitOfMeasure(rate_scale_difference_uom_auth_name, + rate_scale_difference_uom_code); + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE)); + values.emplace_back(operation::ParameterValue::create( + common::Scale(c_locale_stod(rate_scale_difference), + uom_rate_scale_difference))); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_REFERENCE_EPOCH)); + values.emplace_back(operation::ParameterValue::create( + common::Measure(c_locale_stod(epoch), uom_epoch))); + } else if (uom_epoch != common::UnitOfMeasure::NONE) { + // Helmert 8-parameter + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH)); + values.emplace_back(operation::ParameterValue::create( + common::Measure(c_locale_stod(epoch), uom_epoch))); + } else if (!px.empty()) { + // Molodensky-Badekas case + auto uom_pivot = + d->createUnitOfMeasure(pivot_uom_auth_name, pivot_uom_code); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT)); + values.emplace_back(createLength(px, uom_pivot)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT)); + values.emplace_back(createLength(py, uom_pivot)); + + parameters.emplace_back(createOpParamNameEPSGCode( + EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT)); + values.emplace_back(createLength(pz, uom_pivot)); + } + + auto props = + d->createProperties(code, name, deprecated, + area_of_use_auth_name, area_of_use_code); + + auto propsMethod = + util::PropertyMap() + .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) + .set(metadata::Identifier::CODE_KEY, method_code) + .set(common::IdentifiedObject::NAME_KEY, method_name); + + std::vector<metadata::PositionalAccuracyNNPtr> accuracies; + if (!accuracy.empty()) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create(accuracy)); + } + return operation::Transformation::create( + props, sourceCRS, targetCRS, nullptr, propsMethod, parameters, + values, accuracies); + + } catch (const std::exception &ex) { + throw buildFactoryException("transformation", code, ex); + } + } + + if (type == "grid_transformation") { + res = d->runWithCodeParam( + "SELECT name, method_auth_name, method_code, method_name, " + "source_crs_auth_name, source_crs_code, target_crs_auth_name, " + "target_crs_code, area_of_use_auth_name, area_of_use_code, " + "accuracy, grid_param_auth_name, grid_param_code, grid_param_name, " + "grid_name, " + "grid2_param_auth_name, grid2_param_code, grid2_param_name, " + "grid2_name, " + "interpolation_crs_auth_name, interpolation_crs_code, deprecated " + "FROM " + "grid_transformation WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + // shouldn't happen if foreign keys are OK + throw NoSuchAuthorityCodeException("grid_transformation not found", + getAuthority(), code); + } + try { + const auto &row = res[0]; + size_t idx = 0; + const auto &name = row[idx++]; + const auto &method_auth_name = row[idx++]; + const auto &method_code = row[idx++]; + const auto &method_name = row[idx++]; + const auto &source_crs_auth_name = row[idx++]; + const auto &source_crs_code = row[idx++]; + const auto &target_crs_auth_name = row[idx++]; + const auto &target_crs_code = row[idx++]; + const auto &area_of_use_auth_name = row[idx++]; + const auto &area_of_use_code = row[idx++]; + const auto &accuracy = row[idx++]; + const auto &grid_param_auth_name = row[idx++]; + const auto &grid_param_code = row[idx++]; + const auto &grid_param_name = row[idx++]; + const auto &grid_name = row[idx++]; + const auto &grid2_param_auth_name = row[idx++]; + const auto &grid2_param_code = row[idx++]; + const auto &grid2_param_name = row[idx++]; + const auto &grid2_name = row[idx++]; + const auto &interpolation_crs_auth_name = row[idx++]; + const auto &interpolation_crs_code = row[idx++]; + + const auto &deprecated_str = row[idx++]; + const bool deprecated = deprecated_str == "1"; + assert(idx == row.size()); + + auto sourceCRS = + d->createFactory(source_crs_auth_name) + ->createCoordinateReferenceSystem(source_crs_code); + auto targetCRS = + d->createFactory(target_crs_auth_name) + ->createCoordinateReferenceSystem(target_crs_code); + auto interpolationCRS = + interpolation_crs_auth_name.empty() + ? nullptr + : d->createFactory(interpolation_crs_auth_name) + ->createCoordinateReferenceSystem( + interpolation_crs_code) + .as_nullable(); + + std::vector<operation::OperationParameterNNPtr> parameters; + std::vector<operation::ParameterValueNNPtr> values; + + parameters.emplace_back(operation::OperationParameter::create( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, grid_param_name) + .set(metadata::Identifier::CODESPACE_KEY, + grid_param_auth_name) + .set(metadata::Identifier::CODE_KEY, grid_param_code))); + values.emplace_back( + operation::ParameterValue::createFilename(grid_name)); + if (!grid2_name.empty()) { + parameters.emplace_back(operation::OperationParameter::create( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, + grid2_param_name) + .set(metadata::Identifier::CODESPACE_KEY, + grid2_param_auth_name) + .set(metadata::Identifier::CODE_KEY, + grid2_param_code))); + values.emplace_back( + operation::ParameterValue::createFilename(grid2_name)); + } + + auto props = + d->createProperties(code, name, deprecated, + area_of_use_auth_name, area_of_use_code); + auto propsMethod = + util::PropertyMap() + .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) + .set(metadata::Identifier::CODE_KEY, method_code) + .set(common::IdentifiedObject::NAME_KEY, method_name); + + std::vector<metadata::PositionalAccuracyNNPtr> accuracies; + if (!accuracy.empty()) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create(accuracy)); + } + auto transf = operation::Transformation::create( + props, sourceCRS, targetCRS, interpolationCRS, propsMethod, + parameters, values, accuracies); + if (usePROJAlternativeGridNames) { + return transf->substitutePROJAlternativeGridNames(d->context()); + } + return transf; + + } catch (const std::exception &ex) { + throw buildFactoryException("transformation", code, ex); + } + } + + if (type == "other_transformation") { + std::ostringstream buffer; + buffer.imbue(std::locale::classic()); + buffer + << "SELECT name, method_auth_name, method_code, method_name, " + "source_crs_auth_name, source_crs_code, target_crs_auth_name, " + "target_crs_code, area_of_use_auth_name, area_of_use_code, " + "accuracy"; + constexpr int N_MAX_PARAMS = 7; + for (int i = 1; i <= N_MAX_PARAMS; ++i) { + buffer << ", param" << i << "_auth_name"; + buffer << ", param" << i << "_code"; + buffer << ", param" << i << "_name"; + buffer << ", param" << i << "_value"; + buffer << ", param" << i << "_uom_auth_name"; + buffer << ", param" << i << "_uom_code"; + } + buffer << ", deprecated FROM other_transformation WHERE auth_name = ? " + "AND code = ?"; + + res = d->runWithCodeParam(buffer.str(), code); + if (res.empty()) { + // shouldn't happen if foreign keys are OK + throw NoSuchAuthorityCodeException("other_transformation not found", + getAuthority(), code); + } + try { + const auto &row = res[0]; + size_t idx = 0; + const auto &name = row[idx++]; + const auto &method_auth_name = row[idx++]; + const auto &method_code = row[idx++]; + const auto &method_name = row[idx++]; + const auto &source_crs_auth_name = row[idx++]; + const auto &source_crs_code = row[idx++]; + const auto &target_crs_auth_name = row[idx++]; + const auto &target_crs_code = row[idx++]; + const auto &area_of_use_auth_name = row[idx++]; + const auto &area_of_use_code = row[idx++]; + const auto &accuracy = row[idx++]; + + const size_t base_param_idx = idx; + std::vector<operation::OperationParameterNNPtr> parameters; + std::vector<operation::ParameterValueNNPtr> values; + for (int i = 0; i < N_MAX_PARAMS; ++i) { + const auto ¶m_auth_name = row[base_param_idx + i * 6 + 0]; + if (param_auth_name.empty()) { + break; + } + const auto ¶m_code = row[base_param_idx + i * 6 + 1]; + const auto ¶m_name = row[base_param_idx + i * 6 + 2]; + const auto ¶m_value = row[base_param_idx + i * 6 + 3]; + const auto ¶m_uom_auth_name = + row[base_param_idx + i * 6 + 4]; + const auto ¶m_uom_code = row[base_param_idx + i * 6 + 5]; + parameters.emplace_back(operation::OperationParameter::create( + util::PropertyMap() + .set(metadata::Identifier::CODESPACE_KEY, + param_auth_name) + .set(metadata::Identifier::CODE_KEY, param_code) + .set(common::IdentifiedObject::NAME_KEY, param_name))); + std::string normalized_uom_code(param_uom_code); + double normalized_value(c_locale_stod(param_value)); + normalizeMeasure(param_uom_code, param_value, + normalized_uom_code, normalized_value); + auto uom = d->createUnitOfMeasure(param_uom_auth_name, + normalized_uom_code); + values.emplace_back(operation::ParameterValue::create( + common::Measure(normalized_value, uom))); + } + idx = base_param_idx + 6 * N_MAX_PARAMS; + + const auto &deprecated_str = row[idx++]; + const bool deprecated = deprecated_str == "1"; + assert(idx == row.size()); + + auto sourceCRS = + d->createFactory(source_crs_auth_name) + ->createCoordinateReferenceSystem(source_crs_code); + auto targetCRS = + d->createFactory(target_crs_auth_name) + ->createCoordinateReferenceSystem(target_crs_code); + + auto props = + d->createProperties(code, name, deprecated, + area_of_use_auth_name, area_of_use_code); + + std::vector<metadata::PositionalAccuracyNNPtr> accuracies; + if (!accuracy.empty()) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create(accuracy)); + } + + if (method_auth_name == "PROJ") { + if (method_code == "PROJString") { + return operation::SingleOperation::createPROJBased( + props, method_name, sourceCRS, targetCRS, accuracies); + } else if (method_code == "WKT") { + auto op = util::nn_dynamic_pointer_cast< + operation::CoordinateOperation>( + WKTParser().createFromWKT(method_name)); + if (!op) { + throw FactoryException("WKT string does not express a " + "coordinate operation"); + } + op->setCRSs(sourceCRS, targetCRS, nullptr); + return NN_NO_CHECK(op); + } + } + + auto propsMethod = + util::PropertyMap() + .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) + .set(metadata::Identifier::CODE_KEY, method_code) + .set(common::IdentifiedObject::NAME_KEY, method_name); + + if (method_auth_name == metadata::Identifier::EPSG && + operation::isAxisOrderReversal( + std::atoi(method_code.c_str()))) { + auto op = operation::Conversion::create(props, propsMethod, + parameters, values); + op->setCRSs(sourceCRS, targetCRS, nullptr); + return op; + } + return operation::Transformation::create( + props, sourceCRS, targetCRS, nullptr, propsMethod, parameters, + values, accuracies); + + } catch (const std::exception &ex) { + throw buildFactoryException("transformation", code, ex); + } + } + + if (allowConcatenated && type == "concatenated_operation") { + res = d->runWithCodeParam( + "SELECT name, source_crs_auth_name, source_crs_code, " + "target_crs_auth_name, target_crs_code, " + "area_of_use_auth_name, area_of_use_code, accuracy, " + "step1_auth_name, step1_code, step2_auth_name, step2_code, " + "step3_auth_name, step3_code, deprecated FROM " + "concatenated_operation WHERE auth_name = ? AND code = ?", + code); + if (res.empty()) { + // shouldn't happen if foreign keys are OK + throw NoSuchAuthorityCodeException( + "concatenated_operation not found", getAuthority(), code); + } + try { + const auto &row = res[0]; + size_t idx = 0; + const auto &name = row[idx++]; + const auto &source_crs_auth_name = row[idx++]; + const auto &source_crs_code = row[idx++]; + const auto &target_crs_auth_name = row[idx++]; + const auto &target_crs_code = row[idx++]; + const auto &area_of_use_auth_name = row[idx++]; + const auto &area_of_use_code = row[idx++]; + const auto &accuracy = row[idx++]; + const auto &step1_auth_name = row[idx++]; + const auto &step1_code = row[idx++]; + const auto &step2_auth_name = row[idx++]; + const auto &step2_code = row[idx++]; + const auto &step3_auth_name = row[idx++]; + const auto &step3_code = row[idx++]; + const auto &deprecated_str = row[idx++]; + const bool deprecated = deprecated_str == "1"; + + std::vector<operation::CoordinateOperationNNPtr> operations; + operations.push_back( + d->createFactory(step1_auth_name) + ->createCoordinateOperation(step1_code, false, + usePROJAlternativeGridNames)); + operations.push_back( + d->createFactory(step2_auth_name) + ->createCoordinateOperation(step2_code, false, + usePROJAlternativeGridNames)); + + if (!step3_auth_name.empty()) { + operations.push_back( + d->createFactory(step3_auth_name) + ->createCoordinateOperation( + step3_code, false, usePROJAlternativeGridNames)); + } + + // In case the operation is a conversion (we hope this is the + // forward case!) + if (!operations[0]->sourceCRS() || !operations[0]->targetCRS()) { + if (!operations[1]->sourceCRS()) { + throw FactoryException( + "chaining of conversion not supported"); + } + operations[0]->setCRSs( + d->createFactory(source_crs_auth_name) + ->createCoordinateReferenceSystem(source_crs_code), + NN_NO_CHECK(operations[1]->sourceCRS()), nullptr); + } + + // Some concatenated operations, like 8443, might actually chain + // reverse operations rather than forward operations. + { + const auto &op0SrcId = + operations[0]->sourceCRS()->identifiers()[0]; + if (op0SrcId->code() != source_crs_code || + *op0SrcId->codeSpace() != source_crs_auth_name) { + operations[0] = operations[0]->inverse(); + } + } + + { + const auto &op0SrcId = + operations[0]->sourceCRS()->identifiers()[0]; + if (op0SrcId->code() != source_crs_code || + *op0SrcId->codeSpace() != source_crs_auth_name) { + throw FactoryException( + "Source CRS of first operation in concatenated " + "operation " + + code + " does not match source CRS of " + "concatenated operation"); + } + } + + // In case the operation is a conversion (we hope this is the + // forward case!) + if (!operations[1]->sourceCRS() || !operations[1]->targetCRS()) { + if (step3_auth_name.empty()) { + operations[1]->setCRSs( + NN_NO_CHECK(operations[0]->targetCRS()), + d->createFactory(target_crs_auth_name) + ->createCoordinateReferenceSystem(target_crs_code), + nullptr); + } else { + if (!operations[2]->sourceCRS()) { + throw FactoryException( + "chaining of conversion not supported"); + } + operations[1]->setCRSs( + NN_NO_CHECK(operations[0]->targetCRS()), + NN_NO_CHECK(operations[2]->sourceCRS()), nullptr); + } + } + + const auto &op1SrcId = operations[1]->sourceCRS()->identifiers()[0]; + const auto &op0TargetId = + operations[0]->targetCRS()->identifiers()[0]; + while (true) { + if (step3_auth_name.empty()) { + const auto &opLastTargetId = + operations.back()->targetCRS()->identifiers()[0]; + if (opLastTargetId->code() == target_crs_code && + *opLastTargetId->codeSpace() == target_crs_auth_name) { + // in case we have only 2 steps, and + // step2.targetCRS == concatenate.targetCRS do nothing, + // but ConcatenatedOperation::create() will ultimately + // check that step1.targetCRS == step2.sourceCRS + break; + } + } + if (op1SrcId->code() != op0TargetId->code() || + *op1SrcId->codeSpace() != *op0TargetId->codeSpace()) { + operations[1] = operations[1]->inverse(); + } + break; + } + + if (!step3_auth_name.empty()) { + + const auto &op2Src = operations[2]->sourceCRS(); + // In case the operation is a conversion (we hope this is the + // forward case!) + if (!op2Src || !operations[2]->targetCRS()) { + operations[2]->setCRSs( + NN_NO_CHECK(operations[1]->targetCRS()), + d->createFactory(target_crs_auth_name) + ->createCoordinateReferenceSystem(target_crs_code), + nullptr); + } + + const auto &op2SrcId = op2Src->identifiers()[0]; + const auto &op1TargetId = + operations[1]->targetCRS()->identifiers()[0]; + if (op2SrcId->code() != op1TargetId->code() || + *op2SrcId->codeSpace() != *op1TargetId->codeSpace()) { + operations[2] = operations[2]->inverse(); + } + } + + const auto &opLastTargetId = + operations.back()->targetCRS()->identifiers()[0]; + if (opLastTargetId->code() != target_crs_code || + *opLastTargetId->codeSpace() != target_crs_auth_name) { + throw FactoryException( + "Target CRS of last operation in concatenated operation " + + code + + " doest not match target CRS of concatenated operation"); + } + + auto props = + d->createProperties(code, name, deprecated, + area_of_use_auth_name, area_of_use_code); + + std::vector<metadata::PositionalAccuracyNNPtr> accuracies; + if (!accuracy.empty()) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create(accuracy)); + } else { + // Try to compute a reasonable accuracy from the members + double totalAcc = -1; + try { + for (const auto &op : operations) { + auto accs = op->coordinateOperationAccuracies(); + if (accs.size() == 1) { + double acc = c_locale_stod(accs[0]->value()); + if (totalAcc < 0) { + totalAcc = acc; + } else { + totalAcc += acc; + } + } else { + totalAcc = -1; + break; + } + } + if (totalAcc >= 0) { + accuracies.emplace_back( + metadata::PositionalAccuracy::create( + toString(totalAcc))); + } + } catch (const std::exception &) { + } + } + return operation::ConcatenatedOperation::create(props, operations, + accuracies); + + } catch (const std::exception &ex) { + throw buildFactoryException("transformation", code, ex); + } + } + + throw FactoryException("unhandled coordinate operation type: " + type); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a list operation::CoordinateOperation between two CRS. + * + * The list is ordered with preferred operations first. No attempt is made + * at infering operations that are not explicitly in the database. + * + * Deprecated operations are rejected. + * + * @param sourceCRSCode Source CRS code allocated by authority. + * @param targetCRSCode Source CRS code allocated by authority. + * @return list of coordinate operations + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +std::vector<operation::CoordinateOperationNNPtr> +AuthorityFactory::createFromCoordinateReferenceSystemCodes( + const std::string &sourceCRSCode, const std::string &targetCRSCode) const { + return createFromCoordinateReferenceSystemCodes( + getAuthority(), sourceCRSCode, getAuthority(), targetCRSCode, false, + false); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a list operation::CoordinateOperation between two CRS. + * + * The list is ordered with preferred operations first. No attempt is made + * at infering operations that are not explicitly in the database (see + * createFromCRSCodesWithIntermediates() for that), and only + * source -> target operations are searched (ie if target -> source is present, + * you need to call this method with the arguments reversed, and apply the + * reverse transformations). + * + * Deprecated operations are rejected. + * + * If getAuthority() returns empty, then coordinate operations from all + * authorities are considered. + * + * @param sourceCRSAuthName Authority name of sourceCRSCode + * @param sourceCRSCode Source CRS code allocated by authority + * sourceCRSAuthName. + * @param targetCRSAuthName Authority name of targetCRSCode + * @param targetCRSCode Source CRS code allocated by authority + * targetCRSAuthName. + * @param usePROJAlternativeGridNames Whether PROJ alternative grid names + * should be substituted to the official grid names. + * @param discardIfMissingGrid Whether coordinate operations that reference + * missing grids should be removed from the result set. + * @return list of coordinate operations + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +std::vector<operation::CoordinateOperationNNPtr> +AuthorityFactory::createFromCoordinateReferenceSystemCodes( + const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, + const std::string &targetCRSAuthName, const std::string &targetCRSCode, + bool usePROJAlternativeGridNames, bool discardIfMissingGrid) const { + std::vector<operation::CoordinateOperationNNPtr> list; + + // Look-up first for conversion which is the most precise. + std::string sql( + "SELECT conversion_auth_name, conversion_code FROM " + "projected_crs WHERE geodetic_crs_auth_name = ? AND geodetic_crs_code " + "= ? AND auth_name = ? AND code = ? AND deprecated != 1"); + auto params = std::vector<SQLValues>{sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode}; + if (!getAuthority().empty()) { + sql += " AND conversion_auth_name = ?"; + params.emplace_back(getAuthority()); + } + auto res = d->run(sql, params); + if (!res.empty()) { + auto targetCRS = d->createFactory(targetCRSAuthName) + ->createProjectedCRS(targetCRSCode); + auto conv = targetCRS->derivingConversion(); + list.emplace_back(conv); + return list; + } + sql = + "SELECT cov.auth_name, cov.code FROM " + "coordinate_operation_view cov JOIN area ON cov.area_of_use_auth_name " + "= area.auth_name AND cov.area_of_use_code = area.code WHERE " + "source_crs_auth_name = ? AND source_crs_code = ? AND " + "target_crs_auth_name = ? AND target_crs_code = ? AND " + "cov.deprecated != 1"; + params = {sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, + targetCRSCode}; + if (!getAuthority().empty()) { + sql += " AND cov.auth_name = ?"; + params.emplace_back(getAuthority()); + } + sql += " ORDER BY pseudo_area_from_swne(south_lat, west_lon, north_lat, " + "east_lon) DESC, " + "(CASE WHEN accuracy is NULL THEN 1 ELSE 0 END), accuracy"; + res = d->run(sql, params); + for (const auto &row : res) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + auto op = d->createFactory(auth_name)->createCoordinateOperation( + code, true, usePROJAlternativeGridNames); + if (!d->rejectOpDueToMissingGrid(op, discardIfMissingGrid)) { + list.emplace_back(op); + } + } + return list; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static std::string +buildIntermediateWhere(const std::vector<std::pair<std::string, std::string>> + &intermediateCRSAuthCodes, + const std::string &first_field, + const std::string &second_field) { + if (intermediateCRSAuthCodes.empty()) { + return std::string(); + } + std::string sql(" AND ("); + for (size_t i = 0; i < intermediateCRSAuthCodes.size(); ++i) { + if (i > 0) { + sql += " OR"; + } + sql += "(v1." + first_field + "_crs_auth_name = ? AND "; + sql += "v1." + first_field + "_crs_code = ? AND "; + sql += "v2." + second_field + "_crs_auth_name = ? AND "; + sql += "v2." + second_field + "_crs_code = ?) "; + } + sql += ")"; + return sql; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static bool useIrrelevantPivot(const operation::CoordinateOperationNNPtr &op, + const std::string &sourceCRSAuthName, + const std::string &sourceCRSCode, + const std::string &targetCRSAuthName, + const std::string &targetCRSCode) { + auto concat = + dynamic_cast<const operation::ConcatenatedOperation *>(op.get()); + if (!concat) { + return false; + } + auto ops = concat->operations(); + for (size_t i = 0; i + 1 < ops.size(); i++) { + auto targetCRS = ops[i]->targetCRS(); + if (targetCRS) { + const auto &ids = targetCRS->identifiers(); + if (ids.size() == 1 && + ((*ids[0]->codeSpace() == sourceCRSAuthName && + ids[0]->code() == sourceCRSCode) || + (*ids[0]->codeSpace() == targetCRSAuthName && + ids[0]->code() == targetCRSCode))) { + return true; + } + } + } + return false; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns a list operation::CoordinateOperation between two CRS, + * using intermediate codes. + * + * The list is ordered with preferred operations first. + * + * Deprecated operations are rejected. + * + * The method will take care of considering all potential combinations (ie + * contrary to createFromCoordinateReferenceSystemCodes(), you do not need to + * call it with sourceCRS and targetCRS switched) + * + * If getAuthority() returns empty, then coordinate operations from all + * authorities are considered. + * + * @param sourceCRSAuthName Authority name of sourceCRSCode + * @param sourceCRSCode Source CRS code allocated by authority + * sourceCRSAuthName. + * @param targetCRSAuthName Authority name of targetCRSCode + * @param targetCRSCode Source CRS code allocated by authority + * targetCRSAuthName. + * @param usePROJAlternativeGridNames Whether PROJ alternative grid names + * should be substituted to the official grid names. + * @param discardIfMissingGrid Whether coordinate operations that reference + * missing grids should be removed from the result set. + * @param intermediateCRSAuthCodes List of (auth_name, code) of CRS that can be + * used as potential intermediate CRS. If the list is empty, the database will + * be used to find common CRS in operations involving both the source and + * target CRS. + * @return list of coordinate operations + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +std::vector<operation::CoordinateOperationNNPtr> +AuthorityFactory::createFromCRSCodesWithIntermediates( + const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, + const std::string &targetCRSAuthName, const std::string &targetCRSCode, + bool usePROJAlternativeGridNames, bool discardIfMissingGrid, + const std::vector<std::pair<std::string, std::string>> + &intermediateCRSAuthCodes) const { + + std::vector<operation::CoordinateOperationNNPtr> listTmp; + + if (sourceCRSAuthName == targetCRSAuthName && + sourceCRSCode == targetCRSCode) { + return listTmp; + } + + const std::string sqlProlog( + "SELECT v1.auth_name AS auth_name1, v1.code AS code1, " + "v1.accuracy AS accuracy1, " + "v2.auth_name AS auth_name2, v2.code AS code2, " + "v2.accuracy as accuracy2, " + "a1.south_lat AS south_lat1, " + "a1.west_lon AS west_lon1, " + "a1.north_lat AS north_lat1, " + "a1.east_lon AS east_lon1, " + "a2.south_lat AS south_lat2, " + "a2.west_lon AS west_lon2, " + "a2.north_lat AS north_lat2, " + "a2.east_lon AS east_lon2 " + "FROM coordinate_operation_view v1 " + "JOIN coordinate_operation_view v2 "); + const std::string joinArea( + "JOIN area a1 ON v1.area_of_use_auth_name = a1.auth_name " + "AND v1.area_of_use_code = a1.code " + "JOIN area a2 ON v2.area_of_use_auth_name = a2.auth_name " + "AND v2.area_of_use_code = a2.code "); + const std::string orderBy( + "ORDER BY (CASE WHEN accuracy1 is NULL THEN 1 ELSE 0 END) + " + "(CASE WHEN accuracy2 is NULL THEN 1 ELSE 0 END), " + "accuracy1 + accuracy2"); + + // Case (source->intermediate) and (intermediate->target) + std::string sql( + sqlProlog + "ON v1.target_crs_auth_name = v2.source_crs_auth_name " + "AND v1.target_crs_code = v2.source_crs_code " + + joinArea + + "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? " + "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? "); + auto params = std::vector<SQLValues>{sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode}; + + std::string additionalWhere( + "AND v1.deprecated = 0 AND v2.deprecated = 0 " + "AND intersects_bbox(south_lat1, west_lon1, north_lat1, east_lon1, " + "south_lat2, west_lon2, north_lat2, east_lon2) == 1 "); + if (!getAuthority().empty()) { + additionalWhere += "AND v1.auth_name = ? AND v2.auth_name = ? "; + params.emplace_back(getAuthority()); + params.emplace_back(getAuthority()); + } + std::string intermediateWhere = + buildIntermediateWhere(intermediateCRSAuthCodes, "target", "source"); + for (const auto &pair : intermediateCRSAuthCodes) { + params.emplace_back(pair.first); + params.emplace_back(pair.second); + params.emplace_back(pair.first); + params.emplace_back(pair.second); + } + auto res = + d->run(sql + additionalWhere + intermediateWhere + orderBy, params); + + for (const auto &row : res) { + const auto &auth_name1 = row[0]; + const auto &code1 = row[1]; + // const auto &accuracy1 = row[2]; + const auto &auth_name2 = row[3]; + const auto &code2 = row[4]; + // const auto &accuracy2 = row[5]; + auto op1 = d->createFactory(auth_name1) + ->createCoordinateOperation(code1, true, + usePROJAlternativeGridNames); + if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + auto op2 = d->createFactory(auth_name2) + ->createCoordinateOperation(code2, true, + usePROJAlternativeGridNames); + if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + + listTmp.emplace_back( + operation::ConcatenatedOperation::createComputeMetadata({op1, op2}, + false)); + } + + // Case (source->intermediate) and (target->intermediate) + sql = sqlProlog + "ON v1.target_crs_auth_name = v2.target_crs_auth_name " + "AND v1.target_crs_code = v2.target_crs_code " + + joinArea + + "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? " + "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? "; + intermediateWhere = + buildIntermediateWhere(intermediateCRSAuthCodes, "target", "target"); + res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params); + for (const auto &row : res) { + const auto &auth_name1 = row[0]; + const auto &code1 = row[1]; + // const auto &accuracy1 = row[2]; + const auto &auth_name2 = row[3]; + const auto &code2 = row[4]; + // const auto &accuracy2 = row[5]; + auto op1 = d->createFactory(auth_name1) + ->createCoordinateOperation(code1, true, + usePROJAlternativeGridNames); + if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + auto op2 = d->createFactory(auth_name2) + ->createCoordinateOperation(code2, true, + usePROJAlternativeGridNames); + if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + + listTmp.emplace_back( + operation::ConcatenatedOperation::createComputeMetadata( + {op1, op2->inverse()}, false)); + } + + // Case (intermediate->source) and (intermediate->target) + sql = sqlProlog + "ON v1.source_crs_auth_name = v2.source_crs_auth_name " + "AND v1.source_crs_code = v2.source_crs_code " + + joinArea + + "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? " + "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? "; + intermediateWhere = + buildIntermediateWhere(intermediateCRSAuthCodes, "source", "source"); + res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params); + for (const auto &row : res) { + const auto &auth_name1 = row[0]; + const auto &code1 = row[1]; + // const auto &accuracy1 = row[2]; + const auto &auth_name2 = row[3]; + const auto &code2 = row[4]; + // const auto &accuracy2 = row[5]; + auto op1 = d->createFactory(auth_name1) + ->createCoordinateOperation(code1, true, + usePROJAlternativeGridNames); + if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + auto op2 = d->createFactory(auth_name2) + ->createCoordinateOperation(code2, true, + usePROJAlternativeGridNames); + if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + + listTmp.emplace_back( + operation::ConcatenatedOperation::createComputeMetadata( + {op1->inverse(), op2}, false)); + } + + // Case (intermediate->source) and (target->intermediate) + sql = sqlProlog + "ON v1.source_crs_auth_name = v2.target_crs_auth_name " + "AND v1.source_crs_code = v2.target_crs_code " + + joinArea + + "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? " + "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? "; + intermediateWhere = + buildIntermediateWhere(intermediateCRSAuthCodes, "source", "target"); + res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params); + for (const auto &row : res) { + const auto &auth_name1 = row[0]; + const auto &code1 = row[1]; + // const auto &accuracy1 = row[2]; + const auto &auth_name2 = row[3]; + const auto &code2 = row[4]; + // const auto &accuracy2 = row[5]; + auto op1 = d->createFactory(auth_name1) + ->createCoordinateOperation(code1, true, + usePROJAlternativeGridNames); + if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + auto op2 = d->createFactory(auth_name2) + ->createCoordinateOperation(code2, true, + usePROJAlternativeGridNames); + if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, + targetCRSAuthName, targetCRSCode)) { + continue; + } + + listTmp.emplace_back( + operation::ConcatenatedOperation::createComputeMetadata( + {op1->inverse(), op2->inverse()}, false)); + } + + std::vector<operation::CoordinateOperationNNPtr> list; + for (const auto &op : listTmp) { + if (!d->rejectOpDueToMissingGrid(op, discardIfMissingGrid)) { + list.emplace_back(op); + } + } + + return list; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the authority name associated to this factory. + * @return name. + */ +const std::string &AuthorityFactory::getAuthority() PROJ_CONST_DEFN { + return d->authority(); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the set of authority codes of the given object type. + * + * @param type Object type. + * @param allowDeprecated whether we should return deprecated objects as well. + * @return the set of authority codes for spatial reference objects of the given + * type + * @throw FactoryException + */ +std::set<std::string> +AuthorityFactory::getAuthorityCodes(const ObjectType &type, + bool allowDeprecated) const { + std::string sql; + switch (type) { + case ObjectType::PRIME_MERIDIAN: + sql = "SELECT code FROM prime_meridian WHERE "; + break; + case ObjectType::ELLIPSOID: + sql = "SELECT code FROM ellipsoid WHERE "; + break; + case ObjectType::DATUM: + sql = "SELECT code FROM object_view WHERE table_name IN " + "('geodetic_datum', 'vertical_datum') AND "; + break; + case ObjectType::GEODETIC_REFERENCE_FRAME: + sql = "SELECT code FROM geodetic_datum WHERE "; + break; + case ObjectType::VERTICAL_REFERENCE_FRAME: + sql = "SELECT code FROM vertical_datum WHERE "; + break; + case ObjectType::CRS: + sql = "SELECT code FROM crs_view WHERE "; + break; + case ObjectType::GEODETIC_CRS: + sql = "SELECT code FROM geodetic_crs WHERE "; + break; + case ObjectType::GEOCENTRIC_CRS: + sql = "SELECT code FROM geodetic_crs WHERE type = " GEOCENTRIC " AND "; + break; + case ObjectType::GEOGRAPHIC_CRS: + sql = "SELECT code FROM geodetic_crs WHERE type IN (" GEOG_2D + "," GEOG_3D ") AND "; + break; + case ObjectType::GEOGRAPHIC_2D_CRS: + sql = "SELECT code FROM geodetic_crs WHERE type = " GEOG_2D " AND "; + break; + case ObjectType::GEOGRAPHIC_3D_CRS: + sql = "SELECT code FROM geodetic_crs WHERE type = " GEOG_3D " AND "; + break; + case ObjectType::VERTICAL_CRS: + sql = "SELECT code FROM vertical_crs WHERE "; + break; + case ObjectType::PROJECTED_CRS: + sql = "SELECT code FROM projected_crs WHERE "; + break; + case ObjectType::COMPOUND_CRS: + sql = "SELECT code FROM compound_crs WHERE "; + break; + case ObjectType::COORDINATE_OPERATION: + sql = + "SELECT code FROM coordinate_operation_with_conversion_view WHERE "; + break; + case ObjectType::CONVERSION: + sql = "SELECT code FROM conversion WHERE "; + break; + case ObjectType::TRANSFORMATION: + sql = "SELECT code FROM coordinate_operation_view WHERE table_name != " + "'concatenated_operation' AND "; + break; + case ObjectType::CONCATENATED_OPERATION: + sql = "SELECT code FROM concatenated_operation WHERE "; + break; + } + + sql += "auth_name = ?"; + if (!allowDeprecated) { + sql += " AND deprecated != 1"; + } + + auto res = d->run(sql, {getAuthority()}); + std::set<std::string> set; + for (const auto &row : res) { + set.insert(row[0]); + } + return set; +} + +// --------------------------------------------------------------------------- + +/** \brief Gets a description of the object corresponding to a code. + * + * \note In case of several objects of different types with the same code, + * one of them will be arbitrarily selected. + * + * @param code Object code allocated by authority. (e.g. "4326") + * @return description. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ +std::string +AuthorityFactory::getDescriptionText(const std::string &code) const { + auto sql = "SELECT name FROM object_view WHERE auth_name = ? AND code = " + "? ORDER BY table_name"; + auto res = d->runWithCodeParam(sql, code); + if (res.empty()) { + throw NoSuchAuthorityCodeException("object not found", getAuthority(), + code); + } + return res[0][0]; +} + +// --------------------------------------------------------------------------- + +/** \brief Gets the official name from a possibly alias name. + * + * @param aliasedName Alias name. + * @param tableName Table name/category. Can help in case of ambiguities. + * Or empty otherwise. + * @param source Source of the alias. Can help in case of ambiguities. + * Or empty otherwise. + * @param outTableName Table name in which the official name has been found. + * @param outAuthName Authority name of the official name that has been found. + * @param outCode Code of the official name that has been found. + * @return official name (or empty if not found). + * @throw FactoryException + */ +std::string AuthorityFactory::getOfficialNameFromAlias( + const std::string &aliasedName, const std::string &tableName, + const std::string &source, std::string &outTableName, + std::string &outAuthName, std::string &outCode) const { + std::string sql("SELECT table_name, auth_name, code FROM alias_name WHERE " + "alt_name = ?"); + std::vector<SQLValues> params{aliasedName}; + if (!tableName.empty()) { + sql += " AND table_name = ?"; + params.push_back(tableName); + } + if (!source.empty()) { + sql += " AND source = ?"; + params.push_back(source); + } + auto res = d->run(sql, params); + if (res.empty()) { + return std::string(); + } + outTableName = res[0][0]; + outAuthName = res[0][1]; + outCode = res[0][2]; + sql = "SELECT name FROM \""; + sql += replaceAll(outTableName, "\"", "\"\""); + sql += "\" WHERE auth_name = ? AND code = ?"; + res = d->run(sql, {outAuthName, outCode}); + if (res.empty()) { // shouldn't happen normally + return std::string(); + } + return res[0][0]; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static void addToListString(std::string &out, const char *in) { + if (!out.empty()) { + out += ','; + } + out += in; +} + +static void addToListStringWithOR(std::string &out, const char *in) { + if (!out.empty()) { + out += " OR "; + } + out += in; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return a list of objects by their name + * + * @param searchedName Searched name. Must be at least 2 character long. + * @param allowedObjectTypes List of object types into which to search. If + * empty, all object types will be searched. + * @param approximateMatch Whether approximate name identification is allowed. + * @param limitResultCount Maximum number of results to return. + * Or 0 for unlimited. + * @return list of matched objects. + * @throw FactoryException + */ +std::list<common::IdentifiedObjectNNPtr> +AuthorityFactory::createObjectsFromName( + const std::string &searchedName, + const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch, + size_t limitResultCount) { + + std::string searchedNameWithoutDeprecated(searchedName); + bool deprecated = false; + if (ends_with(searchedNameWithoutDeprecated, " (deprecated)")) { + deprecated = true; + searchedNameWithoutDeprecated.resize( + searchedNameWithoutDeprecated.size() - strlen(" (deprecated)")); + } + + const std::string canonicalizedSearchedName( + metadata::Identifier::canonicalizeName(searchedNameWithoutDeprecated)); + if (canonicalizedSearchedName.size() <= 1) { + return {}; + } + + std::string sql( + "SELECT table_name, auth_name, code, name FROM object_view WHERE " + "deprecated = ? AND "); + std::vector<SQLValues> params{deprecated ? 1.0 : 0.0}; + if (!approximateMatch) { + sql += "name LIKE ? AND "; + params.push_back(searchedNameWithoutDeprecated); + } + if (!getAuthority().empty()) { + sql += " auth_name = ? AND "; + params.emplace_back(getAuthority()); + } + + 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 " AND " + "type = " GEOCENTRIC ")"); + break; + case ObjectType::GEOGRAPHIC_CRS: + addToListStringWithOR(otherConditions, + "(table_name = 'geodetic_crs' AND " + "type IN (" GEOG_2D "," GEOG_3D "))"); + break; + case ObjectType::GEOGRAPHIC_2D_CRS: + addToListStringWithOR(otherConditions, + "(table_name = 'geodetic_crs' AND " + "type = " GEOG_2D ")"); + break; + case ObjectType::GEOGRAPHIC_3D_CRS: + addToListStringWithOR(otherConditions, + "(table_name = 'geodetic_crs' AND " + "type = " GEOG_3D ")"); + 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; + } + sql += ')'; + } else if (!otherConditions.empty()) { + sql += "("; + sql += otherConditions; + sql += ')'; + } + } + sql += " ORDER BY length(name), name"; + if (limitResultCount > 0 && + limitResultCount < + static_cast<size_t>(std::numeric_limits<int>::max()) && + !approximateMatch) { + sql += " LIMIT "; + sql += toString(static_cast<int>(limitResultCount)); + } + + std::list<common::IdentifiedObjectNNPtr> res; + auto sqlRes = d->run(sql, params); + for (const auto &row : sqlRes) { + const auto &name = row[3]; + if (approximateMatch) { + bool match = ci_find(name, searchedNameWithoutDeprecated) != + std::string::npos; + if (!match) { + const auto canonicalizedName( + metadata::Identifier::canonicalizeName(name)); + match = ci_find(canonicalizedName, canonicalizedSearchedName) != + std::string::npos; + } + if (!match) { + continue; + } + } + const auto &table_name = row[0]; + const auto &auth_name = row[1]; + const auto &code = row[2]; + auto factory = d->createFactory(auth_name); + if (table_name == "prime_meridian") { + res.emplace_back(factory->createPrimeMeridian(code)); + } else if (table_name == "ellipsoid") { + res.emplace_back(factory->createEllipsoid(code)); + } else if (table_name == "geodetic_datum") { + res.emplace_back(factory->createGeodeticDatum(code)); + } else if (table_name == "vertical_datum") { + res.emplace_back(factory->createVerticalDatum(code)); + } else if (table_name == "geodetic_crs") { + res.emplace_back(factory->createGeodeticCRS(code)); + } else if (table_name == "projected_crs") { + res.emplace_back(factory->createProjectedCRS(code)); + } else if (table_name == "vertical_crs") { + res.emplace_back(factory->createVerticalCRS(code)); + } else if (table_name == "compound_crs") { + res.emplace_back(factory->createCompoundCRS(code)); + } else if (table_name == "conversion") { + res.emplace_back(factory->createConversion(code)); + } else if (table_name == "grid_transformation" || + table_name == "helmert_transformation" || + table_name == "other_transformation" || + table_name == "concatenated_operation") { + res.emplace_back(factory->createCoordinateOperation(code, true)); + } else { + assert(false); + } + if (limitResultCount > 0 && res.size() == limitResultCount) { + break; + } + } + + auto sortLambda = [](const common::IdentifiedObjectNNPtr &a, + const common::IdentifiedObjectNNPtr &b) { + const auto &aName = a->nameStr(); + const auto &bName = b->nameStr(); + if (aName.size() < bName.size()) { + return true; + } + if (aName.size() > bName.size()) { + return false; + } + + const auto &aIds = a->identifiers(); + const auto &bIds = b->identifiers(); + if (aIds.size() < bIds.size()) { + return true; + } + if (aIds.size() > bIds.size()) { + return false; + } + for (size_t idx = 0; idx < aIds.size(); idx++) { + const auto &aCodeSpace = *aIds[idx]->codeSpace(); + const auto &bCodeSpace = *bIds[idx]->codeSpace(); + const auto codeSpaceComparison = aCodeSpace.compare(bCodeSpace); + if (codeSpaceComparison < 0) { + return true; + } + if (codeSpaceComparison > 0) { + return false; + } + const auto &aCode = aIds[idx]->code(); + const auto &bCode = bIds[idx]->code(); + const auto codeComparison = aCode.compare(bCode); + if (codeComparison < 0) { + return true; + } + if (codeComparison > 0) { + return false; + } + } + return strcmp(typeid(a.get()).name(), typeid(b.get()).name()) < 0; + }; + + res.sort(sortLambda); + + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::list<datum::EllipsoidNNPtr> AuthorityFactory::createEllipsoidFromExisting( + const datum::EllipsoidNNPtr &ellipsoid) const { + std::string sql( + "SELECT auth_name, code FROM ellipsoid WHERE " + "abs(semi_major_axis - ?) < 1e-10 * abs(semi_major_axis) AND " + "((semi_minor_axis IS NOT NULL AND " + "abs(semi_minor_axis - ?) < 1e-10 * abs(semi_minor_axis)) OR " + "((inv_flattening IS NOT NULL AND " + "abs(inv_flattening - ?) < 1e-10 * abs(inv_flattening))))"); + std::vector<SQLValues> params{ + ellipsoid->semiMajorAxis().getSIValue(), + ellipsoid->computeSemiMinorAxis().getSIValue(), + ellipsoid->computedInverseFlattening()}; + auto sqlRes = d->run(sql, params); + std::list<datum::EllipsoidNNPtr> res; + for (const auto &row : sqlRes) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + res.emplace_back(d->createFactory(auth_name)->createEllipsoid(code)); + } + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum( + const std::string &datum_auth_name, const std::string &datum_code, + const std::string &geodetic_crs_type) const { + std::string sql( + "SELECT auth_name, code FROM geodetic_crs WHERE " + "datum_auth_name = ? AND datum_code = ? AND deprecated = 0"); + std::vector<SQLValues> params{datum_auth_name, datum_code}; + if (!getAuthority().empty()) { + sql += " AND auth_name = ?"; + params.emplace_back(getAuthority()); + } + if (!geodetic_crs_type.empty()) { + sql += " AND type = ?"; + params.emplace_back(geodetic_crs_type); + } + auto sqlRes = d->run(sql, params); + std::list<crs::GeodeticCRSNNPtr> res; + for (const auto &row : sqlRes) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code)); + } + return res; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::list<crs::GeodeticCRSNNPtr> +AuthorityFactory::createGeodeticCRSFromEllipsoid( + const std::string &ellipsoid_auth_name, const std::string &ellipsoid_code, + const std::string &geodetic_crs_type) const { + std::string sql( + "SELECT geodetic_crs.auth_name, geodetic_crs.code FROM geodetic_crs " + "JOIN geodetic_datum ON " + "geodetic_crs.datum_auth_name = geodetic_datum.auth_name AND " + "geodetic_crs.datum_code = geodetic_datum.code WHERE " + "geodetic_datum.ellipsoid_auth_name = ? AND " + "geodetic_datum.ellipsoid_code = ? AND " + "geodetic_datum.deprecated = 0 AND " + "geodetic_crs.deprecated = 0"); + std::vector<SQLValues> params{ellipsoid_auth_name, ellipsoid_code}; + if (!getAuthority().empty()) { + sql += " AND geodetic_crs.auth_name = ?"; + params.emplace_back(getAuthority()); + } + if (!geodetic_crs_type.empty()) { + sql += " AND geodetic_crs.type = ?"; + params.emplace_back(geodetic_crs_type); + } + auto sqlRes = d->run(sql, params); + std::list<crs::GeodeticCRSNNPtr> res; + for (const auto &row : sqlRes) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code)); + } + return res; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static std::string buildSqlLookForAuthNameCode( + const std::list<std::pair<crs::CRSNNPtr, int>> &list, + std::vector<SQLValues> ¶ms, const char *prefixField) { + std::string sql("("); + + std::set<std::string> authorities; + for (const auto &crs : list) { + const auto &ids = crs.first->identifiers(); + if (!ids.empty()) { + authorities.insert(*(ids[0]->codeSpace())); + } + } + bool firstAuth = true; + for (const auto &auth_name : authorities) { + if (!firstAuth) { + sql += " OR "; + } + firstAuth = false; + sql += "( "; + sql += prefixField; + sql += "auth_name = ? AND "; + sql += prefixField; + sql += "code IN ("; + params.emplace_back(auth_name); + bool firstGeodCRSForAuth = true; + for (const auto &crs : list) { + const auto &ids = crs.first->identifiers(); + if (!ids.empty() && *(ids[0]->codeSpace()) == auth_name) { + if (!firstGeodCRSForAuth) { + sql += ','; + } + firstGeodCRSForAuth = false; + sql += '?'; + params.emplace_back(ids[0]->code()); + } + } + sql += "))"; + } + sql += ')'; + return sql; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::list<crs::ProjectedCRSNNPtr> +AuthorityFactory::createProjectedCRSFromExisting( + const crs::ProjectedCRSNNPtr &crs) const { + std::list<crs::ProjectedCRSNNPtr> res; + + const auto &conv = crs->derivingConversionRef(); + const auto &method = conv->method(); + const auto methodEPSGCode = method->getEPSGCode(); + if (methodEPSGCode == 0) { + return res; + } + + auto lockedThisFactory(d->getSharedFromThis()); + assert(lockedThisFactory); + const auto &baseCRS(crs->baseCRS()); + auto candidatesGeodCRS = baseCRS->crs::CRS::identify(lockedThisFactory); + auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(baseCRS.get()); + if (geogCRS) { + const auto axisOrder = geogCRS->coordinateSystem()->axisOrder(); + if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH || + axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) { + const auto &unit = + geogCRS->coordinateSystem()->axisList()[0]->unit(); + auto otherOrderGeogCRS = crs::GeographicCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + geogCRS->nameStr()), + geogCRS->datum(), geogCRS->datumEnsemble(), + axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH + ? cs::EllipsoidalCS::createLatitudeLongitude(unit) + : cs::EllipsoidalCS::createLongitudeLatitude(unit)); + auto otherCandidatesGeodCRS = + otherOrderGeogCRS->crs::CRS::identify(lockedThisFactory); + candidatesGeodCRS.insert(candidatesGeodCRS.end(), + otherCandidatesGeodCRS.begin(), + otherCandidatesGeodCRS.end()); + } + } + + std::string sql( + "SELECT projected_crs.auth_name, projected_crs.code FROM projected_crs " + "JOIN conversion ON " + "projected_crs.conversion_auth_name = conversion.auth_name AND " + "projected_crs.conversion_code = conversion.code WHERE " + "projected_crs.deprecated = 0 AND "); + std::vector<SQLValues> params; + if (!candidatesGeodCRS.empty()) { + sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params, + "projected_crs.geodetic_crs_"); + sql += " AND "; + } + sql += "conversion.method_auth_name = 'EPSG' AND " + "conversion.method_code = ?"; + params.emplace_back(toString(methodEPSGCode)); + if (!getAuthority().empty()) { + sql += " AND projected_crs.auth_name = ?"; + params.emplace_back(getAuthority()); + } + + int iParam = 1; + for (const auto &genOpParamvalue : conv->parameterValues()) { + auto opParamvalue = + dynamic_cast<const operation::OperationParameterValue *>( + genOpParamvalue.get()); + if (!opParamvalue) { + break; + } + const auto paramEPSGCode = opParamvalue->parameter()->getEPSGCode(); + const auto ¶meterValue = opParamvalue->parameterValue(); + if (!(paramEPSGCode > 0 && + parameterValue->type() == + operation::ParameterValue::Type::MEASURE)) { + break; + } + const auto &measure = parameterValue->value(); + const auto &unit = measure.unit(); + if (unit == common::UnitOfMeasure::DEGREE && + geogCRS->coordinateSystem()->axisList()[0]->unit() == unit) { + const auto iParamAsStr(toString(iParam)); + sql += " AND conversion.param"; + sql += iParamAsStr; + sql += "_code = ? AND conversion.param"; + sql += iParamAsStr; + sql += "_auth_name = 'EPSG' AND conversion.param"; + sql += iParamAsStr; + sql += "_value BETWEEN ? AND ?"; + // As angles might be expressed with the odd unit EPSG:9110 + // "sexagesimal DMS", we have to provide a broad range + params.emplace_back(toString(paramEPSGCode)); + params.emplace_back(measure.value() - 1); + params.emplace_back(measure.value() + 1); + } + iParam++; + } + auto sqlRes = d->run(sql, params); + + params.clear(); + + sql = "SELECT auth_name, code FROM projected_crs WHERE " + "deprecated = 0 AND conversion_auth_name IS NULL AND "; + if (!candidatesGeodCRS.empty()) { + sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params, + "geodetic_crs_"); + sql += " AND "; + } + + const auto escapeLikeStr = [](const std::string &str) { + return replaceAll(replaceAll(replaceAll(str, "\\", "\\\\"), "_", "\\_"), + "%", "\\%"); + }; + + const auto ellpsSemiMajorStr = + toString(baseCRS->ellipsoid()->semiMajorAxis().getSIValue(), 10); + + sql += "(text_definition LIKE ? ESCAPE '\\'"; + + // WKT2 definition + { + std::string patternVal("%"); + + patternVal += ','; + patternVal += ellpsSemiMajorStr; + patternVal += '%'; + + patternVal += escapeLikeStr(method->nameStr()); + patternVal += '%'; + + params.emplace_back(patternVal); + } + + const auto *mapping = getMapping(method.get()); + if (mapping && mapping->proj_name_main) { + sql += " OR (text_definition LIKE ? AND (text_definition LIKE ?"; + + std::string patternVal("%"); + patternVal += "proj="; + patternVal += mapping->proj_name_main; + patternVal += '%'; + params.emplace_back(patternVal); + + // could be a= or R= + patternVal = "%="; + patternVal += ellpsSemiMajorStr; + patternVal += '%'; + params.emplace_back(patternVal); + + std::string projEllpsName; + std::string ellpsName; + if (baseCRS->ellipsoid()->lookForProjWellKnownEllps(projEllpsName, + ellpsName)) { + sql += " OR text_definition LIKE ?"; + // Could be ellps= or datum= + patternVal = "%="; + patternVal += projEllpsName; + patternVal += '%'; + params.emplace_back(patternVal); + } + + sql += "))"; + } + + // WKT1_GDAL definition + const char *wkt1GDALMethodName = conv->getWKT1GDALMethodName(); + if (wkt1GDALMethodName) { + sql += " OR text_definition LIKE ? ESCAPE '\\'"; + std::string patternVal("%"); + + patternVal += ','; + patternVal += ellpsSemiMajorStr; + patternVal += '%'; + + patternVal += escapeLikeStr(wkt1GDALMethodName); + patternVal += '%'; + + params.emplace_back(patternVal); + } + + // WKT1_ESRI definition + const char *esriMethodName = conv->getESRIMethodName(); + if (esriMethodName) { + sql += " OR text_definition LIKE ? ESCAPE '\\'"; + std::string patternVal("%"); + + patternVal += ','; + patternVal += ellpsSemiMajorStr; + patternVal += '%'; + + patternVal += escapeLikeStr(esriMethodName); + patternVal += '%'; + + auto fe = + &conv->parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING); + if (*fe == Measure()) { + fe = &conv->parameterValueMeasure( + EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN); + } + if (!(*fe == Measure())) { + patternVal += "PARAMETER[\"False\\_Easting\","; + patternVal += + toString(fe->convertToUnit( + crs->coordinateSystem()->axisList()[0]->unit()), + 10); + patternVal += '%'; + } + + auto lat = &conv->parameterValueMeasure( + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); + if (*lat == Measure()) { + lat = &conv->parameterValueMeasure( + EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN); + } + if (!(*lat == Measure())) { + patternVal += "PARAMETER[\"Latitude\\_Of\\_Origin\","; + const auto &angularUnit = + dynamic_cast<crs::GeographicCRS *>(crs->baseCRS().get()) + ? crs->baseCRS()->coordinateSystem()->axisList()[0]->unit() + : UnitOfMeasure::DEGREE; + patternVal += toString(lat->convertToUnit(angularUnit), 10); + patternVal += '%'; + } + + params.emplace_back(patternVal); + } + sql += ")"; + if (!getAuthority().empty()) { + sql += " AND auth_name = ?"; + params.emplace_back(getAuthority()); + } + + auto sqlRes2 = d->run(sql, params); + + if (sqlRes.size() <= 200) { + for (const auto &row : sqlRes) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + res.emplace_back( + d->createFactory(auth_name)->createProjectedCRS(code)); + } + } + if (sqlRes2.size() <= 200) { + for (const auto &row : sqlRes2) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + res.emplace_back( + d->createFactory(auth_name)->createProjectedCRS(code)); + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +std::list<crs::CompoundCRSNNPtr> +AuthorityFactory::createCompoundCRSFromExisting( + const crs::CompoundCRSNNPtr &crs) const { + std::list<crs::CompoundCRSNNPtr> res; + + auto lockedThisFactory(d->getSharedFromThis()); + assert(lockedThisFactory); + + const auto &components = crs->componentReferenceSystems(); + if (components.size() != 2) { + return res; + } + auto candidatesHorizCRS = components[0]->identify(lockedThisFactory); + auto candidatesVertCRS = components[1]->identify(lockedThisFactory); + if (candidatesHorizCRS.empty() && candidatesVertCRS.empty()) { + return res; + } + + std::string sql("SELECT auth_name, code FROM compound_crs WHERE " + "deprecated = 0 AND "); + std::vector<SQLValues> params; + bool addAnd = false; + if (!candidatesHorizCRS.empty()) { + sql += buildSqlLookForAuthNameCode(candidatesHorizCRS, params, + "horiz_crs_"); + addAnd = true; + } + if (!candidatesVertCRS.empty()) { + if (addAnd) { + sql += " AND "; + } + sql += buildSqlLookForAuthNameCode(candidatesVertCRS, params, + "vertical_crs_"); + addAnd = true; + } + if (!getAuthority().empty()) { + if (addAnd) { + sql += " AND "; + } + sql += "auth_name = ?"; + params.emplace_back(getAuthority()); + } + + auto sqlRes = d->run(sql, params); + for (const auto &row : sqlRes) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + res.emplace_back(d->createFactory(auth_name)->createCompoundCRS(code)); + } + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +FactoryException::FactoryException(const char *message) : Exception(message) {} + +// --------------------------------------------------------------------------- + +FactoryException::FactoryException(const std::string &message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +FactoryException::~FactoryException() = default; + +// --------------------------------------------------------------------------- + +FactoryException::FactoryException(const FactoryException &) = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +struct NoSuchAuthorityCodeException::Private { + std::string authority_; + std::string code_; + + Private(const std::string &authority, const std::string &code) + : authority_(authority), code_(code) {} +}; + +// --------------------------------------------------------------------------- + +NoSuchAuthorityCodeException::NoSuchAuthorityCodeException( + const std::string &message, const std::string &authority, + const std::string &code) + : FactoryException(message), + d(internal::make_unique<Private>(authority, code)) {} + +// --------------------------------------------------------------------------- + +NoSuchAuthorityCodeException::~NoSuchAuthorityCodeException() = default; + +// --------------------------------------------------------------------------- + +NoSuchAuthorityCodeException::NoSuchAuthorityCodeException( + const NoSuchAuthorityCodeException &other) + : FactoryException(other), d(internal::make_unique<Private>(*(other.d))) {} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns authority name. */ +const std::string &NoSuchAuthorityCodeException::getAuthority() const { + return d->authority_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns authority code. */ +const std::string &NoSuchAuthorityCodeException::getAuthorityCode() const { + return d->code_; +} + +// --------------------------------------------------------------------------- + +} // namespace io +NS_PROJ_END diff --git a/src/general_doc.dox b/src/general_doc.dox new file mode 100644 index 00000000..ce5a8130 --- /dev/null +++ b/src/general_doc.dox @@ -0,0 +1,158 @@ +/*! + +\page general_doc General documentation + +\section general_api_design General API design + +The design of the class hierarchy is strongly derived from \ref ISO_19111_2018. + +Classes for which the constructors are not directly accessible have their +instances constructed with create() methods. The returned object is a non-null +shared pointer. Such objects are immutable, and thread-safe. + +TODO + +\section general_properties General properties + +All classes deriving from IdentifiedObject have general properties that can +be defined at creation time. Those properties are: + +<ul> +<li>osgeo::proj::metadata::Identifier::DESCRIPTION_KEY ("description"): the +natural language description of the meaning of the code value, provided a a string.</li> +<li>osgeo::proj::metadata::Identifier::CODE_KEY ("code"): a numeric or +alphanumeric code, provided as a integer or a string. For example 4326, for +the EPSG:4326 "WGS84" GeographicalCRS</li> +<li>osgeo::proj::metadata::Identifier::CODESPACE_KEY ("codespace"): the organization +responsible for definition and maintenance of the code., provided a a string. +For example "EPSG".</li> +<li>osgeo::proj::metadata::Identifier::VERSION_KEY ("version"): the version +identifier for the namespace, provided a a string.</li> +<li>osgeo::proj::metadata::Identifier::AUTHORITY_KEY ("authority"): a citation for the authority, +provided as a string or a osgeo::proj::metadata::Citation object. Often unused</li> +<li>osgeo::proj::metadata::Identifier::URI_KEY ("uri"): the URI of the identifier, +provided as a string. Often unused</li> + +<li>osgeo::proj::common::IdentifiedObject::NAME_KEY ("name"): the name of a +osgeo::proj::common::IdentifiedObject, provided as a string or +osgeo::proj::metadata::IdentifierNNPtr.</li> +<li>osgeo::proj::common::IdentifiedObject::IDENTIFIERS_KEY ("identifiers"): +the identifier(s) of a osgeo::proj::common::IdentifiedObject, provided as +a osgeo::proj::common::IdentifierNNPtr or a +osgeo::proj::util::ArrayOfBaseObjectNNPtr of +osgeo::proj::metadata::IdentifierNNPtr.</li> +<li>osgeo::proj::common::IdentifiedObject::ALIAS_KEY ("alias"): +the alias(es) of a osgeo::proj::common::IdentifiedObject, +provided as string, a osgeo::proj::util::GenericNameNNPtr or a +osgeo::proj::util::ArrayOfBaseObjectNNPtr +of osgeo::proj::util::GenericNameNNPtr.</li> +<li>osgeo::proj::common::IdentifiedObject::REMARKS_KEY ("remarks"): +the remarks of a osgeo::proj::common::IdentifiedObject, +provided as a string.</li> +<li>osgeo::proj::common::IdentifiedObject::DEPRECATED_KEY ("deprecated"): +the deprecation flag of a osgeo::proj::common::IdentifiedObject, +provided as a boolean.</li> + +<li>osgeo::proj::common::ObjectUsage::SCOPE_KEY ("scope"): +the scope of a osgeo::proj::common::ObjectUsage, provided as a string.</li> +<li>osgeo::proj::common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY ("domainOfValidity"): +the domain of validity of a osgeo::proj::common::ObjectUsage, +provided as a osgeo::proj::metadata::ExtentNNPtr.</li> +<li>osgeo::proj::common::ObjectUsage::OBJECT_DOMAIN_KEY ("objectDomain"): +the object domain(s) of a osgeo::proj::common::ObjectUsage, +provided as a osgeo::proj::common::ObjectDomainNNPtr or a +osgeo::proj::util::ArrayOfBaseObjectNNPtr +of osgeo::proj::common::ObjectDomainNNPtr.</li> + +</ul> + +\section standards Applicable standards + +\subsection ISO_19111 ISO:19111 / OGC Topic 2 standard + +Topic 2 - Spatial referencing by coordinates. + +This is an Abstract Specification describes the data elements, relationships and +associated metadata required for spatial referencing by coordinates. It describes +Coordinate Reference Systems (CRS), coordinate systems (CS) and coordinate +transformation or coordinate conversion between two different coordinate +reference systems. + +\subsubsection ISO_19111_2018 ISO 19111:2018 + +This is the revision mostly used for PROJ C++ modelling. + +[OGC 18-005r1, 2018-04-04, ISO 19111:2018] +(https://portal.opengeospatial.org/files/?artifact_id=78556) +(not yet adopted, at time of writing) + +\subsubsection ISO_19111_2007 ISO 19111:2007 + +The precedent version of the specification was: +[OGC 08-015r2, 2010-04-27, ISO 19111:2007] +(http://portal.opengeospatial.org/files/?artifact_id=39049) + +\subsection WKT2 WKT2 standard + +Well-known text representation of coordinate reference systems. + +Well-known Text (WKT) offers a compact machine- and human-readable +representation of the critical elements of coordinate reference system (CRS) +definitions, and coordinate operations. This is an implementation of +\ref ISO_19111 + +PROJ implements the two following revisions of the standard: + +\subsubsection WKT2_2018 WKT2:2018 + +[OGC 18-010r1, 2018-06-08, WKT2-2018] +(https://portal.opengeospatial.org/files/?artifact_id=79826) +(not yet adopted, at time of writing) + +\subsubsection WKT2_2015 WKT2:2015 + +[OGC 12-063r5, 2015-05-01, WKT2-2015] +(http://docs.opengeospatial.org/is/12-063r5/12-063r5.html) + +\subsection WKT1 WKT1 specification + +Older specifications of well-known text representation of coordinate reference +systems are also supported by PROJ, mostly for compatibility with legacy +systems, or older versions of GDAL. + +GDAL v2.3 and earlier mostly implements: + +[OGC 01-009, 2001-01-12, OpenGIS Coordinate Transformation Service Implementation Specification] +(http://portal.opengeospatial.org/files/?artifact_id=999) + +The [GDAL documentation, OGC WKT Coordinate System Issues] +(http://www.gdal.org/wktproblems.html) discusses issues, and GDAL implementation +choices. + +An older specification of WKT1 is/was used by some software packages: + +[OGC 99-049, 1999-05-05, OpenGIS Simple Features Specification For SQL v1.1] +(http://portal.opengeospatial.org/files/?artifact_id=829) + +\subsection ISO_19115 ISO 19115 (Metadata) + +Defines the schema required for describing geographic information and services. +It provides information about the identification, the extent, the quality, the +spatial and temporal schema, spatial reference, and distribution of digital +geographic data. + +PROJ implements a simplified subset of ISO 19115. + +\subsection GeoAPI GeoAPI + +A set of Java and Python language programming interfaces for geospatial +applications. + +[GeoAPI main page](http://www.geoapi.org/) + +[GeoAPI Javadoc](http://www.geoapi.org/3.0/javadoc/index.html) + +[OGC GeoAPI Implementation Specification] +(http://www.opengeospatial.org/standards/geoapi) + +*/ diff --git a/src/geodesic.h b/src/geodesic.h index 0e18af5a..11484ec7 100644 --- a/src/geodesic.h +++ b/src/geodesic.h @@ -157,6 +157,20 @@ GEODESIC_VERSION_MINOR, \ GEODESIC_VERSION_PATCH) +#ifndef GEOD_DLL +#if defined(_MSC_VER) +#define GEOD_DLL __declspec(dllexport) +#elif defined(__GNUC__) +#define GEOD_DLL __attribute__ ((visibility("default"))) +#else +#define GEOD_DLL +#endif +#endif + +#ifdef PROJ_RENAME_SYMBOLS +#include "proj_symbol_rename.h" +#endif + #if defined(__cplusplus) extern "C" { #endif @@ -224,7 +238,7 @@ extern "C" { * @param[in] a the equatorial radius (meters). * @param[in] f the flattening. **********************************************************************/ - void geod_init(struct geod_geodesic* g, double a, double f); + void GEOD_DLL geod_init(struct geod_geodesic* g, double a, double f); /** * Solve the direct geodesic problem. @@ -262,7 +276,7 @@ extern "C" { printf("%.5f %.5f\n", lat, lon); @endcode **********************************************************************/ - void geod_direct(const struct geod_geodesic* g, + void GEOD_DLL geod_direct(const struct geod_geodesic* g, double lat1, double lon1, double azi1, double s12, double* plat2, double* plon2, double* pazi2); @@ -304,7 +318,7 @@ extern "C" { * that the quantity \e lon2 − \e lon1 indicates how many times and in * what sense the geodesic encircles the ellipsoid. **********************************************************************/ - double geod_gendirect(const struct geod_geodesic* g, + double GEOD_DLL geod_gendirect(const struct geod_geodesic* g, double lat1, double lon1, double azi1, unsigned flags, double s12_a12, double* plat2, double* plon2, double* pazi2, @@ -349,7 +363,7 @@ extern "C" { printf("%.3f\n", s12); @endcode **********************************************************************/ - void geod_inverse(const struct geod_geodesic* g, + void GEOD_DLL geod_inverse(const struct geod_geodesic* g, double lat1, double lon1, double lat2, double lon2, double* ps12, double* pazi1, double* pazi2); @@ -380,7 +394,7 @@ extern "C" { * "return" arguments \e ps12, etc., may be replaced by 0, if you do not need * some quantities computed. **********************************************************************/ - double geod_geninverse(const struct geod_geodesic* g, + double GEOD_DLL geod_geninverse(const struct geod_geodesic* g, double lat1, double lon1, double lat2, double lon2, double* ps12, double* pazi1, double* pazi2, double* pm12, double* pM12, double* pM21, @@ -425,7 +439,7 @@ extern "C" { * When initialized by this function, point 3 is undefined (l->s13 = l->a13 = * NaN). **********************************************************************/ - void geod_lineinit(struct geod_geodesicline* l, + void GEOD_DLL geod_lineinit(struct geod_geodesicline* l, const struct geod_geodesic* g, double lat1, double lon1, double azi1, unsigned caps); @@ -450,7 +464,7 @@ extern "C" { * 2 of the direct geodesic problem. See geod_lineinit() for more * information. **********************************************************************/ - void geod_directline(struct geod_geodesicline* l, + void GEOD_DLL geod_directline(struct geod_geodesicline* l, const struct geod_geodesic* g, double lat1, double lon1, double azi1, double s12, unsigned caps); @@ -480,7 +494,7 @@ extern "C" { * 2 of the direct geodesic problem. See geod_lineinit() for more * information. **********************************************************************/ - void geod_gendirectline(struct geod_geodesicline* l, + void GEOD_DLL geod_gendirectline(struct geod_geodesicline* l, const struct geod_geodesic* g, double lat1, double lon1, double azi1, unsigned flags, double s12_a12, @@ -506,7 +520,7 @@ extern "C" { * 2 of the inverse geodesic problem. See geod_lineinit() for more * information. **********************************************************************/ - void geod_inverseline(struct geod_geodesicline* l, + void GEOD_DLL geod_inverseline(struct geod_geodesicline* l, const struct geod_geodesic* g, double lat1, double lon1, double lat2, double lon2, unsigned caps); @@ -556,7 +570,7 @@ extern "C" { } @endcode **********************************************************************/ - void geod_position(const struct geod_geodesicline* l, double s12, + void GEOD_DLL geod_position(const struct geod_geodesicline* l, double s12, double* plat2, double* plon2, double* pazi2); /** @@ -623,7 +637,7 @@ extern "C" { } @endcode **********************************************************************/ - double geod_genposition(const struct geod_geodesicline* l, + double GEOD_DLL geod_genposition(const struct geod_geodesicline* l, unsigned flags, double s12_a12, double* plat2, double* plon2, double* pazi2, double* ps12, double* pm12, @@ -640,7 +654,7 @@ extern "C" { * This is only useful if the geod_geodesicline object has been constructed * with \e caps |= GEOD_DISTANCE_IN. **********************************************************************/ - void geod_setdistance(struct geod_geodesicline* l, double s13); + void GEOD_DLL geod_setdistance(struct geod_geodesicline* l, double s13); /** * Specify position of point 3 in terms of either distance or arc length. @@ -657,7 +671,7 @@ extern "C" { * GEOD_ARCMODE, the \e s13 is only set if the geod_geodesicline object has * been constructed with \e caps |= GEOD_DISTANCE. **********************************************************************/ - void geod_gensetdistance(struct geod_geodesicline* l, + void GEOD_DLL geod_gensetdistance(struct geod_geodesicline* l, unsigned flags, double s13_a13); /** @@ -679,14 +693,14 @@ extern "C" { * An example of the use of this function is given in the documentation for * geod_polygon_compute(). **********************************************************************/ - void geod_polygon_init(struct geod_polygon* p, int polylinep); + void GEOD_DLL geod_polygon_init(struct geod_polygon* p, int polylinep); /** * Clear the polygon, allowing a new polygon to be started. * * @param[in,out] p a pointer to the object to be cleared. **********************************************************************/ - void geod_polygon_clear(struct geod_polygon* p); + void GEOD_DLL geod_polygon_clear(struct geod_polygon* p); /** * Add a point to the polygon or polyline. @@ -706,7 +720,7 @@ extern "C" { * An example of the use of this function is given in the documentation for * geod_polygon_compute(). **********************************************************************/ - void geod_polygon_addpoint(const struct geod_geodesic* g, + void GEOD_DLL geod_polygon_addpoint(const struct geod_geodesic* g, struct geod_polygon* p, double lat, double lon); @@ -726,7 +740,7 @@ extern "C" { * added yet. The \e lat and \e lon fields of \e p give the location of the * new vertex. **********************************************************************/ - void geod_polygon_addedge(const struct geod_geodesic* g, + void GEOD_DLL geod_polygon_addedge(const struct geod_geodesic* g, struct geod_polygon* p, double azi, double s); @@ -773,7 +787,7 @@ extern "C" { printf("%d %.8f %.3f\n", n, P, A); @endcode **********************************************************************/ - unsigned geod_polygon_compute(const struct geod_geodesic* g, + unsigned GEOD_DLL geod_polygon_compute(const struct geod_geodesic* g, const struct geod_polygon* p, int reverse, int sign, double* pA, double* pP); @@ -804,7 +818,7 @@ extern "C" { * * \e lat should be in the range [−90°, 90°]. **********************************************************************/ - unsigned geod_polygon_testpoint(const struct geod_geodesic* g, + unsigned GEOD_DLL geod_polygon_testpoint(const struct geod_geodesic* g, const struct geod_polygon* p, double lat, double lon, int reverse, int sign, @@ -835,7 +849,7 @@ extern "C" { * polyline (meters). * @return the number of points. **********************************************************************/ - unsigned geod_polygon_testedge(const struct geod_geodesic* g, + unsigned GEOD_DLL geod_polygon_testedge(const struct geod_geodesic* g, const struct geod_polygon* p, double azi, double s, int reverse, int sign, @@ -873,7 +887,7 @@ extern "C" { printf("%.0f %.2f\n", A, P); @endcode **********************************************************************/ - void geod_polygonarea(const struct geod_geodesic* g, + void GEOD_DLL geod_polygonarea(const struct geod_geodesic* g, double lats[], double lons[], int n, double* pA, double* pP); diff --git a/src/internal.cpp b/src/internal.cpp new file mode 100644 index 00000000..aa27192a --- /dev/null +++ b/src/internal.cpp @@ -0,0 +1,298 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/internal/internal.hpp" + +#include <cstring> +#ifdef _MSC_VER +#include <string.h> +#else +#include <strings.h> +#endif +#include <exception> +#include <iomanip> // std::setprecision +#include <locale> +#include <sstream> // std::istringstream and std::ostringstream +#include <string> + +NS_PROJ_START + +namespace internal { + +// --------------------------------------------------------------------------- + +/** + * Replace all occurrences of before with after. + */ +std::string replaceAll(const std::string &str, const std::string &before, + const std::string &after) { + std::string ret(str); + const size_t nBeforeSize = before.size(); + const size_t nAfterSize = after.size(); + if (nBeforeSize) { + size_t nStartPos = 0; + while ((nStartPos = ret.find(before, nStartPos)) != std::string::npos) { + ret.replace(nStartPos, nBeforeSize, after); + nStartPos += nAfterSize; + } + } + return ret; +} + +// --------------------------------------------------------------------------- + +inline static bool EQUALN(const char *a, const char *b, size_t size) { +#ifdef _MSC_VER + return _strnicmp(a, b, size) == 0; +#else + return strncasecmp(a, b, size) == 0; +#endif +} + +/** + * Case-insensitive equality test + */ +bool ci_equal(const std::string &a, const std::string &b) noexcept { + const auto size = a.size(); + if (size != b.size()) { + return false; + } + return EQUALN(a.c_str(), b.c_str(), size); +} + +bool ci_equal(const std::string &a, const char *b) noexcept { + const auto size = a.size(); + if (size != strlen(b)) { + return false; + } + return EQUALN(a.c_str(), b, size); +} + +bool ci_equal(const char *a, const char *b) noexcept { + const auto size = strlen(a); + if (size != strlen(b)) { + return false; + } + return EQUALN(a, b, size); +} + +// --------------------------------------------------------------------------- + +/** + * Convert to lower case. + */ + +std::string tolower(const std::string &str) + +{ + std::string ret(str); + for (size_t i = 0; i < ret.size(); i++) + ret[i] = static_cast<char>(::tolower(ret[i])); + return ret; +} + +// --------------------------------------------------------------------------- + +/** + * Convert to upper case. + */ + +std::string toupper(const std::string &str) + +{ + std::string ret(str); + for (size_t i = 0; i < ret.size(); i++) + ret[i] = static_cast<char>(::toupper(ret[i])); + return ret; +} + +// --------------------------------------------------------------------------- + +/** Strip leading and trailing double quote characters */ +std::string stripQuotes(const std::string &str) { + if (str.size() >= 2 && str[0] == '"' && str.back() == '"') { + return str.substr(1, str.size() - 2); + } + return str; +} + +// --------------------------------------------------------------------------- + +size_t ci_find(const std::string &str, const char *needle) noexcept { + const size_t needleSize = strlen(needle); + for (size_t i = 0; i + needleSize <= str.size(); i++) { + if (EQUALN(str.c_str() + i, needle, needleSize)) { + return i; + } + } + return std::string::npos; +} + +// --------------------------------------------------------------------------- + +size_t ci_find(const std::string &str, const std::string &needle, + size_t startPos) noexcept { + const size_t needleSize = needle.size(); + for (size_t i = startPos; i + needleSize <= str.size(); i++) { + if (EQUALN(str.c_str() + i, needle.c_str(), needleSize)) { + return i; + } + } + return std::string::npos; +} + +// --------------------------------------------------------------------------- + +/* +bool starts_with(const std::string &str, const std::string &prefix) noexcept { + if (str.size() < prefix.size()) { + return false; + } + return std::memcmp(str.c_str(), prefix.c_str(), prefix.size()) == 0; +} +*/ + +// --------------------------------------------------------------------------- + +/* +bool starts_with(const std::string &str, const char *prefix) noexcept { + const size_t prefixSize = std::strlen(prefix); + if (str.size() < prefixSize) { + return false; + } + return std::memcmp(str.c_str(), prefix, prefixSize) == 0; +} +*/ + +// --------------------------------------------------------------------------- + +bool ci_starts_with(const char *str, const char *prefix) noexcept { + const auto str_size = strlen(str); + const auto prefix_size = strlen(prefix); + if (str_size < prefix_size) { + return false; + } + return EQUALN(str, prefix, prefix_size); +} + +// --------------------------------------------------------------------------- + +bool ci_starts_with(const std::string &str, + const std::string &prefix) noexcept { + if (str.size() < prefix.size()) { + return false; + } + return EQUALN(str.c_str(), prefix.c_str(), prefix.size()); +} + +// --------------------------------------------------------------------------- + +bool ends_with(const std::string &str, const std::string &suffix) noexcept { + if (str.size() < suffix.size()) { + return false; + } + return std::memcmp(str.c_str() + str.size() - suffix.size(), suffix.c_str(), + suffix.size()) == 0; +} + +// --------------------------------------------------------------------------- + +double c_locale_stod(const std::string &s) { + std::istringstream iss(s); + iss.imbue(std::locale::classic()); + double d; + iss >> d; + if (!iss.eof() || iss.fail()) { + throw std::invalid_argument("non double value"); + } + return d; +} + +// --------------------------------------------------------------------------- + +std::vector<std::string> split(const std::string &str, char separator) { + std::vector<std::string> res; + size_t lastPos = 0; + size_t newPos = 0; + while ((newPos = str.find(separator, lastPos)) != std::string::npos) { + res.push_back(str.substr(lastPos, newPos - lastPos)); + lastPos = newPos + 1; + } + res.push_back(str.substr(lastPos)); + return res; +} + +// --------------------------------------------------------------------------- + +std::string toString(int val) { + std::ostringstream buffer; + buffer.imbue(std::locale::classic()); + buffer << val; + return buffer.str(); +} + +std::string toString(double val, int precision) { + std::ostringstream buffer; + buffer.imbue(std::locale::classic()); + buffer << std::setprecision(precision); + buffer << val; + auto str = buffer.str(); + if (precision == 15 && str.find("9999999999") != std::string::npos) { + buffer.str(""); + buffer.clear(); + buffer << std::setprecision(14); + buffer << val; + return buffer.str(); + } + return str; +} + +// --------------------------------------------------------------------------- + +std::string concat(const char *a, const std::string &b) { + std::string res(a); + res += b; + return res; +} + +std::string concat(const char *a, const std::string &b, const char *c) { + std::string res(a); + res += b; + res += c; + return res; +} + +// --------------------------------------------------------------------------- + +} // namespace internal + +NS_PROJ_END diff --git a/src/io.cpp b/src/io.cpp new file mode 100644 index 00000000..83450745 --- /dev/null +++ b/src/io.cpp @@ -0,0 +1,6827 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include <algorithm> +#include <cassert> +#include <cctype> +#include <cmath> +#include <cstring> +#include <list> +#include <locale> +#include <map> +#include <set> +#include <sstream> // std::istringstream +#include <string> +#include <utility> +#include <vector> + +#include "proj/common.hpp" +#include "proj/coordinateoperation.hpp" +#include "proj/coordinatesystem.hpp" +#include "proj/crs.hpp" +#include "proj/datum.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/coordinateoperation_internal.hpp" +#include "proj/internal/coordinatesystem_internal.hpp" +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include "proj_constants.h" + +// PROJ include order is sensitive +// clang-format off +#include "proj.h" +#include "projects.h" +#include "proj_api.h" +// clang-format on + +using namespace NS_PROJ::common; +using namespace NS_PROJ::crs; +using namespace NS_PROJ::cs; +using namespace NS_PROJ::datum; +using namespace NS_PROJ::internal; +using namespace NS_PROJ::metadata; +using namespace NS_PROJ::operation; +using namespace NS_PROJ::util; + +//! @cond Doxygen_Suppress +static const std::string emptyString{}; +//! @endcond + +#if 0 +namespace dropbox{ namespace oxygen { +template<> nn<NS_PROJ::io::DatabaseContextPtr>::~nn() = default; +template<> nn<NS_PROJ::io::AuthorityFactoryPtr>::~nn() = default; +template<> nn<std::shared_ptr<NS_PROJ::io::IPROJStringExportable>>::~nn() = default; +template<> nn<std::unique_ptr<NS_PROJ::io::PROJStringFormatter, std::default_delete<NS_PROJ::io::PROJStringFormatter> > >::~nn() = default; +template<> nn<std::unique_ptr<NS_PROJ::io::WKTFormatter, std::default_delete<NS_PROJ::io::WKTFormatter> > >::~nn() = default; +template<> nn<std::unique_ptr<NS_PROJ::io::WKTNode, std::default_delete<NS_PROJ::io::WKTNode> > >::~nn() = default; +}} +#endif + +NS_PROJ_START +namespace io { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +IWKTExportable::~IWKTExportable() = default; + +// --------------------------------------------------------------------------- + +std::string IWKTExportable::exportToWKT(WKTFormatter *formatter) const { + _exportToWKT(formatter); + return formatter->toString(); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct WKTFormatter::Private { + struct Params { + WKTFormatter::Convention convention_ = WKTFormatter::Convention::WKT2; + WKTFormatter::Version version_ = WKTFormatter::Version::WKT2; + bool multiLine_ = true; + bool strict_ = true; + int indentWidth_ = 4; + bool idOnTopLevelOnly_ = false; + bool outputAxisOrder_ = false; + bool primeMeridianOmittedIfGreenwich_ = false; + bool ellipsoidUnitOmittedIfMetre_ = false; + bool primeMeridianOrParameterUnitOmittedIfSameAsAxis_ = false; + bool forceUNITKeyword_ = false; + bool outputCSUnitOnlyOnceIfSame_ = false; + bool primeMeridianInDegree_ = false; + bool use2018Keywords_ = false; + bool useESRIDialect_ = false; + bool outputAxis_ = true; + }; + Params params_{}; + DatabaseContextPtr dbContext_{}; + + int indentLevel_ = 0; + int level_ = 0; + std::vector<bool> stackHasChild_{}; + std::vector<bool> stackHasId_{false}; + std::vector<bool> stackEmptyKeyword_{}; + std::vector<bool> outputUnitStack_{true}; + std::vector<bool> outputIdStack_{true}; + std::vector<UnitOfMeasureNNPtr> axisLinearUnitStack_{ + util::nn_make_shared<UnitOfMeasure>(UnitOfMeasure::METRE)}; + std::vector<UnitOfMeasureNNPtr> axisAngularUnitStack_{ + util::nn_make_shared<UnitOfMeasure>(UnitOfMeasure::DEGREE)}; + bool abridgedTransformation_ = false; + bool useDerivingConversion_ = false; + std::vector<double> toWGS84Parameters_{}; + std::string hDatumExtension_{}; + std::string vDatumExtension_{}; + std::vector<bool> inversionStack_{false}; + std::string result_{}; + + // cppcheck-suppress functionStatic + void addNewLine(); + void addIndentation(); + // cppcheck-suppress functionStatic + void startNewChild(); +}; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Constructs a new formatter. + * + * A formatter can be used only once (its internal state is mutated) + * + * Its default behaviour can be adjusted with the different setters. + * + * @param convention WKT flavor. Defaults to Convention::WKT2 + * @param dbContext Database context, to allow queries in it if needed. + * This is used for example for WKT1_ESRI output to do name substitutions. + * + * @return new formatter. + */ +WKTFormatterNNPtr WKTFormatter::create(Convention convention, + // cppcheck-suppress passedByValue + DatabaseContextPtr dbContext) { + auto ret = NN_NO_CHECK(WKTFormatter::make_unique<WKTFormatter>(convention)); + ret->d->dbContext_ = dbContext; + return ret; +} + +// --------------------------------------------------------------------------- + +/** \brief Constructs a new formatter from another one. + * + * A formatter can be used only once (its internal state is mutated) + * + * Its default behaviour can be adjusted with the different setters. + * + * @param other source formatter. + * @return new formatter. + */ +WKTFormatterNNPtr WKTFormatter::create(const WKTFormatterNNPtr &other) { + auto f = create(other->d->params_.convention_, other->d->dbContext_); + f->d->params_ = other->d->params_; + return f; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +WKTFormatter::~WKTFormatter() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Whether to use multi line output or not. */ +WKTFormatter &WKTFormatter::setMultiLine(bool multiLine) noexcept { + d->params_.multiLine_ = multiLine; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set number of spaces for each indentation level (defaults to 4). + */ +WKTFormatter &WKTFormatter::setIndentationWidth(int width) noexcept { + d->params_.indentWidth_ = width; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether AXIS nodes should be output. + * + * This can typically be set to false for some variants of WKT1_GDAL. + */ +WKTFormatter &WKTFormatter::setOutputAxis(bool outputAxisIn) noexcept { + d->params_.outputAxis_ = outputAxisIn; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether the formatter should operate on strict more or not. + * + * The default is strit mode, in which case a FormattingException can be thrown. + */ +WKTFormatter &WKTFormatter::setStrict(bool strictIn) noexcept { + d->params_.strict_ = strictIn; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns whether the formatter is in strict mode. */ +bool WKTFormatter::isStrict() const noexcept { return d->params_.strict_; } + +// --------------------------------------------------------------------------- + +/** Returns the WKT string from the formatter. */ +const std::string &WKTFormatter::toString() const { + if (d->indentLevel_ > 0 || d->level_ > 0) { + // For intermediary nodes, the formatter is in a inconsistent + // state. + throw FormattingException("toString() called on intermediate nodes"); + } + if (d->axisLinearUnitStack_.size() != 1) + throw FormattingException( + "Unbalanced pushAxisLinearUnit() / popAxisLinearUnit()"); + if (d->axisAngularUnitStack_.size() != 1) + throw FormattingException( + "Unbalanced pushAxisAngularUnit() / popAxisAngularUnit()"); + if (d->outputIdStack_.size() != 1) + throw FormattingException("Unbalanced pushOutputId() / popOutputId()"); + if (d->outputUnitStack_.size() != 1) + throw FormattingException( + "Unbalanced pushOutputUnit() / popOutputUnit()"); + + return d->result_; +} + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +WKTFormatter::WKTFormatter(Convention convention) + : d(internal::make_unique<Private>()) { + d->params_.convention_ = convention; + switch (convention) { + case Convention::WKT2_2018: + d->params_.use2018Keywords_ = true; + PROJ_FALLTHROUGH + case Convention::WKT2: + d->params_.version_ = WKTFormatter::Version::WKT2; + d->params_.outputAxisOrder_ = true; + break; + + case Convention::WKT2_2018_SIMPLIFIED: + d->params_.use2018Keywords_ = true; + PROJ_FALLTHROUGH + case Convention::WKT2_SIMPLIFIED: + d->params_.version_ = WKTFormatter::Version::WKT2; + d->params_.idOnTopLevelOnly_ = true; + d->params_.outputAxisOrder_ = false; + d->params_.primeMeridianOmittedIfGreenwich_ = true; + d->params_.ellipsoidUnitOmittedIfMetre_ = true; + d->params_.primeMeridianOrParameterUnitOmittedIfSameAsAxis_ = true; + d->params_.forceUNITKeyword_ = true; + d->params_.outputCSUnitOnlyOnceIfSame_ = true; + break; + + case Convention::WKT1_GDAL: + d->params_.version_ = WKTFormatter::Version::WKT1; + d->params_.outputAxisOrder_ = false; + d->params_.forceUNITKeyword_ = true; + d->params_.primeMeridianInDegree_ = true; + break; + + case Convention::WKT1_ESRI: + d->params_.version_ = WKTFormatter::Version::WKT1; + d->params_.outputAxisOrder_ = false; + d->params_.forceUNITKeyword_ = true; + d->params_.primeMeridianInDegree_ = true; + d->params_.useESRIDialect_ = true; + d->params_.multiLine_ = false; + d->params_.outputAxis_ = false; + break; + + default: + assert(false); + break; + } +} + +// --------------------------------------------------------------------------- + +WKTFormatter &WKTFormatter::setOutputId(bool outputIdIn) { + if (d->indentLevel_ != 0) { + throw Exception( + "setOutputId() shall only be called when the stack state is empty"); + } + d->outputIdStack_[0] = outputIdIn; + return *this; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::Private::addNewLine() { result_ += '\n'; } + +// --------------------------------------------------------------------------- + +void WKTFormatter::Private::addIndentation() { + result_ += std::string(indentLevel_ * params_.indentWidth_, ' '); +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::enter() { + if (d->indentLevel_ == 0 && d->level_ == 0) { + d->stackHasChild_.push_back(false); + } + ++d->level_; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::leave() { + assert(d->level_ > 0); + --d->level_; + if (d->indentLevel_ == 0 && d->level_ == 0) { + d->stackHasChild_.pop_back(); + } +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::startNode(const std::string &keyword, bool hasId) { + if (!d->stackHasChild_.empty()) { + d->startNewChild(); + } else if (!d->result_.empty()) { + d->result_ += ","; + if (d->params_.multiLine_ && !keyword.empty()) { + d->addNewLine(); + } + } + + if (d->params_.multiLine_) { + if ((d->indentLevel_ || d->level_) && !keyword.empty()) { + if (!d->result_.empty()) { + d->addNewLine(); + } + d->addIndentation(); + } + } + + if (!keyword.empty()) { + d->result_ += keyword; + d->result_ += "["; + } + d->indentLevel_++; + d->stackHasChild_.push_back(false); + d->stackEmptyKeyword_.push_back(keyword.empty()); + + // Starting from a node that has a ID, we should emit ID nodes for : + // - this node + // - and for METHOD&PARAMETER nodes in WKT2, unless idOnTopLevelOnly_ is + // set. + // For WKT2, all other intermediate nodes shouldn't have ID ("not + // recommended") + if (!d->params_.idOnTopLevelOnly_ && d->indentLevel_ >= 2 && + d->params_.version_ == WKTFormatter::Version::WKT2 && + (keyword == WKTConstants::METHOD || + keyword == WKTConstants::PARAMETER)) { + pushOutputId(d->outputIdStack_[0]); + } else if (d->indentLevel_ >= 2 && + d->params_.version_ == WKTFormatter::Version::WKT2) { + pushOutputId(d->outputIdStack_[0] && !d->stackHasId_.back()); + } else { + pushOutputId(outputId()); + } + + d->stackHasId_.push_back(hasId || d->stackHasId_.back()); +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::endNode() { + assert(d->indentLevel_ > 0); + d->stackHasId_.pop_back(); + popOutputId(); + d->indentLevel_--; + bool emptyKeyword = d->stackEmptyKeyword_.back(); + d->stackEmptyKeyword_.pop_back(); + d->stackHasChild_.pop_back(); + if (!emptyKeyword) + d->result_ += "]"; +} + +// --------------------------------------------------------------------------- + +WKTFormatter &WKTFormatter::simulCurNodeHasId() { + d->stackHasId_.back() = true; + return *this; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::Private::startNewChild() { + assert(!stackHasChild_.empty()); + if (stackHasChild_.back()) { + result_ += ","; + } + stackHasChild_.back() = true; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::addQuotedString(const char *str) { + addQuotedString(std::string(str)); +} + +void WKTFormatter::addQuotedString(const std::string &str) { + d->startNewChild(); + d->result_ += "\""; + d->result_ += replaceAll(str, "\"", "\"\""); + d->result_ += "\""; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::add(const std::string &str) { + d->startNewChild(); + d->result_ += str; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::add(int number) { + d->startNewChild(); + d->result_ += internal::toString(number); +} + +// --------------------------------------------------------------------------- + +#ifdef __MINGW32__ +static std::string normalizeSerializedString(const std::string &in) { + // mingw will output 1e-0xy instead of 1e-xy. Fix that + auto pos = in.find("e-0"); + if (pos == std::string::npos) { + return in; + } + if (pos + 4 < in.size() && isdigit(in[pos + 3]) && isdigit(in[pos + 4])) { + return in.substr(0, pos + 2) + in.substr(pos + 3); + } + return in; +} +#else +static std::string normalizeSerializedString(const std::string &in) { + return in; +} +#endif + +// --------------------------------------------------------------------------- + +void WKTFormatter::add(double number, int precision) { + d->startNewChild(); + std::string val( + normalizeSerializedString(internal::toString(number, precision))); + d->result_ += val; + if (d->params_.useESRIDialect_ && val.find('.') == std::string::npos) { + d->result_ += ".0"; + } +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::pushOutputUnit(bool outputUnitIn) { + d->outputUnitStack_.push_back(outputUnitIn); +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::popOutputUnit() { d->outputUnitStack_.pop_back(); } + +// --------------------------------------------------------------------------- + +bool WKTFormatter::outputUnit() const { return d->outputUnitStack_.back(); } + +// --------------------------------------------------------------------------- + +void WKTFormatter::pushOutputId(bool outputIdIn) { + d->outputIdStack_.push_back(outputIdIn); +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::popOutputId() { d->outputIdStack_.pop_back(); } + +// --------------------------------------------------------------------------- + +bool WKTFormatter::outputId() const { + return !d->params_.useESRIDialect_ && d->outputIdStack_.back(); +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::pushAxisLinearUnit(const UnitOfMeasureNNPtr &unit) { + d->axisLinearUnitStack_.push_back(unit); +} +// --------------------------------------------------------------------------- + +void WKTFormatter::popAxisLinearUnit() { d->axisLinearUnitStack_.pop_back(); } + +// --------------------------------------------------------------------------- + +const UnitOfMeasureNNPtr &WKTFormatter::axisLinearUnit() const { + return d->axisLinearUnitStack_.back(); +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::pushAxisAngularUnit(const UnitOfMeasureNNPtr &unit) { + d->axisAngularUnitStack_.push_back(unit); +} +// --------------------------------------------------------------------------- + +void WKTFormatter::popAxisAngularUnit() { d->axisAngularUnitStack_.pop_back(); } + +// --------------------------------------------------------------------------- + +const UnitOfMeasureNNPtr &WKTFormatter::axisAngularUnit() const { + return d->axisAngularUnitStack_.back(); +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::outputAxis() const { return d->params_.outputAxis_; } + +// --------------------------------------------------------------------------- + +bool WKTFormatter::outputAxisOrder() const { + return d->params_.outputAxisOrder_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::primeMeridianOmittedIfGreenwich() const { + return d->params_.primeMeridianOmittedIfGreenwich_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::ellipsoidUnitOmittedIfMetre() const { + return d->params_.ellipsoidUnitOmittedIfMetre_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::primeMeridianOrParameterUnitOmittedIfSameAsAxis() const { + return d->params_.primeMeridianOrParameterUnitOmittedIfSameAsAxis_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::outputCSUnitOnlyOnceIfSame() const { + return d->params_.outputCSUnitOnlyOnceIfSame_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::forceUNITKeyword() const { + return d->params_.forceUNITKeyword_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::primeMeridianInDegree() const { + return d->params_.primeMeridianInDegree_; +} + +// --------------------------------------------------------------------------- + +WKTFormatter::Version WKTFormatter::version() const { + return d->params_.version_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::use2018Keywords() const { + return d->params_.use2018Keywords_; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::useESRIDialect() const { return d->params_.useESRIDialect_; } + +// --------------------------------------------------------------------------- + +const DatabaseContextPtr &WKTFormatter::databaseContext() const { + return d->dbContext_; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::setAbridgedTransformation(bool outputIn) { + d->abridgedTransformation_ = outputIn; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::abridgedTransformation() const { + return d->abridgedTransformation_; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::setUseDerivingConversion(bool useDerivingConversionIn) { + d->useDerivingConversion_ = useDerivingConversionIn; +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::useDerivingConversion() const { + return d->useDerivingConversion_; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::setTOWGS84Parameters(const std::vector<double> ¶ms) { + d->toWGS84Parameters_ = params; +} + +// --------------------------------------------------------------------------- + +const std::vector<double> &WKTFormatter::getTOWGS84Parameters() const { + return d->toWGS84Parameters_; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::setVDatumExtension(const std::string &filename) { + d->vDatumExtension_ = filename; +} + +// --------------------------------------------------------------------------- + +const std::string &WKTFormatter::getVDatumExtension() const { + return d->vDatumExtension_; +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::setHDatumExtension(const std::string &filename) { + d->hDatumExtension_ = filename; +} + +// --------------------------------------------------------------------------- + +const std::string &WKTFormatter::getHDatumExtension() const { + return d->hDatumExtension_; +} + +// --------------------------------------------------------------------------- + +std::string WKTFormatter::morphNameToESRI(const std::string &name) { + std::string ret; + bool insertUnderscore = false; + // Replace any special character by underscore, except at the beginning + // and of the name where those characters are removed. + for (char ch : name) { + if (ch == '+' || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z')) { + if (insertUnderscore && !ret.empty()) { + ret += '_'; + } + ret += ch; + insertUnderscore = false; + } else { + insertUnderscore = true; + } + } + return ret; +} + +#ifdef unused +// --------------------------------------------------------------------------- + +void WKTFormatter::startInversion() { + d->inversionStack_.push_back(!d->inversionStack_.back()); +} + +// --------------------------------------------------------------------------- + +void WKTFormatter::stopInversion() { + assert(!d->inversionStack_.empty()); + d->inversionStack_.pop_back(); +} + +// --------------------------------------------------------------------------- + +bool WKTFormatter::isInverted() const { return d->inversionStack_.back(); } +#endif + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +static WKTNodeNNPtr + null_node(NN_NO_CHECK(internal::make_unique<WKTNode>(std::string()))); + +static inline bool isNull(const WKTNodeNNPtr &node) { + return &node == &null_node; +} + +struct WKTNode::Private { + std::string value_{}; + std::vector<WKTNodeNNPtr> children_{}; + + explicit Private(const std::string &valueIn) : value_(valueIn) {} + + // cppcheck-suppress functionStatic + inline const std::string &value() PROJ_CONST_DEFN { return value_; } + + // cppcheck-suppress functionStatic + inline const std::vector<WKTNodeNNPtr> &children() PROJ_CONST_DEFN { + return children_; + } + + // cppcheck-suppress functionStatic + inline size_t childrenSize() PROJ_CONST_DEFN { return children_.size(); } + + // cppcheck-suppress functionStatic + const WKTNodeNNPtr &lookForChild(const std::string &childName, + int occurrence) const noexcept; + + // cppcheck-suppress functionStatic + const WKTNodeNNPtr &lookForChild(const std::string &name) const noexcept; + + // cppcheck-suppress functionStatic + const WKTNodeNNPtr &lookForChild(const std::string &name, + const std::string &name2) const noexcept; + + // cppcheck-suppress functionStatic + const WKTNodeNNPtr &lookForChild(const std::string &name, + const std::string &name2, + const std::string &name3) const noexcept; + + // cppcheck-suppress functionStatic + const WKTNodeNNPtr &lookForChild(const std::string &name, + const std::string &name2, + const std::string &name3, + const std::string &name4) const noexcept; +}; + +#define GP() getPrivate() + +// --------------------------------------------------------------------------- + +const WKTNodeNNPtr &WKTNode::Private::lookForChild(const std::string &childName, + int occurrence) const + noexcept { + int occCount = 0; + for (const auto &child : children_) { + if (ci_equal(child->GP()->value(), childName)) { + if (occurrence == occCount) { + return child; + } + occCount++; + } + } + return null_node; +} + +const WKTNodeNNPtr & +WKTNode::Private::lookForChild(const std::string &name) const noexcept { + for (const auto &child : children_) { + const auto &v = child->GP()->value(); + if (ci_equal(v, name)) { + return child; + } + } + return null_node; +} + +const WKTNodeNNPtr & +WKTNode::Private::lookForChild(const std::string &name, + const std::string &name2) const noexcept { + for (const auto &child : children_) { + const auto &v = child->GP()->value(); + if (ci_equal(v, name) || ci_equal(v, name2)) { + return child; + } + } + return null_node; +} + +const WKTNodeNNPtr & +WKTNode::Private::lookForChild(const std::string &name, + const std::string &name2, + const std::string &name3) const noexcept { + for (const auto &child : children_) { + const auto &v = child->GP()->value(); + if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3)) { + return child; + } + } + return null_node; +} + +const WKTNodeNNPtr &WKTNode::Private::lookForChild( + const std::string &name, const std::string &name2, const std::string &name3, + const std::string &name4) const noexcept { + for (const auto &child : children_) { + const auto &v = child->GP()->value(); + if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3) || + ci_equal(v, name4)) { + return child; + } + } + return null_node; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a WKTNode. + * + * @param valueIn the name of the node. + */ +WKTNode::WKTNode(const std::string &valueIn) + : d(internal::make_unique<Private>(valueIn)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +WKTNode::~WKTNode() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Adds a child to the current node. + * + * @param child child to add. This should not be a parent of this node. + */ +void WKTNode::addChild(WKTNodeNNPtr &&child) { + d->children_.push_back(std::move(child)); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the (occurrence-1)th sub-node of name childName. + * + * @param childName name of the child. + * @param occurrence occurrence index (starting at 0) + * @return the child, or nullptr. + */ +const WKTNodePtr &WKTNode::lookForChild(const std::string &childName, + int occurrence) const noexcept { + int occCount = 0; + for (const auto &child : d->children_) { + if (ci_equal(child->GP()->value(), childName)) { + if (occurrence == occCount) { + return child; + } + occCount++; + } + } + return null_node; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the count of children of given name. + * + * @param childName name of the children to look for. + * @return count + */ +int WKTNode::countChildrenOfName(const std::string &childName) const noexcept { + int occCount = 0; + for (const auto &child : d->children_) { + if (ci_equal(child->GP()->value(), childName)) { + occCount++; + } + } + return occCount; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the value of a node. + */ +const std::string &WKTNode::value() const { return d->value_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the children of a node. + */ +const std::vector<WKTNodeNNPtr> &WKTNode::children() const { + return d->children_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static size_t skipSpace(const std::string &str, size_t start) { + size_t i = start; + while (i < str.size() && ::isspace(str[i])) { + ++i; + } + return i; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +// As used in examples of OGC 12-063r5 +static const std::string startPrintedQuote("\xE2\x80\x9C"); +static const std::string endPrintedQuote("\xE2\x80\x9D"); +//! @endcond + +WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart, + int recLevel, size_t &indexEnd) { + if (recLevel == 16) { + throw ParsingException("too many nesting levels"); + } + std::string value; + size_t i = skipSpace(wkt, indexStart); + if (i == wkt.size()) { + throw ParsingException("whitespace only string"); + } + std::string closingStringMarker; + bool inString = false; + + for (; i < wkt.size() && + (inString || (wkt[i] != '[' && wkt[i] != '(' && wkt[i] != ',' && + wkt[i] != ']' && wkt[i] != ')' && !::isspace(wkt[i]))); + ++i) { + if (wkt[i] == '"') { + if (!inString) { + inString = true; + closingStringMarker = "\""; + } else if (closingStringMarker == "\"") { + if (i + 1 < wkt.size() && wkt[i + 1] == '"') { + i++; + } else { + inString = false; + closingStringMarker.clear(); + } + } + } else if (i + 3 <= wkt.size() && + wkt.substr(i, 3) == startPrintedQuote) { + if (!inString) { + inString = true; + closingStringMarker = endPrintedQuote; + value += "\""; + i += 2; + continue; + } + } else if (i + 3 <= wkt.size() && + closingStringMarker == endPrintedQuote && + wkt.substr(i, 3) == endPrintedQuote) { + inString = false; + closingStringMarker.clear(); + value += "\""; + i += 2; + continue; + } + value += wkt[i]; + } + i = skipSpace(wkt, i); + if (i == wkt.size()) { + if (indexStart == 0) { + throw ParsingException("missing ["); + } else { + throw ParsingException("missing , or ]"); + } + } + + auto node = NN_NO_CHECK(internal::make_unique<WKTNode>(value)); + + if (indexStart > 0) { + if (wkt[i] == ',') { + indexEnd = i + 1; + return node; + } + if (wkt[i] == ']' || wkt[i] == ')') { + indexEnd = i; + return node; + } + } + if (wkt[i] != '[' && wkt[i] != '(') { + throw ParsingException("missing ["); + } + ++i; // skip [ + i = skipSpace(wkt, i); + while (i < wkt.size() && wkt[i] != ']' && wkt[i] != ')') { + size_t indexEndChild; + node->addChild(createFrom(wkt, i, recLevel + 1, indexEndChild)); + assert(indexEndChild > i); + i = indexEndChild; + i = skipSpace(wkt, i); + } + if (i == wkt.size() || (wkt[i] != ']' && wkt[i] != ')')) { + throw ParsingException("missing ]"); + } + indexEnd = i + 1; + return node; +} +// --------------------------------------------------------------------------- + +/** \brief Instanciate a WKTNode hierarchy from a WKT string. + * + * @param wkt the WKT string to parse. + * @param indexStart the start index in the wkt string. + * @throw ParsingException + */ +WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart) { + size_t indexEnd; + return createFrom(wkt, indexStart, 0, indexEnd); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static std::string escapeIfQuotedString(const std::string &str) { + if (str.size() > 2 && str[0] == '"' && str.back() == '"') { + std::string res("\""); + res += replaceAll(str.substr(1, str.size() - 2), "\"", "\"\""); + res += "\""; + return res; + } else { + return str; + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return a WKT representation of the tree structure. + */ +std::string WKTNode::toString() const { + std::string str(escapeIfQuotedString(d->value_)); + if (!d->children_.empty()) { + str += "["; + bool first = true; + for (auto &child : d->children_) { + if (!first) { + str += ","; + } + first = false; + str += child->toString(); + } + str += "]"; + } + return str; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct WKTParser::Private { + bool strict_ = true; + std::vector<std::string> warningList_{}; + std::vector<double> toWGS84Parameters_{}; + std::string datumPROJ4Grids_{}; + bool esriStyle_ = false; + DatabaseContextPtr dbContext_{}; + + static constexpr int MAX_PROPERTY_SIZE = 1024; + PropertyMap **properties_{}; + int propertyCount_ = 0; + + Private() { properties_ = new PropertyMap *[MAX_PROPERTY_SIZE]; } + + ~Private() { + for (int i = 0; i < propertyCount_; i++) { + delete properties_[i]; + } + delete[] properties_; + } + Private(const Private &) = delete; + Private &operator=(const Private &) = delete; + + void emitRecoverableAssertion(const std::string &errorMsg); + + BaseObjectNNPtr build(const WKTNodeNNPtr &node); + + IdentifierPtr buildId(const WKTNodeNNPtr &node, bool tolerant = true); + + PropertyMap &buildProperties(const WKTNodeNNPtr &node); + + ObjectDomainPtr buildObjectDomain(const WKTNodeNNPtr &node); + + static std::string stripQuotes(const WKTNodeNNPtr &node); + + static double asDouble(const WKTNodeNNPtr &node); + + UnitOfMeasure + buildUnit(const WKTNodeNNPtr &node, + UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN); + + UnitOfMeasure buildUnitInSubNode( + const WKTNodeNNPtr &node, + common::UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN); + + EllipsoidNNPtr buildEllipsoid(const WKTNodeNNPtr &node); + + PrimeMeridianNNPtr + buildPrimeMeridian(const WKTNodeNNPtr &node, + const UnitOfMeasure &defaultAngularUnit); + + optional<std::string> getAnchor(const WKTNodeNNPtr &node); + + static void parseDynamic(const WKTNodeNNPtr &dynamicNode, + double &frameReferenceEpoch, + util::optional<std::string> &modelName); + + GeodeticReferenceFrameNNPtr + buildGeodeticReferenceFrame(const WKTNodeNNPtr &node, + const PrimeMeridianNNPtr &primeMeridian, + const WKTNodeNNPtr &dynamicNode); + + DatumEnsembleNNPtr buildDatumEnsemble(const WKTNodeNNPtr &node, + const PrimeMeridianPtr &primeMeridian, + bool expectEllipsoid); + + MeridianNNPtr buildMeridian(const WKTNodeNNPtr &node); + CoordinateSystemAxisNNPtr buildAxis(const WKTNodeNNPtr &node, + const UnitOfMeasure &unitIn, + bool isGeocentric, + int expectedOrderNum); + + CoordinateSystemNNPtr buildCS(const WKTNodeNNPtr &node, /* maybe null */ + const WKTNodeNNPtr &parentNode, + const UnitOfMeasure &defaultAngularUnit); + + GeodeticCRSNNPtr buildGeodeticCRS(const WKTNodeNNPtr &node); + + CRSNNPtr buildDerivedGeodeticCRS(const WKTNodeNNPtr &node); + + static UnitOfMeasure + guessUnitForParameter(const std::string ¶mName, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit); + + void consumeParameters(const WKTNodeNNPtr &node, bool isAbridged, + std::vector<OperationParameterNNPtr> ¶meters, + std::vector<ParameterValueNNPtr> &values, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit); + + ConversionNNPtr buildConversion(const WKTNodeNNPtr &node, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit); + + static bool hasWebMercPROJ4String(const WKTNodeNNPtr &projCRSNode, + const WKTNodeNNPtr &projectionNode); + + ConversionNNPtr buildProjection(const WKTNodeNNPtr &projCRSNode, + const WKTNodeNNPtr &projectionNode, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit); + + ConversionNNPtr + buildProjectionStandard(const WKTNodeNNPtr &projCRSNode, + const WKTNodeNNPtr &projectionNode, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit); + + ConversionNNPtr + buildProjectionFromESRI(const WKTNodeNNPtr &projCRSNode, + const WKTNodeNNPtr &projectionNode, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit); + + ProjectedCRSNNPtr buildProjectedCRS(const WKTNodeNNPtr &node); + + VerticalReferenceFrameNNPtr + buildVerticalReferenceFrame(const WKTNodeNNPtr &node, + const WKTNodeNNPtr &dynamicNode); + + TemporalDatumNNPtr buildTemporalDatum(const WKTNodeNNPtr &node); + + EngineeringDatumNNPtr buildEngineeringDatum(const WKTNodeNNPtr &node); + + ParametricDatumNNPtr buildParametricDatum(const WKTNodeNNPtr &node); + + CRSNNPtr buildVerticalCRS(const WKTNodeNNPtr &node); + + DerivedVerticalCRSNNPtr buildDerivedVerticalCRS(const WKTNodeNNPtr &node); + + CompoundCRSNNPtr buildCompoundCRS(const WKTNodeNNPtr &node); + + BoundCRSNNPtr buildBoundCRS(const WKTNodeNNPtr &node); + + TemporalCSNNPtr buildTemporalCS(const WKTNodeNNPtr &parentNode); + + TemporalCRSNNPtr buildTemporalCRS(const WKTNodeNNPtr &node); + + DerivedTemporalCRSNNPtr buildDerivedTemporalCRS(const WKTNodeNNPtr &node); + + EngineeringCRSNNPtr buildEngineeringCRS(const WKTNodeNNPtr &node); + + EngineeringCRSNNPtr + buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node); + + DerivedEngineeringCRSNNPtr + buildDerivedEngineeringCRS(const WKTNodeNNPtr &node); + + ParametricCSNNPtr buildParametricCS(const WKTNodeNNPtr &parentNode); + + ParametricCRSNNPtr buildParametricCRS(const WKTNodeNNPtr &node); + + DerivedParametricCRSNNPtr + buildDerivedParametricCRS(const WKTNodeNNPtr &node); + + DerivedProjectedCRSNNPtr buildDerivedProjectedCRS(const WKTNodeNNPtr &node); + + CRSPtr buildCRS(const WKTNodeNNPtr &node); + + CoordinateOperationNNPtr buildCoordinateOperation(const WKTNodeNNPtr &node); + + ConcatenatedOperationNNPtr + buildConcatenatedOperation(const WKTNodeNNPtr &node); +}; + +// --------------------------------------------------------------------------- + +WKTParser::WKTParser() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +WKTParser::~WKTParser() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Set whether parsing should be done in strict mode. + */ +WKTParser &WKTParser::setStrict(bool strict) { + d->strict_ = strict; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the list of warnings found during parsing. + * + * \note The list might be non-empty only is setStrict(false) has been called. + */ +std::vector<std::string> WKTParser::warningList() const { + return d->warningList_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void WKTParser::Private::emitRecoverableAssertion(const std::string &errorMsg) { + if (strict_) { + throw ParsingException(errorMsg); + } else { + warningList_.push_back(errorMsg); + } +} + +// --------------------------------------------------------------------------- + +static double asDouble(const std::string &val) { return c_locale_stod(val); } + +// --------------------------------------------------------------------------- + +PROJ_NO_RETURN static void ThrowNotEnoughChildren(const std::string &nodeName) { + throw ParsingException( + concat("not enough children in ", nodeName, " node")); +} + +// --------------------------------------------------------------------------- + +PROJ_NO_RETURN static void +ThrowNotRequiredNumberOfChildren(const std::string &nodeName) { + throw ParsingException( + concat("not required number of children in ", nodeName, " node")); +} + +// --------------------------------------------------------------------------- + +PROJ_NO_RETURN static void ThrowMissing(const std::string &nodeName) { + throw ParsingException(concat("missing ", nodeName, " node")); +} + +// --------------------------------------------------------------------------- + +PROJ_NO_RETURN static void +ThrowNotExpectedCSType(const std::string &expectedCSType) { + throw ParsingException(concat("CS node is not of type ", expectedCSType)); +} + +// --------------------------------------------------------------------------- + +static ParsingException buildRethrow(const char *funcName, + const std::exception &e) { + std::string res(funcName); + res += ": "; + res += e.what(); + return ParsingException(res); +} + +// --------------------------------------------------------------------------- + +std::string WKTParser::Private::stripQuotes(const WKTNodeNNPtr &node) { + return ::stripQuotes(node->GP()->value()); +} + +// --------------------------------------------------------------------------- + +double WKTParser::Private::asDouble(const WKTNodeNNPtr &node) { + return io::asDouble(node->GP()->value()); +} + +// --------------------------------------------------------------------------- + +IdentifierPtr WKTParser::Private::buildId(const WKTNodeNNPtr &node, + bool tolerant) { + const auto *nodeP = node->GP(); + const auto &nodeChidren = nodeP->children(); + if (nodeChidren.size() >= 2) { + auto codeSpace = stripQuotes(nodeChidren[0]); + auto code = stripQuotes(nodeChidren[1]); + auto &citationNode = nodeP->lookForChild(WKTConstants::CITATION); + auto &uriNode = nodeP->lookForChild(WKTConstants::URI); + PropertyMap propertiesId; + propertiesId.set(Identifier::CODESPACE_KEY, codeSpace); + bool authoritySet = false; + /*if (!isNull(citationNode))*/ { + const auto *citationNodeP = citationNode->GP(); + if (citationNodeP->childrenSize() == 1) { + authoritySet = true; + propertiesId.set(Identifier::AUTHORITY_KEY, + stripQuotes(citationNodeP->children()[0])); + } + } + if (!authoritySet) { + propertiesId.set(Identifier::AUTHORITY_KEY, codeSpace); + } + /*if (!isNull(uriNode))*/ { + const auto *uriNodeP = uriNode->GP(); + if (uriNodeP->childrenSize() == 1) { + propertiesId.set(Identifier::URI_KEY, + stripQuotes(uriNodeP->children()[0])); + } + } + if (nodeChidren.size() >= 3 && + nodeChidren[2]->GP()->childrenSize() == 0) { + auto version = stripQuotes(nodeChidren[2]); + propertiesId.set(Identifier::VERSION_KEY, version); + } + return Identifier::create(code, propertiesId); + } else if (strict_ || !tolerant) { + ThrowNotEnoughChildren(nodeP->value()); + } else { + std::string msg("not enough children in "); + msg += nodeP->value(); + msg += " node"; + warningList_.emplace_back(std::move(msg)); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +PropertyMap &WKTParser::Private::buildProperties(const WKTNodeNNPtr &node) { + + if (propertyCount_ == MAX_PROPERTY_SIZE) { + throw ParsingException("MAX_PROPERTY_SIZE reached"); + } + properties_[propertyCount_] = new PropertyMap(); + auto &&properties = properties_[propertyCount_]; + propertyCount_++; + + std::string authNameFromAlias; + std::string codeFromAlias; + const auto *nodeP = node->GP(); + const auto &nodeChildren = nodeP->children(); + if (!nodeChildren.empty()) { + const auto &nodeName(nodeP->value()); + auto name(stripQuotes(nodeChildren[0])); + + const char *tableNameForAlias = nullptr; + if (ci_equal(nodeName, WKTConstants::GEOGCS)) { + if (starts_with(name, "GCS_")) { + esriStyle_ = true; + if (name == "GCS_WGS_1984") { + name = "WGS 84"; + } else { + tableNameForAlias = "geodetic_crs"; + } + } + } else if (esriStyle_ && ci_equal(nodeName, WKTConstants::SPHEROID)) { + if (name == "WGS_1984") { + name = "WGS 84"; + authNameFromAlias = Identifier::EPSG; + codeFromAlias = "7030"; + } else { + tableNameForAlias = "ellipsoid"; + } + } + + if (dbContext_ && tableNameForAlias) { + std::string outTableName; + auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), + std::string()); + auto officialName = authFactory->getOfficialNameFromAlias( + name, tableNameForAlias, "ESRI", outTableName, + authNameFromAlias, codeFromAlias); + if (!officialName.empty()) { + name = officialName; + + // Clearing authority for geodetic_crs because of + // potential axis order mismatch. + if (strcmp(tableNameForAlias, "geodetic_crs") == 0) { + authNameFromAlias.clear(); + codeFromAlias.clear(); + } + } + } + + properties->set(IdentifiedObject::NAME_KEY, name); + } + + auto identifiers = ArrayOfBaseObject::create(); + for (const auto &subNode : nodeChildren) { + const auto &subNodeName(subNode->GP()->value()); + if (ci_equal(subNodeName, WKTConstants::ID) || + ci_equal(subNodeName, WKTConstants::AUTHORITY)) { + auto id = buildId(subNode); + if (id) { + identifiers->add(NN_NO_CHECK(id)); + } + } + } + if (identifiers->empty() && !authNameFromAlias.empty()) { + identifiers->add(Identifier::create( + codeFromAlias, + PropertyMap() + .set(Identifier::CODESPACE_KEY, authNameFromAlias) + .set(Identifier::AUTHORITY_KEY, authNameFromAlias))); + } + if (!identifiers->empty()) { + properties->set(IdentifiedObject::IDENTIFIERS_KEY, identifiers); + } + + auto &remarkNode = nodeP->lookForChild(WKTConstants::REMARK); + if (!isNull(remarkNode)) { + const auto &remarkChildren = remarkNode->GP()->children(); + if (remarkChildren.size() == 1) { + properties->set(IdentifiedObject::REMARKS_KEY, + stripQuotes(remarkChildren[0])); + } else { + ThrowNotRequiredNumberOfChildren(remarkNode->GP()->value()); + } + } + + ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create(); + for (const auto &subNode : nodeP->children()) { + const auto &subNodeName(subNode->GP()->value()); + if (ci_equal(subNodeName, WKTConstants::USAGE)) { + auto objectDomain = buildObjectDomain(subNode); + if (!objectDomain) { + throw ParsingException( + concat("missing children in ", subNodeName, " node")); + } + array->add(NN_NO_CHECK(objectDomain)); + } + } + if (!array->empty()) { + properties->set(ObjectUsage::OBJECT_DOMAIN_KEY, array); + } else { + auto objectDomain = buildObjectDomain(node); + if (objectDomain) { + properties->set(ObjectUsage::OBJECT_DOMAIN_KEY, + NN_NO_CHECK(objectDomain)); + } + } + + return *properties; +} + +// --------------------------------------------------------------------------- + +ObjectDomainPtr +WKTParser::Private::buildObjectDomain(const WKTNodeNNPtr &node) { + + const auto *nodeP = node->GP(); + auto &scopeNode = nodeP->lookForChild(WKTConstants::SCOPE); + auto &areaNode = nodeP->lookForChild(WKTConstants::AREA); + auto &bboxNode = nodeP->lookForChild(WKTConstants::BBOX); + auto &verticalExtentNode = + nodeP->lookForChild(WKTConstants::VERTICALEXTENT); + auto &temporalExtentNode = nodeP->lookForChild(WKTConstants::TIMEEXTENT); + if (!isNull(scopeNode) || !isNull(areaNode) || !isNull(bboxNode) || + !isNull(verticalExtentNode) || !isNull(temporalExtentNode)) { + optional<std::string> scope; + const auto *scopeNodeP = scopeNode->GP(); + const auto &scopeChildren = scopeNodeP->children(); + if (scopeChildren.size() == 1) { + scope = stripQuotes(scopeChildren[0]); + } + ExtentPtr extent; + if (!isNull(areaNode) || !isNull(bboxNode)) { + util::optional<std::string> description; + std::vector<GeographicExtentNNPtr> geogExtent; + std::vector<VerticalExtentNNPtr> verticalExtent; + std::vector<TemporalExtentNNPtr> temporalExtent; + if (!isNull(areaNode)) { + const auto &areaChildren = areaNode->GP()->children(); + if (areaChildren.size() == 1) { + description = stripQuotes(areaChildren[0]); + } else { + ThrowNotRequiredNumberOfChildren(areaNode->GP()->value()); + } + } + if (!isNull(bboxNode)) { + const auto &bboxChildren = bboxNode->GP()->children(); + if (bboxChildren.size() == 4) { + try { + double south = asDouble(bboxChildren[0]); + double west = asDouble(bboxChildren[1]); + double north = asDouble(bboxChildren[2]); + double east = asDouble(bboxChildren[3]); + auto bbox = GeographicBoundingBox::create(west, south, + east, north); + geogExtent.emplace_back(bbox); + } catch (const std::exception &) { + throw ParsingException(concat("not 4 double values in ", + bboxNode->GP()->value(), + " node")); + } + } else { + ThrowNotRequiredNumberOfChildren(bboxNode->GP()->value()); + } + } + + if (!isNull(verticalExtentNode)) { + const auto &verticalExtentChildren = + verticalExtentNode->GP()->children(); + const auto verticalExtentChildrenSize = + verticalExtentChildren.size(); + if (verticalExtentChildrenSize == 2 || + verticalExtentChildrenSize == 3) { + double min; + double max; + try { + min = asDouble(verticalExtentChildren[0]); + max = asDouble(verticalExtentChildren[1]); + } catch (const std::exception &) { + throw ParsingException( + concat("not 2 double values in ", + verticalExtentNode->GP()->value(), " node")); + } + UnitOfMeasure unit = UnitOfMeasure::METRE; + if (verticalExtentChildrenSize == 3) { + unit = buildUnit(verticalExtentChildren[2], + UnitOfMeasure::Type::LINEAR); + } + verticalExtent.emplace_back(VerticalExtent::create( + min, max, util::nn_make_shared<UnitOfMeasure>(unit))); + } else { + ThrowNotRequiredNumberOfChildren( + verticalExtentNode->GP()->value()); + } + } + + if (!isNull(temporalExtentNode)) { + const auto &temporalExtentChildren = + temporalExtentNode->GP()->children(); + if (temporalExtentChildren.size() == 2) { + temporalExtent.emplace_back(TemporalExtent::create( + stripQuotes(temporalExtentChildren[0]), + stripQuotes(temporalExtentChildren[1]))); + } else { + ThrowNotRequiredNumberOfChildren( + temporalExtentNode->GP()->value()); + } + } + extent = Extent::create(description, geogExtent, verticalExtent, + temporalExtent) + .as_nullable(); + } + return ObjectDomain::create(scope, extent).as_nullable(); + } + + return nullptr; +} + +// --------------------------------------------------------------------------- + +UnitOfMeasure WKTParser::Private::buildUnit(const WKTNodeNNPtr &node, + UnitOfMeasure::Type type) { + const auto *nodeP = node->GP(); + const auto &children = nodeP->children(); + if ((type != UnitOfMeasure::Type::TIME && children.size() < 2) || + (type == UnitOfMeasure::Type::TIME && children.size() < 1)) { + ThrowNotEnoughChildren(nodeP->value()); + } + try { + std::string unitName(stripQuotes(children[0])); + PropertyMap properties(buildProperties(node)); + auto &idNode = + nodeP->lookForChild(WKTConstants::ID, WKTConstants::AUTHORITY); + if (!isNull(idNode) && idNode->GP()->childrenSize() < 2) { + emitRecoverableAssertion("not enough children in " + + idNode->GP()->value() + " node"); + } + const bool hasValidIdNode = + !isNull(idNode) && idNode->GP()->childrenSize() >= 2; + + const auto &idNodeChildren(idNode->GP()->children()); + std::string codeSpace(hasValidIdNode ? stripQuotes(idNodeChildren[0]) + : std::string()); + std::string code(hasValidIdNode ? stripQuotes(idNodeChildren[1]) + : std::string()); + + bool queryDb = true; + if (type == UnitOfMeasure::Type::UNKNOWN) { + if (ci_equal(unitName, "METER") || ci_equal(unitName, "METRE")) { + type = UnitOfMeasure::Type::LINEAR; + unitName = "metre"; + if (codeSpace.empty()) { + codeSpace = Identifier::EPSG; + code = "9001"; + queryDb = false; + } + } else if (ci_equal(unitName, "DEGREE") || + ci_equal(unitName, "GRAD")) { + type = UnitOfMeasure::Type::ANGULAR; + } + } + + if (esriStyle_ && dbContext_ && queryDb) { + std::string outTableName; + std::string authNameFromAlias; + std::string codeFromAlias; + auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), + std::string()); + auto officialName = authFactory->getOfficialNameFromAlias( + unitName, "unit_of_measure", "ESRI", outTableName, + authNameFromAlias, codeFromAlias); + if (!officialName.empty()) { + unitName = officialName; + codeSpace = authNameFromAlias; + code = codeFromAlias; + } + } + + double convFactor = children.size() >= 2 ? asDouble(children[1]) : 0.0; + constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37; + constexpr double REL_ERROR = 1e-10; + // Fix common rounding errors + if (std::fabs(convFactor - UnitOfMeasure::DEGREE.conversionToSI()) < + REL_ERROR * convFactor) { + convFactor = UnitOfMeasure::DEGREE.conversionToSI(); + } else if (std::fabs(convFactor - US_FOOT_CONV_FACTOR) < + REL_ERROR * convFactor) { + convFactor = US_FOOT_CONV_FACTOR; + } + + return UnitOfMeasure(unitName, convFactor, type, codeSpace, code); + } catch (const std::exception &e) { + throw buildRethrow(__FUNCTION__, e); + } +} + +// --------------------------------------------------------------------------- + +// node here is a parent node, not a UNIT/LENGTHUNIT/ANGLEUNIT/TIMEUNIT/... node +UnitOfMeasure WKTParser::Private::buildUnitInSubNode(const WKTNodeNNPtr &node, + UnitOfMeasure::Type type) { + const auto *nodeP = node->GP(); + { + auto &unitNode = nodeP->lookForChild(WKTConstants::LENGTHUNIT); + if (!isNull(unitNode)) { + return buildUnit(unitNode, UnitOfMeasure::Type::LINEAR); + } + } + + { + auto &unitNode = nodeP->lookForChild(WKTConstants::ANGLEUNIT); + if (!isNull(unitNode)) { + return buildUnit(unitNode, UnitOfMeasure::Type::ANGULAR); + } + } + + { + auto &unitNode = nodeP->lookForChild(WKTConstants::SCALEUNIT); + if (!isNull(unitNode)) { + return buildUnit(unitNode, UnitOfMeasure::Type::SCALE); + } + } + + { + auto &unitNode = nodeP->lookForChild(WKTConstants::TIMEUNIT); + if (!isNull(unitNode)) { + return buildUnit(unitNode, UnitOfMeasure::Type::TIME); + } + } + + { + auto &unitNode = nodeP->lookForChild(WKTConstants::PARAMETRICUNIT); + if (!isNull(unitNode)) { + return buildUnit(unitNode, UnitOfMeasure::Type::PARAMETRIC); + } + } + + { + auto &unitNode = nodeP->lookForChild(WKTConstants::UNIT); + if (!isNull(unitNode)) { + return buildUnit(unitNode, type); + } + } + + return UnitOfMeasure::NONE; +} + +// --------------------------------------------------------------------------- + +EllipsoidNNPtr WKTParser::Private::buildEllipsoid(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + const auto &children = nodeP->children(); + if (children.size() < 3) { + ThrowNotEnoughChildren(nodeP->value()); + } + try { + UnitOfMeasure unit = + buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR); + if (unit == UnitOfMeasure::NONE) { + unit = UnitOfMeasure::METRE; + } + Length semiMajorAxis(asDouble(children[1]), unit); + Scale invFlattening(asDouble(children[2])); + const auto celestialBody( + Ellipsoid::guessBodyName(dbContext_, semiMajorAxis.getSIValue())); + if (invFlattening.getSIValue() == 0) { + return Ellipsoid::createSphere(buildProperties(node), semiMajorAxis, + celestialBody); + } else { + return Ellipsoid::createFlattenedSphere( + buildProperties(node), semiMajorAxis, invFlattening, + celestialBody); + } + } catch (const std::exception &e) { + throw buildRethrow(__FUNCTION__, e); + } +} + +// --------------------------------------------------------------------------- + +PrimeMeridianNNPtr WKTParser::Private::buildPrimeMeridian( + const WKTNodeNNPtr &node, const UnitOfMeasure &defaultAngularUnit) { + const auto *nodeP = node->GP(); + const auto &children = nodeP->children(); + if (children.size() < 2) { + ThrowNotEnoughChildren(nodeP->value()); + } + auto name = stripQuotes(children[0]); + UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR); + if (unit == UnitOfMeasure::NONE) { + unit = defaultAngularUnit; + if (unit == UnitOfMeasure::NONE) { + unit = UnitOfMeasure::DEGREE; + } + } + try { + double angleValue = asDouble(children[1]); + + // Correct for GDAL WKT1 departure + if (name == "Paris" && std::fabs(angleValue - 2.33722917) < 1e-8 && + unit == UnitOfMeasure::GRAD) { + angleValue = 2.5969213; + } + + Angle angle(angleValue, unit); + return PrimeMeridian::create(buildProperties(node), angle); + } catch (const std::exception &e) { + throw buildRethrow(__FUNCTION__, e); + } +} + +// --------------------------------------------------------------------------- + +optional<std::string> WKTParser::Private::getAnchor(const WKTNodeNNPtr &node) { + + auto &anchorNode = node->GP()->lookForChild(WKTConstants::ANCHOR); + if (anchorNode->GP()->childrenSize() == 1) { + return optional<std::string>( + stripQuotes(anchorNode->GP()->children()[0])); + } + return optional<std::string>(); +} + +// --------------------------------------------------------------------------- + +static const PrimeMeridianNNPtr &createReferenceMeridian() { + static const PrimeMeridianNNPtr meridian = PrimeMeridian::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, "Reference meridian"), + common::Angle(0)); + return meridian; +} + +// --------------------------------------------------------------------------- + +static const PrimeMeridianNNPtr & +fixupPrimeMeridan(const EllipsoidNNPtr &ellipsoid, + const PrimeMeridianNNPtr &pm) { + return (ellipsoid->celestialBody() != Ellipsoid::EARTH && + pm.get() == PrimeMeridian::GREENWICH.get()) + ? createReferenceMeridian() + : pm; +} + +// --------------------------------------------------------------------------- + +GeodeticReferenceFrameNNPtr WKTParser::Private::buildGeodeticReferenceFrame( + const WKTNodeNNPtr &node, const PrimeMeridianNNPtr &primeMeridian, + const WKTNodeNNPtr &dynamicNode) { + const auto *nodeP = node->GP(); + auto &ellipsoidNode = + nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID); + if (isNull(ellipsoidNode)) { + ThrowMissing(WKTConstants::ELLIPSOID); + } + auto &properties = buildProperties(node); + + // do that before buildEllipsoid() so that esriStyle_ can be set + auto name = stripQuotes(nodeP->children()[0]); + if (name == "WGS_1984") { + properties.set(IdentifiedObject::NAME_KEY, + GeodeticReferenceFrame::EPSG_6326->nameStr()); + } else if (starts_with(name, "D_")) { + esriStyle_ = true; + const char *tableNameForAlias = nullptr; + std::string authNameFromAlias; + std::string codeFromAlias; + if (name == "D_WGS_1984") { + name = "World Geodetic System 1984"; + authNameFromAlias = Identifier::EPSG; + codeFromAlias = "6326"; + } else { + tableNameForAlias = "geodetic_datum"; + } + + if (dbContext_ && tableNameForAlias) { + std::string outTableName; + auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), + std::string()); + auto officialName = authFactory->getOfficialNameFromAlias( + name, tableNameForAlias, "ESRI", outTableName, + authNameFromAlias, codeFromAlias); + if (!officialName.empty()) { + if (primeMeridian->nameStr() != + PrimeMeridian::GREENWICH->nameStr()) { + auto nameWithPM = + officialName + " (" + primeMeridian->nameStr() + ")"; + if (dbContext_->isKnownName(nameWithPM, "geodetic_datum")) { + officialName = nameWithPM; + } + } + name = officialName; + } + } + + properties.set(IdentifiedObject::NAME_KEY, name); + if (!authNameFromAlias.empty()) { + auto identifiers = ArrayOfBaseObject::create(); + identifiers->add(Identifier::create( + codeFromAlias, + PropertyMap() + .set(Identifier::CODESPACE_KEY, authNameFromAlias) + .set(Identifier::AUTHORITY_KEY, authNameFromAlias))); + properties.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers); + } + } + + auto ellipsoid = buildEllipsoid(ellipsoidNode); + const auto &primeMeridianModified = + fixupPrimeMeridan(ellipsoid, primeMeridian); + + auto &TOWGS84Node = nodeP->lookForChild(WKTConstants::TOWGS84); + if (!isNull(TOWGS84Node)) { + const auto &TOWGS84Children = TOWGS84Node->GP()->children(); + const size_t TOWGS84Size = TOWGS84Children.size(); + if (TOWGS84Size == 3 || TOWGS84Size == 7) { + try { + for (const auto &child : TOWGS84Children) { + toWGS84Parameters_.push_back(asDouble(child)); + } + for (size_t i = TOWGS84Size; i < 7; ++i) { + toWGS84Parameters_.push_back(0.0); + } + } catch (const std::exception &) { + throw ParsingException("Invalid TOWGS84 node"); + } + } else { + throw ParsingException("Invalid TOWGS84 node"); + } + } + auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION); + const auto &extensionChildren = extensionNode->GP()->children(); + if (extensionChildren.size() == 2) { + if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) { + datumPROJ4Grids_ = stripQuotes(extensionChildren[1]); + } + } + + if (!isNull(dynamicNode)) { + double frameReferenceEpoch = 0.0; + util::optional<std::string> modelName; + parseDynamic(dynamicNode, frameReferenceEpoch, modelName); + return DynamicGeodeticReferenceFrame::create( + properties, ellipsoid, getAnchor(node), primeMeridianModified, + common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR), + modelName); + } + + return GeodeticReferenceFrame::create( + properties, ellipsoid, getAnchor(node), primeMeridianModified); +} + +// --------------------------------------------------------------------------- + +DatumEnsembleNNPtr +WKTParser::Private::buildDatumEnsemble(const WKTNodeNNPtr &node, + const PrimeMeridianPtr &primeMeridian, + bool expectEllipsoid) { + const auto *nodeP = node->GP(); + auto &ellipsoidNode = + nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID); + if (expectEllipsoid && isNull(ellipsoidNode)) { + ThrowMissing(WKTConstants::ELLIPSOID); + } + + std::vector<DatumNNPtr> datums; + for (const auto &subNode : nodeP->children()) { + if (ci_equal(subNode->GP()->value(), WKTConstants::MEMBER)) { + if (subNode->GP()->childrenSize() == 0) { + throw ParsingException("Invalid MEMBER node"); + } + if (expectEllipsoid) { + datums.emplace_back(GeodeticReferenceFrame::create( + buildProperties(subNode), buildEllipsoid(ellipsoidNode), + optional<std::string>(), + primeMeridian ? NN_NO_CHECK(primeMeridian) + : PrimeMeridian::GREENWICH)); + } else { + datums.emplace_back( + VerticalReferenceFrame::create(buildProperties(subNode))); + } + } + } + + auto &accuracyNode = nodeP->lookForChild(WKTConstants::ENSEMBLEACCURACY); + auto &accuracyNodeChildren = accuracyNode->GP()->children(); + if (accuracyNodeChildren.empty()) { + ThrowMissing(WKTConstants::ENSEMBLEACCURACY); + } + auto accuracy = + PositionalAccuracy::create(accuracyNodeChildren[0]->GP()->value()); + + try { + return DatumEnsemble::create(buildProperties(node), datums, accuracy); + } catch (const util::Exception &e) { + throw buildRethrow(__FUNCTION__, e); + } +} + +// --------------------------------------------------------------------------- + +MeridianNNPtr WKTParser::Private::buildMeridian(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + const auto &children = nodeP->children(); + if (children.size() < 2) { + ThrowNotEnoughChildren(nodeP->value()); + } + UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR); + try { + double angleValue = asDouble(children[0]); + Angle angle(angleValue, unit); + return Meridian::create(angle); + } catch (const std::exception &e) { + throw buildRethrow(__FUNCTION__, e); + } +} + +// --------------------------------------------------------------------------- + +CoordinateSystemAxisNNPtr +WKTParser::Private::buildAxis(const WKTNodeNNPtr &node, + const UnitOfMeasure &unitIn, bool isGeocentric, + int expectedOrderNum) { + const auto *nodeP = node->GP(); + const auto &children = nodeP->children(); + if (children.size() < 2) { + ThrowNotEnoughChildren(nodeP->value()); + } + + auto &orderNode = nodeP->lookForChild(WKTConstants::ORDER); + if (!isNull(orderNode)) { + const auto &orderNodeChildren = orderNode->GP()->children(); + if (orderNodeChildren.size() != 1) { + ThrowNotEnoughChildren(WKTConstants::ORDER); + } + const auto &order = orderNodeChildren[0]->GP()->value(); + int orderNum; + try { + orderNum = std::stoi(order); + } catch (const std::exception &) { + throw ParsingException( + concat("buildAxis: invalid ORDER value: ", order)); + } + if (orderNum != expectedOrderNum) { + throw ParsingException( + concat("buildAxis: did not get expected ORDER value: ", order)); + } + } + + // The axis designation in WK2 can be: "name", "(abbrev)" or "name + // (abbrev)" + std::string axisDesignation(stripQuotes(children[0])); + size_t sepPos = axisDesignation.find(" ("); + std::string axisName; + std::string abbreviation; + if (sepPos != std::string::npos && axisDesignation.back() == ')') { + axisName = CoordinateSystemAxis::normalizeAxisName( + axisDesignation.substr(0, sepPos)); + abbreviation = axisDesignation.substr(sepPos + 2); + abbreviation.resize(abbreviation.size() - 1); + } else if (!axisDesignation.empty() && axisDesignation[0] == '(' && + axisDesignation.back() == ')') { + abbreviation = axisDesignation.substr(1, axisDesignation.size() - 2); + if (abbreviation == AxisAbbreviation::E) { + axisName = AxisName::Easting; + } else if (abbreviation == AxisAbbreviation::N) { + axisName = AxisName::Northing; + } else if (abbreviation == AxisAbbreviation::lat) { + axisName = AxisName::Latitude; + } else if (abbreviation == AxisAbbreviation::lon) { + axisName = AxisName::Longitude; + } + } else { + axisName = CoordinateSystemAxis::normalizeAxisName(axisDesignation); + if (axisName == AxisName::Latitude) { + abbreviation = AxisAbbreviation::lat; + } else if (axisName == AxisName::Longitude) { + abbreviation = AxisAbbreviation::lon; + } else if (axisName == AxisName::Ellipsoidal_height) { + abbreviation = AxisAbbreviation::h; + } + } + const std::string &dirString = children[1]->GP()->value(); + const AxisDirection *direction = AxisDirection::valueOf(dirString); + + // WKT2, geocentric CS: axis names are omitted + if (axisName.empty()) { + if (direction == &AxisDirection::GEOCENTRIC_X && + abbreviation == AxisAbbreviation::X) { + axisName = AxisName::Geocentric_X; + } else if (direction == &AxisDirection::GEOCENTRIC_Y && + abbreviation == AxisAbbreviation::Y) { + axisName = AxisName::Geocentric_Y; + } else if (direction == &AxisDirection::GEOCENTRIC_Z && + abbreviation == AxisAbbreviation::Z) { + axisName = AxisName::Geocentric_Z; + } + } + + // WKT1 + if (!direction && isGeocentric && axisName == AxisName::Geocentric_X) { + abbreviation = AxisAbbreviation::X; + direction = &AxisDirection::GEOCENTRIC_X; + } else if (!direction && isGeocentric && + axisName == AxisName::Geocentric_Y) { + abbreviation = AxisAbbreviation::Y; + direction = &AxisDirection::GEOCENTRIC_Y; + } else if (isGeocentric && axisName == AxisName::Geocentric_Z && + dirString == AxisDirectionWKT1::NORTH.toString()) { + abbreviation = AxisAbbreviation::Z; + direction = &AxisDirection::GEOCENTRIC_Z; + } else if (dirString == AxisDirectionWKT1::OTHER.toString()) { + direction = &AxisDirection::UNSPECIFIED; + } else if (!direction && AxisDirectionWKT1::valueOf(dirString) != nullptr) { + direction = AxisDirection::valueOf(tolower(dirString)); + } + + if (!direction) { + throw ParsingException( + concat("unhandled axis direction: ", children[1]->GP()->value())); + } + UnitOfMeasure unit(buildUnitInSubNode(node)); + if (unit == UnitOfMeasure::NONE) { + // If no unit in the AXIS node, use the one potentially coming from + // the CS. + unit = unitIn; + } + + auto &meridianNode = nodeP->lookForChild(WKTConstants::MERIDIAN); + + return CoordinateSystemAxis::create( + buildProperties(node).set(IdentifiedObject::NAME_KEY, axisName), + abbreviation, *direction, unit, + !isNull(meridianNode) ? buildMeridian(meridianNode).as_nullable() + : nullptr); +} + +// --------------------------------------------------------------------------- + +static const PropertyMap emptyPropertyMap{}; + +PROJ_NO_RETURN static void ThrowParsingException(const std::string &msg) { + throw ParsingException(msg); +} + +PROJ_NO_RETURN static void ThrowParsingExceptionMissingUNIT() { + throw ParsingException("buildCS: missing UNIT"); +} + +static ParsingException +buildParsingExceptionInvalidAxisCount(const std::string &csType) { + return ParsingException( + concat("buildCS: invalid CS axis count for ", csType)); +} + +CoordinateSystemNNPtr +WKTParser::Private::buildCS(const WKTNodeNNPtr &node, /* maybe null */ + const WKTNodeNNPtr &parentNode, + const UnitOfMeasure &defaultAngularUnit) { + bool isGeocentric = false; + std::string csType; + const int numberOfAxis = + parentNode->countChildrenOfName(WKTConstants::AXIS); + int axisCount = numberOfAxis; + if (!isNull(node)) { + const auto *nodeP = node->GP(); + const auto &children = nodeP->children(); + if (children.size() < 2) { + ThrowNotEnoughChildren(nodeP->value()); + } + csType = children[0]->GP()->value(); + try { + axisCount = std::stoi(children[1]->GP()->value()); + } catch (const std::exception &) { + ThrowParsingException(concat("buildCS: invalid CS axis count: ", + children[1]->GP()->value())); + } + } else { + const char *csTypeCStr = ""; + const auto &parentNodeName = parentNode->GP()->value(); + if (ci_equal(parentNodeName, WKTConstants::GEOCCS)) { + csTypeCStr = "Cartesian"; + isGeocentric = true; + if (axisCount == 0) { + auto unit = + buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); + if (unit == UnitOfMeasure::NONE) { + ThrowParsingExceptionMissingUNIT(); + } + return CartesianCS::createGeocentric(unit); + } + } else if (ci_equal(parentNodeName, WKTConstants::GEOGCS)) { + csTypeCStr = "Ellipsoidal"; + if (axisCount == 0) { + // Missing axis with GEOGCS ? Presumably Long/Lat order + // implied + auto unit = buildUnitInSubNode(parentNode, + UnitOfMeasure::Type::ANGULAR); + if (unit == UnitOfMeasure::NONE) { + ThrowParsingExceptionMissingUNIT(); + } + // WKT1 --> long/lat + return EllipsoidalCS::createLongitudeLatitude(unit); + } + } else if (ci_equal(parentNodeName, WKTConstants::BASEGEODCRS) || + ci_equal(parentNodeName, WKTConstants::BASEGEOGCRS)) { + csTypeCStr = "Ellipsoidal"; + if (axisCount == 0) { + auto unit = buildUnitInSubNode(parentNode, + UnitOfMeasure::Type::ANGULAR); + if (unit == UnitOfMeasure::NONE) { + unit = defaultAngularUnit; + } + // WKT2 --> presumably lat/long + return EllipsoidalCS::createLatitudeLongitude(unit); + } + } else if (ci_equal(parentNodeName, WKTConstants::PROJCS) || + ci_equal(parentNodeName, WKTConstants::BASEPROJCRS) || + ci_equal(parentNodeName, WKTConstants::BASEENGCRS)) { + csTypeCStr = "Cartesian"; + if (axisCount == 0) { + auto unit = + buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); + if (unit == UnitOfMeasure::NONE) { + if (ci_equal(parentNodeName, WKTConstants::PROJCS)) { + ThrowParsingExceptionMissingUNIT(); + } else { + unit = UnitOfMeasure::METRE; + } + } + return CartesianCS::createEastingNorthing(unit); + } + } else if (ci_equal(parentNodeName, WKTConstants::VERT_CS) || + ci_equal(parentNodeName, WKTConstants::BASEVERTCRS)) { + csTypeCStr = "vertical"; + if (axisCount == 0) { + auto unit = + buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); + if (unit == UnitOfMeasure::NONE) { + if (ci_equal(parentNodeName, WKTConstants::VERT_CS)) { + ThrowParsingExceptionMissingUNIT(); + } else { + unit = UnitOfMeasure::METRE; + } + } + return VerticalCS::createGravityRelatedHeight(unit); + } + } else if (ci_equal(parentNodeName, WKTConstants::LOCAL_CS)) { + if (axisCount == 0) { + auto unit = + buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); + if (unit == UnitOfMeasure::NONE) { + unit = UnitOfMeasure::METRE; + } + return CartesianCS::createEastingNorthing(unit); + } else if (axisCount == 1) { + csTypeCStr = "vertical"; + } else if (axisCount == 2) { + csTypeCStr = "Cartesian"; + } else { + throw ParsingException( + "buildCS: unexpected AXIS count for LOCAL_CS"); + } + } else if (ci_equal(parentNodeName, WKTConstants::BASEPARAMCRS)) { + csTypeCStr = "parametric"; + if (axisCount == 0) { + auto unit = + buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); + if (unit == UnitOfMeasure::NONE) { + unit = UnitOfMeasure("unknown", 1, + UnitOfMeasure::Type::PARAMETRIC); + } + return ParametricCS::create( + emptyPropertyMap, + CoordinateSystemAxis::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, + "unknown parametric"), + std::string(), AxisDirection::UNSPECIFIED, unit)); + } + } else if (ci_equal(parentNodeName, WKTConstants::BASETIMECRS)) { + csTypeCStr = "temporal"; + if (axisCount == 0) { + auto unit = + buildUnitInSubNode(parentNode, UnitOfMeasure::Type::TIME); + if (unit == UnitOfMeasure::NONE) { + unit = + UnitOfMeasure("unknown", 1, UnitOfMeasure::Type::TIME); + } + return DateTimeTemporalCS::create( + emptyPropertyMap, + CoordinateSystemAxis::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, + "unknown temporal"), + std::string(), AxisDirection::FUTURE, unit)); + } + } else { + // Shouldn't happen normally + throw ParsingException( + concat("buildCS: unexpected parent node: ", parentNodeName)); + } + csType = csTypeCStr; + } + + if (axisCount != 1 && axisCount != 2 && axisCount != 3) { + throw buildParsingExceptionInvalidAxisCount(csType); + } + if (numberOfAxis != axisCount) { + throw ParsingException("buildCS: declared number of axis by CS node " + "and number of AXIS are inconsistent"); + } + + UnitOfMeasure unit = buildUnitInSubNode( + parentNode, + ci_equal(csType, "ellipsoidal") + ? UnitOfMeasure::Type::ANGULAR + : ci_equal(csType, "ordinal") + ? UnitOfMeasure::Type::NONE + : ci_equal(csType, "parametric") + ? UnitOfMeasure::Type::PARAMETRIC + : ci_equal(csType, "Cartesian") || + ci_equal(csType, "vertical") + ? UnitOfMeasure::Type::LINEAR + : (ci_equal(csType, "temporal") || + ci_equal(csType, "TemporalDateTime") || + ci_equal(csType, "TemporalCount") || + ci_equal(csType, "TemporalMeasure")) + ? UnitOfMeasure::Type::TIME + : UnitOfMeasure::Type::UNKNOWN); + + std::vector<CoordinateSystemAxisNNPtr> axisList; + for (int i = 0; i < axisCount; i++) { + axisList.emplace_back( + buildAxis(parentNode->GP()->lookForChild(WKTConstants::AXIS, i), + unit, isGeocentric, i + 1)); + }; + + const PropertyMap &csMap = emptyPropertyMap; + if (ci_equal(csType, "ellipsoidal")) { + if (axisCount == 2) { + return EllipsoidalCS::create(csMap, axisList[0], axisList[1]); + } else if (axisCount == 3) { + return EllipsoidalCS::create(csMap, axisList[0], axisList[1], + axisList[2]); + } + } else if (ci_equal(csType, "Cartesian")) { + if (axisCount == 2) { + return CartesianCS::create(csMap, axisList[0], axisList[1]); + } else if (axisCount == 3) { + return CartesianCS::create(csMap, axisList[0], axisList[1], + axisList[2]); + } + } else if (ci_equal(csType, "vertical")) { + if (axisCount == 1) { + return VerticalCS::create(csMap, axisList[0]); + } + } else if (ci_equal(csType, "spherical")) { + if (axisCount == 3) { + return SphericalCS::create(csMap, axisList[0], axisList[1], + axisList[2]); + } + } else if (ci_equal(csType, "ordinal")) { // WKT2-2018 + return OrdinalCS::create(csMap, axisList); + } else if (ci_equal(csType, "parametric")) { + if (axisCount == 1) { + return ParametricCS::create(csMap, axisList[0]); + } + } else if (ci_equal(csType, "temporal")) { // WKT2-2015 + if (axisCount == 1) { + return DateTimeTemporalCS::create( + csMap, + axisList[0]); // FIXME: there are 3 possible subtypes of + // TemporalCS + } + } else if (ci_equal(csType, "TemporalDateTime")) { // WKT2-2018 + if (axisCount == 1) { + return DateTimeTemporalCS::create(csMap, axisList[0]); + } + } else if (ci_equal(csType, "TemporalCount")) { // WKT2-2018 + if (axisCount == 1) { + return TemporalCountCS::create(csMap, axisList[0]); + } + } else if (ci_equal(csType, "TemporalMeasure")) { // WKT2-2018 + if (axisCount == 1) { + return TemporalMeasureCS::create(csMap, axisList[0]); + } + } else { + throw ParsingException(concat("unhandled CS type: ", csType)); + } + throw buildParsingExceptionInvalidAxisCount(csType); +} + +// --------------------------------------------------------------------------- + +GeodeticCRSNNPtr +WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &datumNode = nodeP->lookForChild( + WKTConstants::DATUM, WKTConstants::GEODETICDATUM, WKTConstants::TRF); + auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE); + if (isNull(datumNode) && isNull(ensembleNode)) { + throw ParsingException("Missing DATUM or ENSEMBLE node"); + } + + auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC); + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + const auto &nodeName = nodeP->value(); + if (isNull(csNode) && !ci_equal(nodeName, WKTConstants::GEOGCS) && + !ci_equal(nodeName, WKTConstants::GEOCCS) && + !ci_equal(nodeName, WKTConstants::BASEGEODCRS) && + !ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) { + ThrowMissing(WKTConstants::CS); + } + + auto &primeMeridianNode = + nodeP->lookForChild(WKTConstants::PRIMEM, WKTConstants::PRIMEMERIDIAN); + if (isNull(primeMeridianNode)) { + // PRIMEM is required in WKT1 + if (ci_equal(nodeName, WKTConstants::GEOGCS) || + ci_equal(nodeName, WKTConstants::GEOCCS)) { + emitRecoverableAssertion(nodeName + " should have a PRIMEM node"); + } + } + + auto angularUnit = + buildUnitInSubNode(node, ci_equal(nodeName, WKTConstants::GEOGCS) + ? UnitOfMeasure::Type::ANGULAR + : UnitOfMeasure::Type::UNKNOWN); + if (angularUnit.type() != UnitOfMeasure::Type::ANGULAR) { + angularUnit = UnitOfMeasure::NONE; + } + + auto primeMeridian = + !isNull(primeMeridianNode) + ? buildPrimeMeridian(primeMeridianNode, angularUnit) + : PrimeMeridian::GREENWICH; + if (angularUnit == UnitOfMeasure::NONE) { + angularUnit = primeMeridian->longitude().unit(); + } + + auto datum = + !isNull(datumNode) + ? buildGeodeticReferenceFrame(datumNode, primeMeridian, dynamicNode) + .as_nullable() + : nullptr; + auto datumEnsemble = + !isNull(ensembleNode) + ? buildDatumEnsemble(ensembleNode, primeMeridian, true) + .as_nullable() + : nullptr; + auto cs = buildCS(csNode, node, angularUnit); + auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs); + if (ellipsoidalCS) { + assert(!ci_equal(nodeName, WKTConstants::GEOCCS)); + try { + return GeographicCRS::create(buildProperties(node), datum, + datumEnsemble, + NN_NO_CHECK(ellipsoidalCS)); + } catch (const util::Exception &e) { + throw ParsingException(std::string("buildGeodeticCRS: ") + + e.what()); + } + } else if (ci_equal(nodeName, WKTConstants::GEOGCRS) || + ci_equal(nodeName, WKTConstants::GEOGRAPHICCRS) || + ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) { + // This is a WKT2-2018 GeographicCRS. An ellipsoidal CS is expected + throw ParsingException(concat("ellipsoidal CS expected, but found ", + cs->getWKT2Type(true))); + } + + auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs); + if (cartesianCS) { + if (cartesianCS->axisList().size() != 3) { + throw ParsingException( + "Cartesian CS for a GeodeticCRS should have 3 axis"); + } + try { + return GeodeticCRS::create(buildProperties(node), datum, + datumEnsemble, NN_NO_CHECK(cartesianCS)); + } catch (const util::Exception &e) { + throw ParsingException(std::string("buildGeodeticCRS: ") + + e.what()); + } + } + + auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs); + if (sphericalCS) { + try { + return GeodeticCRS::create(buildProperties(node), datum, + datumEnsemble, NN_NO_CHECK(sphericalCS)); + } catch (const util::Exception &e) { + throw ParsingException(std::string("buildGeodeticCRS: ") + + e.what()); + } + } + + throw ParsingException( + concat("unhandled CS type: ", cs->getWKT2Type(true))); +} + +// --------------------------------------------------------------------------- + +CRSNNPtr WKTParser::Private::buildDerivedGeodeticCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &baseGeodCRSNode = nodeP->lookForChild(WKTConstants::BASEGEODCRS, + WKTConstants::BASEGEOGCRS); + // given the constraints enforced on calling code path + assert(!isNull(baseGeodCRSNode)); + + auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode); + + auto &derivingConversionNode = + nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); + if (isNull(derivingConversionNode)) { + ThrowMissing(WKTConstants::DERIVINGCONVERSION); + } + auto derivingConversion = buildConversion( + derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + if (isNull(csNode)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); + + auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs); + if (ellipsoidalCS) { + return DerivedGeographicCRS::create(buildProperties(node), baseGeodCRS, + derivingConversion, + NN_NO_CHECK(ellipsoidalCS)); + } else if (ci_equal(nodeP->value(), WKTConstants::GEOGCRS)) { + // This is a WKT2-2018 GeographicCRS. An ellipsoidal CS is expected + throw ParsingException(concat("ellipsoidal CS expected, but found ", + cs->getWKT2Type(true))); + } + + auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs); + if (cartesianCS) { + if (cartesianCS->axisList().size() != 3) { + throw ParsingException( + "Cartesian CS for a GeodeticCRS should have 3 axis"); + } + return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS, + derivingConversion, + NN_NO_CHECK(cartesianCS)); + } + + auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs); + if (sphericalCS) { + return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS, + derivingConversion, + NN_NO_CHECK(sphericalCS)); + } + + throw ParsingException( + concat("unhandled CS type: ", cs->getWKT2Type(true))); +} + +// --------------------------------------------------------------------------- + +UnitOfMeasure WKTParser::Private::guessUnitForParameter( + const std::string ¶mName, const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit) { + UnitOfMeasure unit; + // scale must be first because of 'Scale factor on pseudo standard parallel' + if (ci_find(paramName, "scale") != std::string::npos) { + unit = UnitOfMeasure::SCALE_UNITY; + } else if (ci_find(paramName, "latitude") != std::string::npos || + ci_find(paramName, "longitude") != std::string::npos || + ci_find(paramName, "meridian") != std::string::npos || + ci_find(paramName, "parallel") != std::string::npos || + ci_find(paramName, "azimuth") != std::string::npos || + ci_find(paramName, "angle") != std::string::npos) { + unit = defaultAngularUnit; + } else if (ci_find(paramName, "easting") != std::string::npos || + ci_find(paramName, "northing") != std::string::npos || + ci_find(paramName, "height") != std::string::npos) { + unit = defaultLinearUnit; + } + return unit; +} + +// --------------------------------------------------------------------------- + +void WKTParser::Private::consumeParameters( + const WKTNodeNNPtr &node, bool isAbridged, + std::vector<OperationParameterNNPtr> ¶meters, + std::vector<ParameterValueNNPtr> &values, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit) { + for (const auto &childNode : node->GP()->children()) { + const auto &childNodeChildren = childNode->GP()->children(); + if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { + if (childNodeChildren.size() < 2) { + ThrowNotEnoughChildren(childNode->GP()->value()); + } + parameters.push_back( + OperationParameter::create(buildProperties(childNode))); + const auto ¶mValue = childNodeChildren[1]->GP()->value(); + if (!paramValue.empty() && paramValue[0] == '"') { + values.push_back( + ParameterValue::create(stripQuotes(childNodeChildren[1]))); + } else { + try { + double val = asDouble(childNodeChildren[1]); + auto unit = buildUnitInSubNode(childNode); + if (unit == UnitOfMeasure::NONE) { + const auto ¶mName = + childNodeChildren[0]->GP()->value(); + unit = guessUnitForParameter( + paramName, defaultLinearUnit, defaultAngularUnit); + } + + if (isAbridged) { + const auto ¶mName = parameters.back()->nameStr(); + int paramEPSGCode = 0; + const auto ¶mIds = parameters.back()->identifiers(); + if (paramIds.size() == 1 && + ci_equal(*(paramIds[0]->codeSpace()), + Identifier::EPSG)) { + paramEPSGCode = ::atoi(paramIds[0]->code().c_str()); + } + const common::UnitOfMeasure *pUnit = nullptr; + if (OperationParameterValue::convertFromAbridged( + paramName, val, pUnit, paramEPSGCode)) { + unit = *pUnit; + parameters.back() = OperationParameter::create( + buildProperties(childNode) + .set(Identifier::CODESPACE_KEY, + Identifier::EPSG) + .set(Identifier::CODE_KEY, paramEPSGCode)); + } + } + + values.push_back( + ParameterValue::create(Measure(val, unit))); + } catch (const std::exception &) { + throw ParsingException(concat( + "unhandled parameter value type : ", paramValue)); + } + } + } else if (ci_equal(childNode->GP()->value(), + WKTConstants::PARAMETERFILE)) { + if (childNodeChildren.size() < 2) { + ThrowNotEnoughChildren(childNode->GP()->value()); + } + parameters.push_back( + OperationParameter::create(buildProperties(childNode))); + values.push_back(ParameterValue::createFilename( + stripQuotes(childNodeChildren[1]))); + } + } +} + +// --------------------------------------------------------------------------- + +ConversionNNPtr +WKTParser::Private::buildConversion(const WKTNodeNNPtr &node, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit) { + auto &methodNode = node->GP()->lookForChild(WKTConstants::METHOD, + WKTConstants::PROJECTION); + if (isNull(methodNode)) { + ThrowMissing(WKTConstants::METHOD); + } + if (methodNode->GP()->childrenSize() == 0) { + ThrowNotEnoughChildren(WKTConstants::METHOD); + } + + std::vector<OperationParameterNNPtr> parameters; + std::vector<ParameterValueNNPtr> values; + consumeParameters(node, false, parameters, values, defaultLinearUnit, + defaultAngularUnit); + + return Conversion::create(buildProperties(node), + buildProperties(methodNode), parameters, values); +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr +WKTParser::Private::buildCoordinateOperation(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD); + if (isNull(methodNode)) { + ThrowMissing(WKTConstants::METHOD); + } + if (methodNode->GP()->childrenSize() == 0) { + ThrowNotEnoughChildren(WKTConstants::METHOD); + } + + auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS); + if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) { + ThrowMissing(WKTConstants::SOURCECRS); + } + auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]); + if (!sourceCRS) { + throw ParsingException("Invalid content in SOURCECRS node"); + } + + auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS); + if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) { + ThrowMissing(WKTConstants::TARGETCRS); + } + auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]); + if (!targetCRS) { + throw ParsingException("Invalid content in TARGETCRS node"); + } + + auto &interpolationCRSNode = + nodeP->lookForChild(WKTConstants::INTERPOLATIONCRS); + CRSPtr interpolationCRS; + if (/*!isNull(interpolationCRSNode) && */ interpolationCRSNode->GP() + ->childrenSize() == 1) { + interpolationCRS = buildCRS(interpolationCRSNode->GP()->children()[0]); + } + + std::vector<OperationParameterNNPtr> parameters; + std::vector<ParameterValueNNPtr> values; + auto defaultLinearUnit = UnitOfMeasure::NONE; + auto defaultAngularUnit = UnitOfMeasure::NONE; + consumeParameters(node, false, parameters, values, defaultLinearUnit, + defaultAngularUnit); + + std::vector<PositionalAccuracyNNPtr> accuracies; + auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY); + if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) { + accuracies.push_back(PositionalAccuracy::create( + stripQuotes(accuracyNode->GP()->children()[0]))); + } + + return util::nn_static_pointer_cast<CoordinateOperation>( + Transformation::create(buildProperties(node), NN_NO_CHECK(sourceCRS), + NN_NO_CHECK(targetCRS), interpolationCRS, + buildProperties(methodNode), parameters, values, + accuracies)); +} + +// --------------------------------------------------------------------------- + +ConcatenatedOperationNNPtr +WKTParser::Private::buildConcatenatedOperation(const WKTNodeNNPtr &node) { + std::vector<CoordinateOperationNNPtr> operations; + for (const auto &childNode : node->GP()->children()) { + if (ci_equal(childNode->GP()->value(), WKTConstants::STEP)) { + if (childNode->GP()->childrenSize() != 1) { + throw ParsingException("Invalid content in STEP node"); + } + auto op = nn_dynamic_pointer_cast<CoordinateOperation>( + build(childNode->GP()->children()[0])); + if (!op) { + throw ParsingException("Invalid content in STEP node"); + } + operations.emplace_back(NN_NO_CHECK(op)); + } + } + try { + return ConcatenatedOperation::create( + buildProperties(node), operations, + std::vector<PositionalAccuracyNNPtr>()); + } catch (const InvalidOperation &e) { + throw ParsingException( + std::string("Cannot build concatenated operation: ") + e.what()); + } +} + +// --------------------------------------------------------------------------- + +bool WKTParser::Private::hasWebMercPROJ4String( + const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode) { + if (projectionNode->GP()->childrenSize() == 0) { + ThrowNotEnoughChildren(WKTConstants::PROJECTION); + } + const std::string wkt1ProjectionName = + stripQuotes(projectionNode->GP()->children()[0]); + + auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION); + + if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(), + "Mercator_1SP") && + projCRSNode->countChildrenOfName("center_latitude") == 0) { + + // Hack to detect the hacky way of encodign webmerc in GDAL WKT1 + // with a EXTENSION["PROJ", "+proj=merc +a=6378137 +b=6378137 + // +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m + // +nadgrids=@null +wktext +no_defs"] node + if (extensionNode && extensionNode->GP()->childrenSize() == 2 && + ci_equal(stripQuotes(extensionNode->GP()->children()[0]), + "PROJ4")) { + std::string projString = + stripQuotes(extensionNode->GP()->children()[1]); + if (projString.find("+proj=merc") != std::string::npos && + projString.find("+a=6378137") != std::string::npos && + projString.find("+b=6378137") != std::string::npos && + projString.find("+lon_0=0") != std::string::npos && + projString.find("+x_0=0") != std::string::npos && + projString.find("+y_0=0") != std::string::npos && + projString.find("+nadgrids=@null") != std::string::npos && + (projString.find("+lat_ts=") == std::string::npos || + projString.find("+lat_ts=0") != std::string::npos) && + (projString.find("+k=") == std::string::npos || + projString.find("+k=1") != std::string::npos) && + (projString.find("+units=") == std::string::npos || + projString.find("+units=m") != std::string::npos)) { + return true; + } + } + } + return false; +} + +// --------------------------------------------------------------------------- + +ConversionNNPtr WKTParser::Private::buildProjectionFromESRI( + const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit) { + const std::string esriProjectionName = + stripQuotes(projectionNode->GP()->children()[0]); + + // Lambert_Conformal_Conic or Krovak may map to different WKT2 methods + // depending + // on the parameters / their values + const auto esriMappings = getMappingsFromESRI(esriProjectionName); + if (esriMappings.empty()) { + return buildProjectionStandard(projCRSNode, projectionNode, + defaultLinearUnit, defaultAngularUnit); + } + + // Build a map of present parameters + std::map<std::string, std::string> mapParamNameToValue; + for (const auto &childNode : projCRSNode->GP()->children()) { + if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { + const auto &childNodeChildren = childNode->GP()->children(); + if (childNodeChildren.size() < 2) { + ThrowNotEnoughChildren(WKTConstants::PARAMETER); + } + const std::string parameterName(stripQuotes(childNodeChildren[0])); + const auto ¶mValue = childNodeChildren[1]->GP()->value(); + mapParamNameToValue[parameterName] = paramValue; + } + } + + // Compare parameters present with the ones expected in the mapping + const ESRIMethodMapping *esriMapping = esriMappings[0]; + int bestMatchCount = -1; + for (const auto &mapping : esriMappings) { + int matchCount = 0; + for (const auto *param = mapping->params; param->esri_name; ++param) { + auto iter = mapParamNameToValue.find(param->esri_name); + if (iter != mapParamNameToValue.end()) { + if (param->wkt2_name == nullptr) { + try { + if (param->fixed_value == io::asDouble(iter->second)) { + matchCount++; + } + } catch (const std::exception &) { + } + } else { + matchCount++; + } + } + } + if (matchCount > bestMatchCount) { + esriMapping = mapping; + bestMatchCount = matchCount; + } + } + + std::map<std::string, const char *> mapWKT2NameToESRIName; + for (const auto *param = esriMapping->params; param->esri_name; ++param) { + if (param->wkt2_name) { + mapWKT2NameToESRIName[param->wkt2_name] = param->esri_name; + } + } + + const auto *wkt2_mapping = getMapping(esriMapping->wkt2_name); + assert(wkt2_mapping); + if (esriProjectionName == "Stereographic") { + try { + if (std::fabs(io::asDouble( + mapParamNameToValue["Latitude_Of_Origin"])) == 90.0) { + wkt2_mapping = + getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A); + } + } catch (const std::exception &) { + } + } + + PropertyMap propertiesMethod; + propertiesMethod.set(IdentifiedObject::NAME_KEY, wkt2_mapping->wkt2_name); + if (wkt2_mapping->epsg_code != 0) { + propertiesMethod.set(Identifier::CODE_KEY, wkt2_mapping->epsg_code); + propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG); + } + + std::vector<OperationParameterNNPtr> parameters; + std::vector<ParameterValueNNPtr> values; + + if (wkt2_mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL && + ci_equal(esriProjectionName, "Plate_Carree")) { + // Add a fixed Latitude of 1st parallel = 0 so as to have all + // parameters expected by Equidistant Cylindrical. + mapWKT2NameToESRIName[EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL] = + "Standard_Parallel_1"; + mapParamNameToValue["Standard_Parallel_1"] = "0"; + } else if ((wkt2_mapping->epsg_code == + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A || + wkt2_mapping->epsg_code == + EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) && + !ci_equal(esriProjectionName, + "Rectified_Skew_Orthomorphic_Natural_Origin") && + !ci_equal(esriProjectionName, + "Rectified_Skew_Orthomorphic_Center")) { + // ESRI WKT lacks the angle to skew grid + // Take it from the azimuth value + mapWKT2NameToESRIName + [EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID] = "Azimuth"; + } + + for (int i = 0; wkt2_mapping->params[i] != nullptr; i++) { + const auto *paramMapping = wkt2_mapping->params[i]; + + auto iter = mapWKT2NameToESRIName.find(paramMapping->wkt2_name); + if (iter == mapWKT2NameToESRIName.end()) { + continue; + } + auto iter2 = mapParamNameToValue.find(iter->second); + if (iter2 == mapParamNameToValue.end()) { + continue; + } + + PropertyMap propertiesParameter; + propertiesParameter.set(IdentifiedObject::NAME_KEY, + paramMapping->wkt2_name); + if (paramMapping->epsg_code != 0) { + propertiesParameter.set(Identifier::CODE_KEY, + paramMapping->epsg_code); + propertiesParameter.set(Identifier::CODESPACE_KEY, + Identifier::EPSG); + } + parameters.push_back(OperationParameter::create(propertiesParameter)); + + try { + double val = io::asDouble(iter2->second); + auto unit = guessUnitForParameter( + paramMapping->wkt2_name, defaultLinearUnit, defaultAngularUnit); + values.push_back(ParameterValue::create(Measure(val, unit))); + } catch (const std::exception &) { + throw ParsingException( + concat("unhandled parameter value type : ", iter2->second)); + } + } + + return Conversion::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), + propertiesMethod, parameters, values) + ->identify(); +} + +// --------------------------------------------------------------------------- + +ConversionNNPtr +WKTParser::Private::buildProjection(const WKTNodeNNPtr &projCRSNode, + const WKTNodeNNPtr &projectionNode, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit) { + if (projectionNode->GP()->childrenSize() == 0) { + ThrowNotEnoughChildren(WKTConstants::PROJECTION); + } + if (esriStyle_) { + return buildProjectionFromESRI(projCRSNode, projectionNode, + defaultLinearUnit, defaultAngularUnit); + } + return buildProjectionStandard(projCRSNode, projectionNode, + defaultLinearUnit, defaultAngularUnit); +} +// --------------------------------------------------------------------------- + +ConversionNNPtr WKTParser::Private::buildProjectionStandard( + const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, + const UnitOfMeasure &defaultLinearUnit, + const UnitOfMeasure &defaultAngularUnit) { + const std::string wkt1ProjectionName = + stripQuotes(projectionNode->GP()->children()[0]); + + std::vector<OperationParameterNNPtr> parameters; + std::vector<ParameterValueNNPtr> values; + bool tryToIdentifyWKT1Method = true; + + auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION); + const auto &extensionChildren = extensionNode->GP()->children(); + + if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(), + "Mercator_1SP") && + projCRSNode->countChildrenOfName("center_latitude") == 0) { + + // The latitude of origin, which should always be zero, is + // missing + // in GDAL WKT1, but provisionned in the EPSG Mercator_1SP + // definition, + // so add it manually. + PropertyMap propertiesParameter; + propertiesParameter.set(IdentifiedObject::NAME_KEY, + "Latitude of natural origin"); + propertiesParameter.set(Identifier::CODE_KEY, 8801); + propertiesParameter.set(Identifier::CODESPACE_KEY, Identifier::EPSG); + parameters.push_back(OperationParameter::create(propertiesParameter)); + values.push_back( + ParameterValue::create(Measure(0, UnitOfMeasure::DEGREE))); + + } else if (metadata::Identifier::isEquivalentName( + wkt1ProjectionName.c_str(), "Polar_Stereographic")) { + std::map<std::string, Measure> mapParameters; + for (const auto &childNode : projCRSNode->GP()->children()) { + const auto &childNodeChildren = childNode->GP()->children(); + if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) && + childNodeChildren.size() == 2) { + const std::string wkt1ParameterName( + stripQuotes(childNodeChildren[0])); + try { + double val = asDouble(childNodeChildren[1]); + auto unit = guessUnitForParameter(wkt1ParameterName, + defaultLinearUnit, + defaultAngularUnit); + mapParameters.insert(std::pair<std::string, Measure>( + wkt1ParameterName, Measure(val, unit))); + } catch (const std::exception &) { + } + } + } + + Measure latitudeOfOrigin = mapParameters["latitude_of_origin"]; + Measure centralMeridian = mapParameters["central_meridian"]; + Measure scaleFactorFromMap = mapParameters["scale_factor"]; + Measure scaleFactor((scaleFactorFromMap.unit() == UnitOfMeasure::NONE) + ? Measure(1.0, UnitOfMeasure::SCALE_UNITY) + : scaleFactorFromMap); + Measure falseEasting = mapParameters["false_easting"]; + Measure falseNorthing = mapParameters["false_northing"]; + if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE && + std::fabs(std::fabs(latitudeOfOrigin.convertToUnit( + UnitOfMeasure::DEGREE)) - + 90.0) < 1e-10) { + return Conversion::createPolarStereographicVariantA( + PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), + Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()), + Angle(centralMeridian.value(), centralMeridian.unit()), + Scale(scaleFactor.value(), scaleFactor.unit()), + Length(falseEasting.value(), falseEasting.unit()), + Length(falseNorthing.value(), falseNorthing.unit())); + } else if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE && + scaleFactor.getSIValue() == 1.0) { + return Conversion::createPolarStereographicVariantB( + PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), + Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()), + Angle(centralMeridian.value(), centralMeridian.unit()), + Length(falseEasting.value(), falseEasting.unit()), + Length(falseNorthing.value(), falseNorthing.unit())); + } + tryToIdentifyWKT1Method = false; + // Import GDAL PROJ4 extension nodes + } else if (extensionChildren.size() == 2 && + ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) { + std::string projString = stripQuotes(extensionChildren[1]); + if (starts_with(projString, "+proj=")) { + try { + auto projObj = + PROJStringParser().createFromPROJString(projString); + auto projObjCrs = + nn_dynamic_pointer_cast<ProjectedCRS>(projObj); + if (projObjCrs) { + return projObjCrs->derivingConversion(); + } + } catch (const io::ParsingException &) { + } + } + } + + std::string projectionName(wkt1ProjectionName); + const MethodMapping *mapping = + tryToIdentifyWKT1Method ? getMappingFromWKT1(projectionName) : nullptr; + + // For Krovak, we need to look at axis to decide between the Krovak and + // Krovak East-North Oriented methods + if (ci_equal(projectionName, "Krovak") && + projCRSNode->countChildrenOfName(WKTConstants::AXIS) == 2 && + &buildAxis( + projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 0), + defaultLinearUnit, false, + 1)->direction() == &AxisDirection::SOUTH && + &buildAxis( + projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 1), + defaultLinearUnit, false, + 2)->direction() == &AxisDirection::WEST) { + mapping = getMapping(EPSG_CODE_METHOD_KROVAK); + } + + PropertyMap propertiesMethod; + if (mapping) { + projectionName = mapping->wkt2_name; + if (mapping->epsg_code != 0) { + propertiesMethod.set(Identifier::CODE_KEY, mapping->epsg_code); + propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG); + } + } + propertiesMethod.set(IdentifiedObject::NAME_KEY, projectionName); + + for (const auto &childNode : projCRSNode->GP()->children()) { + if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { + const auto &childNodeChildren = childNode->GP()->children(); + if (childNodeChildren.size() < 2) { + ThrowNotEnoughChildren(WKTConstants::PARAMETER); + } + PropertyMap propertiesParameter; + const std::string wkt1ParameterName( + stripQuotes(childNodeChildren[0])); + std::string parameterName(wkt1ParameterName); + const auto *paramMapping = + mapping ? getMappingFromWKT1(mapping, parameterName) : nullptr; + if (paramMapping) { + parameterName = paramMapping->wkt2_name; + if (paramMapping->epsg_code != 0) { + propertiesParameter.set(Identifier::CODE_KEY, + paramMapping->epsg_code); + propertiesParameter.set(Identifier::CODESPACE_KEY, + Identifier::EPSG); + } + } + propertiesParameter.set(IdentifiedObject::NAME_KEY, parameterName); + parameters.push_back( + OperationParameter::create(propertiesParameter)); + const auto ¶mValue = childNodeChildren[1]->GP()->value(); + try { + double val = io::asDouble(paramValue); + auto unit = guessUnitForParameter( + wkt1ParameterName, defaultLinearUnit, defaultAngularUnit); + values.push_back(ParameterValue::create(Measure(val, unit))); + } catch (const std::exception &) { + throw ParsingException( + concat("unhandled parameter value type : ", paramValue)); + } + } + } + + return Conversion::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), + propertiesMethod, parameters, values) + ->identify(); +} + +// --------------------------------------------------------------------------- + +ProjectedCRSNNPtr +WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) { + + const auto *nodeP = node->GP(); + auto &conversionNode = nodeP->lookForChild(WKTConstants::CONVERSION); + auto &projectionNode = nodeP->lookForChild(WKTConstants::PROJECTION); + if (isNull(conversionNode) && isNull(projectionNode)) { + ThrowMissing(WKTConstants::CONVERSION); + } + if (isNull(conversionNode) && hasWebMercPROJ4String(node, projectionNode)) { + auto conversion = Conversion::createPopularVisualisationPseudoMercator( + PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(0), + Angle(0), Length(0), Length(0)); + return ProjectedCRS::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, + "WGS 84 / Pseudo-Mercator"), + GeographicCRS::EPSG_4326, conversion, + CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); + } + + auto &baseGeodCRSNode = + nodeP->lookForChild(WKTConstants::BASEGEODCRS, + WKTConstants::BASEGEOGCRS, WKTConstants::GEOGCS); + if (isNull(baseGeodCRSNode)) { + throw ParsingException( + "Missing BASEGEODCRS / BASEGEOGCRS / GEOGCS node"); + } + auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode); + + auto linearUnit = buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR); + auto angularUnit = baseGeodCRS->coordinateSystem()->axisList()[0]->unit(); + + auto conversion = + !isNull(conversionNode) + ? 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); + + if (isNull(csNode) && node->countChildrenOfName(WKTConstants::AXIS) == 0) { + const auto methodCode = conversion->method()->getEPSGCode(); + // Krovak south oriented ? + if (methodCode == EPSG_CODE_METHOD_KROVAK) { + cartesianCS = + CartesianCS::create( + PropertyMap(), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Southing), + emptyString, AxisDirection::SOUTH, linearUnit), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Westing), + emptyString, AxisDirection::WEST, linearUnit)) + .as_nullable(); + } else if (esriStyle_ && + methodCode == + EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A) { + // It is likely that the ESRI definition of EPSG:32661 (UPS North) & + // EPSG:32761 (UPS South) uses the easting-northing order, instead + // of the EPSG northing-easting order. + const double lat0 = conversion->parameterValueNumeric( + EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, + common::UnitOfMeasure::DEGREE); + if (std::fabs(lat0 - 90) < 1e-10) { + cartesianCS = + CartesianCS::create( + PropertyMap(), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Easting), + AxisAbbreviation::E, AxisDirection::SOUTH, + linearUnit, + Meridian::create(Angle(90, UnitOfMeasure::DEGREE))), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Northing), + AxisAbbreviation::N, AxisDirection::SOUTH, + linearUnit, Meridian::create( + Angle(180, UnitOfMeasure::DEGREE)))) + .as_nullable(); + } else if (std::fabs(lat0 - -90) < 1e-10) { + cartesianCS = + CartesianCS::create( + PropertyMap(), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Easting), + AxisAbbreviation::E, AxisDirection::NORTH, + linearUnit, + Meridian::create(Angle(90, UnitOfMeasure::DEGREE))), + CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Northing), + AxisAbbreviation::N, AxisDirection::NORTH, + linearUnit, + Meridian::create(Angle(0, UnitOfMeasure::DEGREE)))) + .as_nullable(); + } + } + } + if (!cartesianCS) { + ThrowNotExpectedCSType("Cartesian"); + } + + auto props = buildProperties(node); + if (esriStyle_ && dbContext_) { + auto projCRSName = stripQuotes(nodeP->children()[0]); + + // It is likely that the ESRI definition of EPSG:32661 (UPS North) & + // EPSG:32761 (UPS South) uses the easting-northing order, instead + // of the EPSG northing-easting order + // so don't substitue names to avoid confusion. + if (projCRSName == "UPS_North") { + props.set(IdentifiedObject::NAME_KEY, "WGS 84 / UPS North (E,N)"); + } else if (projCRSName == "UPS_South") { + props.set(IdentifiedObject::NAME_KEY, "WGS 84 / UPS South (E,N)"); + } else { + std::string outTableName; + std::string authNameFromAlias; + std::string codeFromAlias; + auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), + std::string()); + auto officialName = authFactory->getOfficialNameFromAlias( + projCRSName, "projected_crs", "ESRI", outTableName, + authNameFromAlias, codeFromAlias); + if (!officialName.empty()) { + props.set(IdentifiedObject::NAME_KEY, officialName); + } + } + } + + return ProjectedCRS::create(props, baseGeodCRS, conversion, + NN_NO_CHECK(cartesianCS)); +} + +// --------------------------------------------------------------------------- + +void WKTParser::Private::parseDynamic(const WKTNodeNNPtr &dynamicNode, + double &frameReferenceEpoch, + util::optional<std::string> &modelName) { + auto &frameEpochNode = dynamicNode->lookForChild(WKTConstants::FRAMEEPOCH); + const auto &frameEpochChildren = frameEpochNode->GP()->children(); + if (frameEpochChildren.empty()) { + ThrowMissing(WKTConstants::FRAMEEPOCH); + } + try { + frameReferenceEpoch = asDouble(frameEpochChildren[0]); + } catch (const std::exception &) { + throw ParsingException("Invalid FRAMEEPOCH node"); + } + auto &modelNode = dynamicNode->GP()->lookForChild( + WKTConstants::MODEL, WKTConstants::VELOCITYGRID); + const auto &modelChildren = modelNode->GP()->children(); + if (modelChildren.size() == 1) { + modelName = stripQuotes(modelChildren[0]); + } +} + +// --------------------------------------------------------------------------- + +VerticalReferenceFrameNNPtr WKTParser::Private::buildVerticalReferenceFrame( + const WKTNodeNNPtr &node, const WKTNodeNNPtr &dynamicNode) { + + if (!isNull(dynamicNode)) { + double frameReferenceEpoch = 0.0; + util::optional<std::string> modelName; + parseDynamic(dynamicNode, frameReferenceEpoch, modelName); + return DynamicVerticalReferenceFrame::create( + buildProperties(node), getAnchor(node), + optional<RealizationMethod>(), + common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR), + modelName); + } + + // WKT1 VERT_DATUM has a datum type after the datum name that we ignore. + return VerticalReferenceFrame::create(buildProperties(node), + getAnchor(node)); +} + +// --------------------------------------------------------------------------- + +TemporalDatumNNPtr +WKTParser::Private::buildTemporalDatum(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &calendarNode = nodeP->lookForChild(WKTConstants::CALENDAR); + std::string calendar = TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN; + const auto &calendarChildren = calendarNode->GP()->children(); + if (calendarChildren.size() == 1) { + calendar = stripQuotes(calendarChildren[0]); + } + + auto &timeOriginNode = nodeP->lookForChild(WKTConstants::TIMEORIGIN); + std::string originStr; + const auto &timeOriginNodeChildren = timeOriginNode->GP()->children(); + if (timeOriginNodeChildren.size() == 1) { + originStr = stripQuotes(timeOriginNodeChildren[0]); + } + auto origin = DateTime::create(originStr); + return TemporalDatum::create(buildProperties(node), origin, calendar); +} + +// --------------------------------------------------------------------------- + +EngineeringDatumNNPtr +WKTParser::Private::buildEngineeringDatum(const WKTNodeNNPtr &node) { + return EngineeringDatum::create(buildProperties(node), getAnchor(node)); +} + +// --------------------------------------------------------------------------- + +ParametricDatumNNPtr +WKTParser::Private::buildParametricDatum(const WKTNodeNNPtr &node) { + return ParametricDatum::create(buildProperties(node), getAnchor(node)); +} + +// --------------------------------------------------------------------------- + +CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &datumNode = + nodeP->lookForChild(WKTConstants::VDATUM, WKTConstants::VERT_DATUM, + WKTConstants::VERTICALDATUM, WKTConstants::VRF); + auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE); + if (isNull(datumNode) && isNull(ensembleNode)) { + throw ParsingException("Missing VDATUM or ENSEMBLE node"); + } + + auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC); + auto datum = + !isNull(datumNode) + ? buildVerticalReferenceFrame(datumNode, dynamicNode).as_nullable() + : nullptr; + auto datumEnsemble = + !isNull(ensembleNode) + ? buildDatumEnsemble(ensembleNode, nullptr, false).as_nullable() + : nullptr; + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + const auto &nodeValue = nodeP->value(); + if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::VERT_CS) && + !ci_equal(nodeValue, WKTConstants::BASEVERTCRS)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); + auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(cs); + if (!verticalCS) { + ThrowNotExpectedCSType("vertical"); + } + + auto crs = nn_static_pointer_cast<CRS>(VerticalCRS::create( + buildProperties(node), datum, datumEnsemble, NN_NO_CHECK(verticalCS))); + + if (!isNull(datumNode)) { + auto &extensionNode = datumNode->lookForChild(WKTConstants::EXTENSION); + const auto &extensionChildren = extensionNode->GP()->children(); + if (extensionChildren.size() == 2) { + if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) { + std::string transformationName(crs->nameStr()); + if (!ends_with(transformationName, " height")) { + transformationName += " height"; + } + transformationName += " to WGS84 ellipsoidal height"; + auto transformation = + Transformation::createGravityRelatedHeightToGeographic3D( + PropertyMap().set(IdentifiedObject::NAME_KEY, + transformationName), + crs, GeographicCRS::EPSG_4979, + stripQuotes(extensionChildren[1]), + std::vector<PositionalAccuracyNNPtr>()); + return nn_static_pointer_cast<CRS>(BoundCRS::create( + crs, GeographicCRS::EPSG_4979, transformation)); + } + } + } + + return crs; +} + +// --------------------------------------------------------------------------- + +DerivedVerticalCRSNNPtr +WKTParser::Private::buildDerivedVerticalCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &baseVertCRSNode = nodeP->lookForChild(WKTConstants::BASEVERTCRS); + // given the constraints enforced on calling code path + assert(!isNull(baseVertCRSNode)); + + auto baseVertCRS_tmp = buildVerticalCRS(baseVertCRSNode); + auto baseVertCRS = NN_NO_CHECK(baseVertCRS_tmp->extractVerticalCRS()); + + auto &derivingConversionNode = + nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); + if (isNull(derivingConversionNode)) { + ThrowMissing(WKTConstants::DERIVINGCONVERSION); + } + auto derivingConversion = buildConversion( + derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + if (isNull(csNode)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); + + auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(cs); + if (!verticalCS) { + throw ParsingException( + concat("vertical CS expected, but found ", cs->getWKT2Type(true))); + } + + return DerivedVerticalCRS::create(buildProperties(node), baseVertCRS, + derivingConversion, + NN_NO_CHECK(verticalCS)); +} + +// --------------------------------------------------------------------------- + +CompoundCRSNNPtr +WKTParser::Private::buildCompoundCRS(const WKTNodeNNPtr &node) { + std::vector<CRSNNPtr> components; + for (const auto &child : node->GP()->children()) { + auto crs = buildCRS(child); + if (crs) { + components.push_back(NN_NO_CHECK(crs)); + } + } + return CompoundCRS::create(buildProperties(node), components); +} + +// --------------------------------------------------------------------------- + +BoundCRSNNPtr WKTParser::Private::buildBoundCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &abridgedNode = + nodeP->lookForChild(WKTConstants::ABRIDGEDTRANSFORMATION); + if (isNull(abridgedNode)) { + ThrowNotEnoughChildren(WKTConstants::ABRIDGEDTRANSFORMATION); + } + + auto &methodNode = abridgedNode->GP()->lookForChild(WKTConstants::METHOD); + if (isNull(methodNode)) { + ThrowMissing(WKTConstants::METHOD); + } + if (methodNode->GP()->childrenSize() == 0) { + ThrowNotEnoughChildren(WKTConstants::METHOD); + } + + auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS); + const auto &sourceCRSNodeChildren = sourceCRSNode->GP()->children(); + if (sourceCRSNodeChildren.size() != 1) { + ThrowNotEnoughChildren(WKTConstants::SOURCECRS); + } + auto sourceCRS = buildCRS(sourceCRSNodeChildren[0]); + if (!sourceCRS) { + throw ParsingException("Invalid content in SOURCECRS node"); + } + + auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS); + const auto &targetCRSNodeChildren = targetCRSNode->GP()->children(); + if (targetCRSNodeChildren.size() != 1) { + ThrowNotEnoughChildren(WKTConstants::TARGETCRS); + } + auto targetCRS = buildCRS(targetCRSNodeChildren[0]); + if (!targetCRS) { + throw ParsingException("Invalid content in TARGETCRS node"); + } + + std::vector<OperationParameterNNPtr> parameters; + std::vector<ParameterValueNNPtr> values; + auto defaultLinearUnit = UnitOfMeasure::NONE; + auto defaultAngularUnit = UnitOfMeasure::NONE; + consumeParameters(abridgedNode, true, parameters, values, defaultLinearUnit, + defaultAngularUnit); + + CRSPtr sourceTransformationCRS; + if (dynamic_cast<GeographicCRS *>(targetCRS.get())) { + sourceTransformationCRS = sourceCRS->extractGeographicCRS(); + if (!sourceTransformationCRS) { + throw ParsingException("Cannot find GeographicCRS in sourceCRS"); + } + } else { + sourceTransformationCRS = sourceCRS; + } + + auto transformation = Transformation::create( + buildProperties(abridgedNode), NN_NO_CHECK(sourceTransformationCRS), + NN_NO_CHECK(targetCRS), nullptr, buildProperties(methodNode), + parameters, values, std::vector<PositionalAccuracyNNPtr>()); + + return BoundCRS::create(NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), + transformation); +} + +// --------------------------------------------------------------------------- + +TemporalCSNNPtr +WKTParser::Private::buildTemporalCS(const WKTNodeNNPtr &parentNode) { + + auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS); + if (isNull(csNode) && + !ci_equal(parentNode->GP()->value(), WKTConstants::BASETIMECRS)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE); + auto temporalCS = nn_dynamic_pointer_cast<TemporalCS>(cs); + if (!temporalCS) { + ThrowNotExpectedCSType("temporal"); + } + return NN_NO_CHECK(temporalCS); +} + +// --------------------------------------------------------------------------- + +TemporalCRSNNPtr +WKTParser::Private::buildTemporalCRS(const WKTNodeNNPtr &node) { + auto &datumNode = + node->GP()->lookForChild(WKTConstants::TDATUM, WKTConstants::TIMEDATUM); + if (isNull(datumNode)) { + throw ParsingException("Missing TDATUM / TIMEDATUM node"); + } + + return TemporalCRS::create(buildProperties(node), + buildTemporalDatum(datumNode), + buildTemporalCS(node)); +} + +// --------------------------------------------------------------------------- + +DerivedTemporalCRSNNPtr +WKTParser::Private::buildDerivedTemporalCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &baseCRSNode = nodeP->lookForChild(WKTConstants::BASETIMECRS); + // given the constraints enforced on calling code path + assert(!isNull(baseCRSNode)); + + auto &derivingConversionNode = + nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); + if (isNull(derivingConversionNode)) { + ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); + } + + return DerivedTemporalCRS::create( + buildProperties(node), buildTemporalCRS(baseCRSNode), + buildConversion(derivingConversionNode, UnitOfMeasure::NONE, + UnitOfMeasure::NONE), + buildTemporalCS(node)); +} + +// --------------------------------------------------------------------------- + +EngineeringCRSNNPtr +WKTParser::Private::buildEngineeringCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &datumNode = nodeP->lookForChild(WKTConstants::EDATUM, + WKTConstants::ENGINEERINGDATUM); + if (isNull(datumNode)) { + throw ParsingException("Missing EDATUM / ENGINEERINGDATUM node"); + } + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::BASEENGCRS)) { + ThrowMissing(WKTConstants::CS); + } + + auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); + return EngineeringCRS::create(buildProperties(node), + buildEngineeringDatum(datumNode), cs); +} + +// --------------------------------------------------------------------------- + +EngineeringCRSNNPtr +WKTParser::Private::buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node) { + auto &datumNode = node->GP()->lookForChild(WKTConstants::LOCAL_DATUM); + auto cs = buildCS(null_node, node, UnitOfMeasure::NONE); + auto datum = EngineeringDatum::create( + !isNull(datumNode) + ? buildProperties(datumNode) + : + // In theory OGC 01-009 mandates LOCAL_DATUM, but GDAL has a + // tradition of emitting just LOCAL_CS["foo"] + emptyPropertyMap); + return EngineeringCRS::create(buildProperties(node), datum, cs); +} + +// --------------------------------------------------------------------------- + +DerivedEngineeringCRSNNPtr +WKTParser::Private::buildDerivedEngineeringCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &baseEngCRSNode = nodeP->lookForChild(WKTConstants::BASEENGCRS); + // given the constraints enforced on calling code path + assert(!isNull(baseEngCRSNode)); + + auto baseEngCRS = buildEngineeringCRS(baseEngCRSNode); + + auto &derivingConversionNode = + nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); + if (isNull(derivingConversionNode)) { + ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); + } + auto derivingConversion = buildConversion( + derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + if (isNull(csNode)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); + + return DerivedEngineeringCRS::create(buildProperties(node), baseEngCRS, + derivingConversion, cs); +} + +// --------------------------------------------------------------------------- + +ParametricCSNNPtr +WKTParser::Private::buildParametricCS(const WKTNodeNNPtr &parentNode) { + + auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS); + if (isNull(csNode) && + !ci_equal(parentNode->GP()->value(), WKTConstants::BASEPARAMCRS)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE); + auto parametricCS = nn_dynamic_pointer_cast<ParametricCS>(cs); + if (!parametricCS) { + ThrowNotExpectedCSType("parametric"); + } + return NN_NO_CHECK(parametricCS); +} + +// --------------------------------------------------------------------------- + +ParametricCRSNNPtr +WKTParser::Private::buildParametricCRS(const WKTNodeNNPtr &node) { + auto &datumNode = node->GP()->lookForChild(WKTConstants::PDATUM, + WKTConstants::PARAMETRICDATUM); + if (isNull(datumNode)) { + throw ParsingException("Missing PDATUM / PARAMETRICDATUM node"); + } + + return ParametricCRS::create(buildProperties(node), + buildParametricDatum(datumNode), + buildParametricCS(node)); +} + +// --------------------------------------------------------------------------- + +DerivedParametricCRSNNPtr +WKTParser::Private::buildDerivedParametricCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &baseParamCRSNode = nodeP->lookForChild(WKTConstants::BASEPARAMCRS); + // given the constraints enforced on calling code path + assert(!isNull(baseParamCRSNode)); + + auto &derivingConversionNode = + nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); + if (isNull(derivingConversionNode)) { + ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); + } + + return DerivedParametricCRS::create( + buildProperties(node), buildParametricCRS(baseParamCRSNode), + buildConversion(derivingConversionNode, UnitOfMeasure::NONE, + UnitOfMeasure::NONE), + buildParametricCS(node)); +} + +// --------------------------------------------------------------------------- + +DerivedProjectedCRSNNPtr +WKTParser::Private::buildDerivedProjectedCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + auto &baseProjCRSNode = nodeP->lookForChild(WKTConstants::BASEPROJCRS); + if (isNull(baseProjCRSNode)) { + ThrowNotEnoughChildren(WKTConstants::BASEPROJCRS); + } + auto baseProjCRS = buildProjectedCRS(baseProjCRSNode); + + auto &conversionNode = + nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); + if (isNull(conversionNode)) { + ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); + } + + auto linearUnit = buildUnitInSubNode(node); + auto angularUnit = + baseProjCRS->baseCRS()->coordinateSystem()->axisList()[0]->unit(); + + auto conversion = buildConversion(conversionNode, linearUnit, angularUnit); + + auto &csNode = nodeP->lookForChild(WKTConstants::CS); + if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::PROJCS)) { + ThrowMissing(WKTConstants::CS); + } + auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); + return DerivedProjectedCRS::create(buildProperties(node), baseProjCRS, + conversion, cs); +} + +// --------------------------------------------------------------------------- + +static bool isGeodeticCRS(const std::string &name) { + return ci_equal(name, WKTConstants::GEODCRS) || // WKT2 + ci_equal(name, WKTConstants::GEODETICCRS) || // WKT2 + ci_equal(name, WKTConstants::GEOGCRS) || // WKT2 2018 + ci_equal(name, WKTConstants::GEOGRAPHICCRS) || // WKT2 2018 + ci_equal(name, WKTConstants::GEOGCS) || // WKT1 + ci_equal(name, WKTConstants::GEOCCS); // WKT1 +} + +// --------------------------------------------------------------------------- + +CRSPtr WKTParser::Private::buildCRS(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + const std::string &name(nodeP->value()); + + if (isGeodeticCRS(name)) { + if (!isNull(nodeP->lookForChild(WKTConstants::BASEGEOGCRS, + WKTConstants::BASEGEODCRS))) { + return buildDerivedGeodeticCRS(node); + } else { + return util::nn_static_pointer_cast<CRS>(buildGeodeticCRS(node)); + } + } + + if (ci_equal(name, WKTConstants::PROJCS) || + ci_equal(name, WKTConstants::PROJCRS) || + ci_equal(name, WKTConstants::PROJECTEDCRS)) { + return util::nn_static_pointer_cast<CRS>(buildProjectedCRS(node)); + } + + if (ci_equal(name, WKTConstants::VERT_CS) || + ci_equal(name, WKTConstants::VERTCRS) || + ci_equal(name, WKTConstants::VERTICALCRS)) { + if (!isNull(nodeP->lookForChild(WKTConstants::BASEVERTCRS))) { + return util::nn_static_pointer_cast<CRS>( + buildDerivedVerticalCRS(node)); + } else { + return util::nn_static_pointer_cast<CRS>(buildVerticalCRS(node)); + } + } + + if (ci_equal(name, WKTConstants::COMPD_CS) || + ci_equal(name, WKTConstants::COMPOUNDCRS)) { + return util::nn_static_pointer_cast<CRS>(buildCompoundCRS(node)); + } + + if (ci_equal(name, WKTConstants::BOUNDCRS)) { + return util::nn_static_pointer_cast<CRS>(buildBoundCRS(node)); + } + + if (ci_equal(name, WKTConstants::TIMECRS)) { + if (!isNull(nodeP->lookForChild(WKTConstants::BASETIMECRS))) { + return util::nn_static_pointer_cast<CRS>( + buildDerivedTemporalCRS(node)); + } else { + return util::nn_static_pointer_cast<CRS>(buildTemporalCRS(node)); + } + } + + if (ci_equal(name, WKTConstants::DERIVEDPROJCRS)) { + return util::nn_static_pointer_cast<CRS>( + buildDerivedProjectedCRS(node)); + } + + if (ci_equal(name, WKTConstants::ENGCRS) || + ci_equal(name, WKTConstants::ENGINEERINGCRS)) { + if (!isNull(nodeP->lookForChild(WKTConstants::BASEENGCRS))) { + return util::nn_static_pointer_cast<CRS>( + buildDerivedEngineeringCRS(node)); + } else { + return util::nn_static_pointer_cast<CRS>(buildEngineeringCRS(node)); + } + } + + if (ci_equal(name, WKTConstants::LOCAL_CS)) { + return util::nn_static_pointer_cast<CRS>( + buildEngineeringCRSFromLocalCS(node)); + } + + if (ci_equal(name, WKTConstants::PARAMETRICCRS)) { + if (!isNull(nodeP->lookForChild(WKTConstants::BASEPARAMCRS))) { + return util::nn_static_pointer_cast<CRS>( + buildDerivedParametricCRS(node)); + } else { + return util::nn_static_pointer_cast<CRS>(buildParametricCRS(node)); + } + } + + return nullptr; +} + +// --------------------------------------------------------------------------- + +BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) { + const auto *nodeP = node->GP(); + const std::string &name(nodeP->value()); + + auto crs = buildCRS(node); + if (crs) { + if (!toWGS84Parameters_.empty()) { + return util::nn_static_pointer_cast<BaseObject>( + BoundCRS::createFromTOWGS84(NN_NO_CHECK(crs), + toWGS84Parameters_)); + } + if (!datumPROJ4Grids_.empty()) { + return util::nn_static_pointer_cast<BaseObject>( + BoundCRS::createFromNadgrids(NN_NO_CHECK(crs), + datumPROJ4Grids_)); + } + return util::nn_static_pointer_cast<BaseObject>(NN_NO_CHECK(crs)); + } + + if (ci_equal(name, WKTConstants::DATUM) || + ci_equal(name, WKTConstants::GEODETICDATUM) || + ci_equal(name, WKTConstants::TRF)) { + return util::nn_static_pointer_cast<BaseObject>( + buildGeodeticReferenceFrame(node, PrimeMeridian::GREENWICH, + null_node)); + } + + if (ci_equal(name, WKTConstants::ENSEMBLE)) { + return util::nn_static_pointer_cast<BaseObject>(buildDatumEnsemble( + node, PrimeMeridian::GREENWICH, + !isNull(nodeP->lookForChild(WKTConstants::ELLIPSOID)))); + } + + if (ci_equal(name, WKTConstants::VDATUM) || + ci_equal(name, WKTConstants::VERT_DATUM) || + ci_equal(name, WKTConstants::VERTICALDATUM) || + ci_equal(name, WKTConstants::VRF)) { + return util::nn_static_pointer_cast<BaseObject>( + buildVerticalReferenceFrame(node, null_node)); + } + + if (ci_equal(name, WKTConstants::TDATUM) || + ci_equal(name, WKTConstants::TIMEDATUM)) { + return util::nn_static_pointer_cast<BaseObject>( + buildTemporalDatum(node)); + } + + if (ci_equal(name, WKTConstants::EDATUM) || + ci_equal(name, WKTConstants::ENGINEERINGDATUM)) { + return util::nn_static_pointer_cast<BaseObject>( + buildEngineeringDatum(node)); + } + + if (ci_equal(name, WKTConstants::PDATUM) || + ci_equal(name, WKTConstants::PARAMETRICDATUM)) { + return util::nn_static_pointer_cast<BaseObject>( + buildParametricDatum(node)); + } + + if (ci_equal(name, WKTConstants::ELLIPSOID) || + ci_equal(name, WKTConstants::SPHEROID)) { + return util::nn_static_pointer_cast<BaseObject>(buildEllipsoid(node)); + } + + if (ci_equal(name, WKTConstants::COORDINATEOPERATION)) { + return util::nn_static_pointer_cast<BaseObject>( + buildCoordinateOperation(node)); + } + + if (ci_equal(name, WKTConstants::CONVERSION)) { + return util::nn_static_pointer_cast<BaseObject>( + buildConversion(node, UnitOfMeasure::METRE, UnitOfMeasure::DEGREE)); + } + + if (ci_equal(name, WKTConstants::CONCATENATEDOPERATION)) { + return util::nn_static_pointer_cast<BaseObject>( + buildConcatenatedOperation(node)); + } + + if (ci_equal(name, WKTConstants::ID) || + ci_equal(name, WKTConstants::AUTHORITY)) { + return util::nn_static_pointer_cast<BaseObject>( + NN_NO_CHECK(buildId(node, false))); + } + + throw ParsingException(concat("unhandled keyword: ", name)); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a sub-class of BaseObject from a user specified text. + * + * The text can be a: + * <ul> + * <li>WKT string</li> + * <li>PROJ string</li> + * <li>database code, prefixed by its authoriy. e.g. "EPSG:4326"</li> + * <li>URN. e.g. "urn:ogc:def:crs:EPSG::4326", + * "urn:ogc:def:coordinateOperation:EPSG::1671"</li> + * <li>an objet name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as + * uniqueness is not guaranteed, the function may apply heuristics to + * determine the appropriate best match.</li> + * </ul> + * + * @throw ParsingException + */ +BaseObjectNNPtr createFromUserInput(const std::string &text, + const DatabaseContextPtr &dbContext) { + for (const auto &wktConstants : WKTConstants::constants()) { + if (ci_starts_with(text, wktConstants)) { + return WKTParser().attachDatabaseContext(dbContext).createFromWKT( + text); + } + } + const char *textWithoutPlusPrefix = text.c_str(); + if (textWithoutPlusPrefix[0] == '+') + textWithoutPlusPrefix++; + + if (strncmp(textWithoutPlusPrefix, "proj=", strlen("proj=")) == 0 || + strncmp(textWithoutPlusPrefix, "init=", strlen("init=")) == 0 || + strncmp(textWithoutPlusPrefix, "title=", strlen("title=")) == 0) { + return PROJStringParser() + .attachDatabaseContext(dbContext) + .createFromPROJString(text); + } + + auto tokens = split(text, ':'); + if (tokens.size() == 2) { + if (!dbContext) { + throw ParsingException("no database context specified"); + } + auto factory = + AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[0]); + return factory->createCoordinateReferenceSystem(tokens[1]); + } + + // urn:ogc:def:crs:EPSG::4326 + if (tokens.size() == 7) { + if (!dbContext) { + throw ParsingException("no database context specified"); + } + const auto &type = tokens[3]; + auto factory = + AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[4]); + const auto &code = tokens[6]; + if (type == "crs") { + return factory->createCoordinateReferenceSystem(code); + } + if (type == "coordinateOperation") { + return factory->createCoordinateOperation(code, true); + } + if (type == "datum") { + return factory->createDatum(code); + } + if (type == "ellipsoid") { + return factory->createEllipsoid(code); + } + if (type == "meridian") { + return factory->createPrimeMeridian(code); + } + throw ParsingException(concat("unhandled object type: ", type)); + } + + if (dbContext) { + auto factory = + AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string()); + // First pass: exact match on CRS objects + // Second pass: exact match on other objects + // Third pass: approximate match on CRS objects + // Fourth pass: approximate match on other objects + constexpr size_t limitResultCount = 10; + for (int pass = 0; pass <= 3; ++pass) { + const bool approximateMatch = (pass >= 2); + auto res = factory->createObjectsFromName( + text, + (pass == 0 || pass == 2) + ? std::vector< + AuthorityFactory::ObjectType>{AuthorityFactory:: + ObjectType::CRS} + : std::vector< + AuthorityFactory:: + ObjectType>{AuthorityFactory::ObjectType:: + ELLIPSOID, + AuthorityFactory::ObjectType::DATUM, + AuthorityFactory::ObjectType:: + COORDINATE_OPERATION}, + approximateMatch, limitResultCount); + if (res.size() == 1) { + return res.front(); + } + if (res.size() > 1) { + if (pass == 0 || pass == 2) { + for (size_t ndim = 2; ndim <= 3; ndim++) { + for (const auto &obj : res) { + auto crs = + dynamic_cast<crs::GeographicCRS *>(obj.get()); + if (crs && + crs->coordinateSystem()->axisList().size() == + ndim) { + return obj; + } + } + } + } + + std::string msg("several objects matching this name: "); + bool first = true; + for (const auto &obj : res) { + if (msg.size() > 200) { + msg += ", ..."; + break; + } + if (!first) { + msg += ", "; + } + first = false; + msg += obj->nameStr(); + } + throw ParsingException(msg); + } + } + } + + throw ParsingException("unrecognized format / unknown name"); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a sub-class of BaseObject from a WKT string. + * @throw ParsingException + */ +BaseObjectNNPtr WKTParser::createFromWKT(const std::string &wkt) { + WKTNodeNNPtr root = WKTNode::createFrom(wkt); + return d->build(root); +} + +// --------------------------------------------------------------------------- + +/** \brief Attach a database context, to allow queries in it if needed. + */ +WKTParser & +WKTParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) { + d->dbContext_ = dbContext; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Guess the "dialect" of the WKT string. + */ +WKTParser::WKTGuessedDialect +WKTParser::guessDialect(const std::string &wkt) noexcept { + const std::string *const wkt1_keywords[] = { + &WKTConstants::GEOCCS, &WKTConstants::GEOGCS, &WKTConstants::COMPD_CS, + &WKTConstants::PROJCS, &WKTConstants::VERT_CS, &WKTConstants::LOCAL_CS}; + for (const auto &pointerKeyword : wkt1_keywords) { + if (ci_starts_with(wkt, *pointerKeyword)) { + + if (ci_find(wkt, "GEOGCS[\"GCS_") != std::string::npos) { + return WKTGuessedDialect::WKT1_ESRI; + } + + return WKTGuessedDialect::WKT1_GDAL; + } + } + + const std::string *const wkt2_2018_only_keywords[] = { + &WKTConstants::GEOGCRS, + // contained in previous one + // &WKTConstants::BASEGEOGCRS, + &WKTConstants::CONCATENATEDOPERATION, &WKTConstants::USAGE, + &WKTConstants::DYNAMIC, &WKTConstants::FRAMEEPOCH, &WKTConstants::MODEL, + &WKTConstants::VELOCITYGRID, &WKTConstants::ENSEMBLE, + &WKTConstants::DERIVEDPROJCRS, &WKTConstants::BASEPROJCRS, + &WKTConstants::GEOGRAPHICCRS, &WKTConstants::TRF, &WKTConstants::VRF}; + + for (const auto &pointerKeyword : wkt2_2018_only_keywords) { + auto pos = ci_find(wkt, *pointerKeyword); + if (pos != std::string::npos && + wkt[pos + pointerKeyword->size()] == '[') { + return WKTGuessedDialect::WKT2_2018; + } + } + static const char *const wkt2_2018_only_substrings[] = { + "CS[TemporalDateTime,", "CS[TemporalCount,", "CS[TemporalMeasure,", + }; + for (const auto &substrings : wkt2_2018_only_substrings) { + if (ci_find(wkt, substrings) != std::string::npos) { + return WKTGuessedDialect::WKT2_2018; + } + } + + for (const auto &wktConstants : WKTConstants::constants()) { + if (ci_starts_with(wkt, wktConstants)) { + return WKTGuessedDialect::WKT2_2015; + } + } + + return WKTGuessedDialect::NOT_WKT; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +FormattingException::FormattingException(const char *message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +FormattingException::FormattingException(const std::string &message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +FormattingException::FormattingException(const FormattingException &) = default; + +// --------------------------------------------------------------------------- + +FormattingException::~FormattingException() = default; + +// --------------------------------------------------------------------------- + +void FormattingException::Throw(const char *msg) { + throw FormattingException(msg); +} + +// --------------------------------------------------------------------------- + +void FormattingException::Throw(const std::string &msg) { + throw FormattingException(msg); +} + +// --------------------------------------------------------------------------- + +ParsingException::ParsingException(const char *message) : Exception(message) {} + +// --------------------------------------------------------------------------- + +ParsingException::ParsingException(const std::string &message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +ParsingException::ParsingException(const ParsingException &) = default; + +// --------------------------------------------------------------------------- + +ParsingException::~ParsingException() = default; + +// --------------------------------------------------------------------------- + +IPROJStringExportable::~IPROJStringExportable() = default; + +// --------------------------------------------------------------------------- + +std::string IPROJStringExportable::exportToPROJString( + PROJStringFormatter *formatter) const { + _exportToPROJString(formatter); + return formatter->toString(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +struct Step { + std::string name{}; + bool isInit = false; + bool inverted{false}; + + struct KeyValue { + std::string key{}; + std::string value{}; + + explicit KeyValue(const std::string &keyIn) : key(keyIn) {} + + KeyValue(const char *keyIn, const std::string &valueIn); + + KeyValue(const std::string &keyIn, const std::string &valueIn) + : key(keyIn), value(valueIn) {} + + // cppcheck-suppress functionStatic + bool keyEquals(const char *otherKey) const noexcept { + return key == otherKey; + } + + // cppcheck-suppress functionStatic + bool equals(const char *otherKey, const char *otherVal) const noexcept { + return key == otherKey && value == otherVal; + } + + bool operator!=(const KeyValue &other) const noexcept { + return key != other.key || value != other.value; + } + }; + + std::vector<KeyValue> paramValues{}; +}; + +Step::KeyValue::KeyValue(const char *keyIn, const std::string &valueIn) + : key(keyIn), value(valueIn) {} + +struct PROJStringFormatter::Private { + PROJStringFormatter::Convention convention_ = + PROJStringFormatter::Convention::PROJ_5; + std::vector<double> toWGS84Parameters_{}; + std::string vDatumExtension_{}; + std::string hDatumExtension_{}; + + std::list<Step> steps_{}; + std::vector<Step::KeyValue> globalParamValues_{}; + + struct InversionStackElt { + std::list<Step>::iterator startIter{}; + bool iterValid = false; + bool currentInversionState = false; + }; + std::vector<InversionStackElt> inversionStack_{InversionStackElt()}; + bool omitProjLongLatIfPossible_ = false; + bool omitZUnitConversion_ = false; + DatabaseContextPtr dbContext_{}; + bool useETMercForTMerc_ = false; + + std::string result_{}; + + // cppcheck-suppress functionStatic + void appendToResult(const char *str); + + // cppcheck-suppress functionStatic + void addStep(); +}; + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PROJStringFormatter::PROJStringFormatter(Convention conventionIn, + const DatabaseContextPtr &dbContext) + : d(internal::make_unique<Private>()) { + d->convention_ = conventionIn; + d->dbContext_ = dbContext; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PROJStringFormatter::~PROJStringFormatter() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Constructs a new formatter. + * + * A formatter can be used only once (its internal state is mutated) + * + * Its default behaviour can be adjusted with the different setters. + * + * @param conventionIn PROJ string flavor. Defaults to Convention::PROJ_5 + * @param dbContext Database context (can help to find alternative grid names). + * May be nullptr + * @return new formatter. + */ +PROJStringFormatterNNPtr +PROJStringFormatter::create(Convention conventionIn, + DatabaseContextPtr dbContext) { + return NN_NO_CHECK(PROJStringFormatter::make_unique<PROJStringFormatter>( + conventionIn, dbContext)); +} + +// --------------------------------------------------------------------------- + +/** \brief Set whether Extented Transverse Mercator (etmerc) should be used + * instead of tmerc */ +void PROJStringFormatter::setUseETMercForTMerc(bool flag) { + d->useETMercForTMerc_ = flag; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the PROJ string. */ +const std::string &PROJStringFormatter::toString() const { + + assert(d->inversionStack_.size() == 1); + + d->result_.clear(); + + for (auto iter = d->steps_.begin(); iter != d->steps_.end();) { + // Remove no-op helmert + auto &step = *iter; + const auto paramCount = step.paramValues.size(); + if (step.name == "helmert" && (paramCount == 3 || paramCount == 8) && + step.paramValues[0].equals("x", "0") && + step.paramValues[1].equals("y", "0") && + step.paramValues[2].equals("z", "0") && + (paramCount == 3 || + (step.paramValues[3].equals("rx", "0") && + step.paramValues[4].equals("ry", "0") && + step.paramValues[5].equals("rz", "0") && + step.paramValues[6].equals("s", "0") && + step.paramValues[7].keyEquals("convention")))) { + iter = d->steps_.erase(iter); + } else { + ++iter; + } + } + + for (auto &step : d->steps_) { + if (!step.inverted) { + continue; + } + + const auto paramCount = step.paramValues.size(); + + // axisswap order=2,1 is its own inverse + if (step.name == "axisswap" && paramCount == 1 && + step.paramValues[0].equals("order", "2,1")) { + step.inverted = false; + continue; + } + + // handle unitconvert inverse + if (step.name == "unitconvert" && paramCount == 2 && + step.paramValues[0].keyEquals("xy_in") && + step.paramValues[1].keyEquals("xy_out")) { + std::swap(step.paramValues[0].value, step.paramValues[1].value); + step.inverted = false; + continue; + } + + if (step.name == "unitconvert" && paramCount == 2 && + step.paramValues[0].keyEquals("z_in") && + step.paramValues[1].keyEquals("z_out")) { + std::swap(step.paramValues[0].value, step.paramValues[1].value); + step.inverted = false; + continue; + } + + if (step.name == "unitconvert" && paramCount == 4 && + step.paramValues[0].keyEquals("xy_in") && + step.paramValues[1].keyEquals("z_in") && + step.paramValues[2].keyEquals("xy_out") && + step.paramValues[3].keyEquals("z_out")) { + std::swap(step.paramValues[0].value, step.paramValues[2].value); + std::swap(step.paramValues[1].value, step.paramValues[3].value); + step.inverted = false; + continue; + } + } + + bool changeDone; + do { + changeDone = false; + auto iterPrev = d->steps_.begin(); + if (iterPrev == d->steps_.end()) { + break; + } + auto iterCur = iterPrev; + iterCur++; + for (size_t i = 1; i < d->steps_.size(); ++i, ++iterCur, ++iterPrev) { + + auto &prevStep = *iterPrev; + auto &curStep = *iterCur; + + const auto curStepParamCount = curStep.paramValues.size(); + const auto prevStepParamCount = prevStep.paramValues.size(); + + // longlat (or its inverse) with ellipsoid only is a no-op + // do that only for an internal step + if (i + 1 < d->steps_.size() && curStep.name == "longlat" && + curStepParamCount == 1 && + curStep.paramValues[0].keyEquals("ellps")) { + d->steps_.erase(iterCur); + changeDone = true; + break; + } + + // unitconvert (xy) followed by its inverse is a no-op + if (curStep.name == "unitconvert" && + prevStep.name == "unitconvert" && !curStep.inverted && + !prevStep.inverted && curStepParamCount == 2 && + prevStepParamCount == 2 && + curStep.paramValues[0].keyEquals("xy_in") && + prevStep.paramValues[0].keyEquals("xy_in") && + curStep.paramValues[1].keyEquals("xy_out") && + prevStep.paramValues[1].keyEquals("xy_out") && + curStep.paramValues[0].value == prevStep.paramValues[1].value && + curStep.paramValues[1].value == prevStep.paramValues[0].value) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + changeDone = true; + break; + } + + // unitconvert (z) followed by its inverse is a no-op + if (curStep.name == "unitconvert" && + prevStep.name == "unitconvert" && !curStep.inverted && + !prevStep.inverted && curStepParamCount == 2 && + prevStepParamCount == 2 && + curStep.paramValues[0].keyEquals("z_in") && + prevStep.paramValues[0].keyEquals("z_in") && + curStep.paramValues[1].keyEquals("z_out") && + prevStep.paramValues[1].keyEquals("z_out") && + curStep.paramValues[0].value == prevStep.paramValues[1].value && + curStep.paramValues[1].value == prevStep.paramValues[0].value) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + changeDone = true; + break; + } + + // unitconvert (xyz) followed by its inverse is a no-op + if (curStep.name == "unitconvert" && + prevStep.name == "unitconvert" && !curStep.inverted && + !prevStep.inverted && curStepParamCount == 4 && + prevStepParamCount == 4 && + curStep.paramValues[0].keyEquals("xy_in") && + prevStep.paramValues[0].keyEquals("xy_in") && + curStep.paramValues[1].keyEquals("z_in") && + prevStep.paramValues[1].keyEquals("z_in") && + curStep.paramValues[2].keyEquals("xy_out") && + prevStep.paramValues[2].keyEquals("xy_out") && + curStep.paramValues[3].keyEquals("z_out") && + prevStep.paramValues[3].keyEquals("z_out") && + curStep.paramValues[0].value == prevStep.paramValues[2].value && + curStep.paramValues[1].value == prevStep.paramValues[3].value && + curStep.paramValues[2].value == prevStep.paramValues[0].value && + curStep.paramValues[3].value == prevStep.paramValues[1].value) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + changeDone = true; + break; + } + + // combine unitconvert (xy) and unitconvert (z) + for (int k = 0; k < 2; ++k) { + auto &first = (k == 0) ? curStep : prevStep; + auto &second = (k == 0) ? prevStep : curStep; + if (first.name == "unitconvert" && + second.name == "unitconvert" && !first.inverted && + !second.inverted && first.paramValues.size() == 2 && + second.paramValues.size() == 2 && + second.paramValues[0].keyEquals("xy_in") && + second.paramValues[1].keyEquals("xy_out") && + first.paramValues[0].keyEquals("z_in") && + first.paramValues[1].keyEquals("z_out")) { + + auto xy_in = second.paramValues[0].value; + auto xy_out = second.paramValues[1].value; + auto z_in = first.paramValues[0].value; + auto z_out = first.paramValues[1].value; + d->steps_.erase(iterPrev, iterCur); + iterCur->paramValues.clear(); + iterCur->paramValues.emplace_back( + Step::KeyValue("xy_in", xy_in)); + iterCur->paramValues.emplace_back( + Step::KeyValue("z_in", z_in)); + iterCur->paramValues.emplace_back( + Step::KeyValue("xy_out", xy_out)); + iterCur->paramValues.emplace_back( + Step::KeyValue("z_out", z_out)); + changeDone = true; + break; + } + } + if (changeDone) { + break; + } + + // +step +proj=unitconvert +xy_in=X1 +xy_out=X2 + // +step +proj=unitconvert +xy_in=X2 +z_in=Z1 +xy_out=X1 +z_out=Z2 + // ==> step +proj=unitconvert +z_in=Z1 +z_out=Z2 + for (int k = 0; k < 2; ++k) { + auto &first = (k == 0) ? curStep : prevStep; + auto &second = (k == 0) ? prevStep : curStep; + if (first.name == "unitconvert" && + second.name == "unitconvert" && !first.inverted && + !second.inverted && first.paramValues.size() == 4 && + second.paramValues.size() == 2 && + first.paramValues[0].keyEquals("xy_in") && + first.paramValues[1].keyEquals("z_in") && + first.paramValues[2].keyEquals("xy_out") && + first.paramValues[3].keyEquals("z_out") && + second.paramValues[0].keyEquals("xy_in=") && + second.paramValues[1].keyEquals("xy_out") && + first.paramValues[0].value == second.paramValues[1].value && + first.paramValues[2].value == second.paramValues[0].value) { + auto z_in = first.paramValues[1].value; + auto z_out = first.paramValues[3].value; + if (z_in != z_out) { + d->steps_.erase(iterPrev, iterCur); + iterCur->paramValues.clear(); + iterCur->paramValues.emplace_back( + Step::KeyValue("z_in", z_in)); + iterCur->paramValues.emplace_back( + Step::KeyValue("z_out", z_out)); + } else { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + } + changeDone = true; + break; + } + } + if (changeDone) { + break; + } + + // axisswap order=2,1 followed by itself is a no-op + if (curStep.name == "axisswap" && prevStep.name == "axisswap" && + curStepParamCount == 1 && prevStepParamCount == 1 && + curStep.paramValues[0].equals("order", "2,1") && + prevStep.paramValues[0].equals("order", "2,1")) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + changeDone = true; + break; + } + + // for practical purposes WGS84 and GRS80 ellipsoids are + // equivalents (cartesian transform between both lead to differences + // of the order of 1e-14 deg..). + // No need to do a cart roundtrip for that... + // and actually IGNF uses the GRS80 definition for the WGS84 datum + if (curStep.name == "cart" && prevStep.name == "cart" && + curStep.inverted == !prevStep.inverted && + curStepParamCount == 1 && prevStepParamCount == 1 && + ((curStep.paramValues[0].equals("ellps", "WGS84") && + prevStep.paramValues[0].equals("ellps", "GRS80")) || + (curStep.paramValues[0].equals("ellps", "GRS80") && + prevStep.paramValues[0].equals("ellps", "WGS84")))) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + changeDone = true; + break; + } + + if (curStep.name == "helmert" && prevStep.name == "helmert" && + !curStep.inverted && !prevStep.inverted && + curStepParamCount == 3 && + curStepParamCount == prevStepParamCount) { + std::map<std::string, double> leftParamsMap; + std::map<std::string, double> rightParamsMap; + try { + for (const auto &kv : prevStep.paramValues) { + leftParamsMap[kv.key] = c_locale_stod(kv.value); + } + for (const auto &kv : curStep.paramValues) { + rightParamsMap[kv.key] = c_locale_stod(kv.value); + } + } catch (const std::invalid_argument &) { + break; + } + const std::string x("x"); + const std::string y("y"); + const std::string z("z"); + if (leftParamsMap.find(x) != leftParamsMap.end() && + leftParamsMap.find(y) != leftParamsMap.end() && + leftParamsMap.find(z) != leftParamsMap.end() && + rightParamsMap.find(x) != rightParamsMap.end() && + rightParamsMap.find(y) != rightParamsMap.end() && + rightParamsMap.find(z) != rightParamsMap.end()) { + + const double xSum = leftParamsMap[x] + rightParamsMap[x]; + const double ySum = leftParamsMap[y] + rightParamsMap[y]; + const double zSum = leftParamsMap[z] + rightParamsMap[z]; + if (xSum == 0.0 && ySum == 0.0 && zSum == 0.0) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + } else { + prevStep.paramValues[0] = + Step::KeyValue("x", internal::toString(xSum)); + prevStep.paramValues[1] = + Step::KeyValue("y", internal::toString(ySum)); + prevStep.paramValues[2] = + Step::KeyValue("z", internal::toString(zSum)); + + d->steps_.erase(iterCur); + } + changeDone = true; + break; + } + } + + // hermert followed by its inverse is a no-op + if (curStep.name == "helmert" && prevStep.name == "helmert" && + !curStep.inverted && !prevStep.inverted && + curStepParamCount == prevStepParamCount) { + std::set<std::string> leftParamsSet; + std::set<std::string> rightParamsSet; + std::map<std::string, std::string> leftParamsMap; + std::map<std::string, std::string> rightParamsMap; + for (const auto &kv : prevStep.paramValues) { + leftParamsSet.insert(kv.key); + leftParamsMap[kv.key] = kv.value; + } + for (const auto &kv : curStep.paramValues) { + rightParamsSet.insert(kv.key); + rightParamsMap[kv.key] = kv.value; + } + if (leftParamsSet == rightParamsSet) { + bool doErase = true; + try { + for (const auto ¶m : leftParamsSet) { + if (param == "convention" || param == "t_epoch" || + param == "t_obs") { + if (leftParamsMap[param] != + rightParamsMap[param]) { + doErase = false; + break; + } + } else if (c_locale_stod(leftParamsMap[param]) != + -c_locale_stod(rightParamsMap[param])) { + doErase = false; + break; + } + } + } catch (const std::invalid_argument &) { + break; + } + if (doErase) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + changeDone = true; + break; + } + } + } + + // detect a step and its inverse + if (curStep.inverted != prevStep.inverted && + curStep.name == prevStep.name && + curStepParamCount == prevStepParamCount) { + bool allSame = true; + for (size_t j = 0; j < curStepParamCount; j++) { + if (curStep.paramValues[j] != prevStep.paramValues[j]) { + allSame = false; + break; + } + } + if (allSame) { + ++iterCur; + d->steps_.erase(iterPrev, iterCur); + changeDone = true; + break; + } + } + } + } while (changeDone); + + if (d->steps_.size() > 1 || + (d->steps_.size() == 1 && + (d->steps_.front().inverted || !d->globalParamValues_.empty()))) { + d->appendToResult("+proj=pipeline"); + + for (const auto ¶mValue : d->globalParamValues_) { + d->appendToResult("+"); + d->result_ += paramValue.key; + if (!paramValue.value.empty()) { + d->result_ += "="; + d->result_ += paramValue.value; + } + } + } + + for (const auto &step : d->steps_) { + if (!d->result_.empty()) { + d->appendToResult("+step"); + } + if (step.inverted) { + d->appendToResult("+inv"); + } + if (!step.name.empty()) { + d->appendToResult(step.isInit ? "+init=" : "+proj="); + d->result_ += step.name; + } + for (const auto ¶mValue : step.paramValues) { + d->appendToResult("+"); + d->result_ += paramValue.key; + if (!paramValue.value.empty()) { + d->result_ += "="; + d->result_ += paramValue.value; + } + } + } + return d->result_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +PROJStringFormatter::Convention PROJStringFormatter::convention() const { + return d->convention_; +} + +// --------------------------------------------------------------------------- + +bool PROJStringFormatter::getUseETMercForTMerc() const { + return d->useETMercForTMerc_; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::Private::appendToResult(const char *str) { + if (!result_.empty()) { + result_ += " "; + } + result_ += str; +} + +// --------------------------------------------------------------------------- + +static void +PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps, + std::vector<Step::KeyValue> &globalParamValues, + std::string &title, std::string &vunits, + std::string &vto_meter) { + std::string word; + std::istringstream iss(projString, std::istringstream::in); + bool inverted = false; + bool prevWasStep = false; + bool inProj = false; + bool inPipeline = false; + bool prevWasTitle = false; + bool prevWasInit = false; + + while (iss >> word) { + if (word[0] == '+') { + word = word.substr(1); + } else if (prevWasTitle && word.find('=') == std::string::npos) { + title += " "; + title += word; + continue; + } + + prevWasTitle = false; + if (word == "proj=pipeline") { + if (inPipeline) { + throw ParsingException("nested pipeline not supported"); + } + inverted = false; + prevWasStep = false; + inProj = true; + inPipeline = true; + } else if (word == "step") { + if (!inPipeline) { + throw ParsingException("+step found outside pipeline"); + } + inverted = false; + prevWasStep = true; + prevWasInit = false; + } else if (word == "inv") { + if (prevWasStep) { + inverted = true; + } else { + if (steps.empty()) { + throw ParsingException("+inv found at unexpected place"); + } + steps.back().inverted = true; + } + prevWasStep = false; + } else if (starts_with(word, "proj=")) { + if (prevWasInit) { + throw ParsingException("+init= found at unexpected place"); + } + auto stepName = word.substr(strlen("proj=")); + steps.push_back(Step()); + steps.back().name = stepName; + steps.back().inverted = inverted; + prevWasStep = false; + inProj = true; + } else if (starts_with(word, "init=")) { + if (prevWasInit) { + throw ParsingException("+init= found at unexpected place"); + } + auto initName = word.substr(strlen("init=")); + steps.push_back(Step()); + steps.back().name = initName; + steps.back().isInit = true; + steps.back().inverted = inverted; + prevWasStep = false; + prevWasInit = true; + inProj = true; + } else if (inProj) { + const auto pos = word.find('='); + auto key = word.substr(0, pos); + auto pair = (pos != std::string::npos) + ? Step::KeyValue(key, word.substr(pos + 1)) + : Step::KeyValue(key); + if (steps.empty()) { + globalParamValues.push_back(pair); + } else { + steps.back().paramValues.push_back(pair); + } + prevWasStep = false; + } else if (starts_with(word, "vunits=")) { + vunits = word.substr(strlen("vunits=")); + } else if (starts_with(word, "vto_meter=")) { + vto_meter = word.substr(strlen("vto_meter=")); + } else if (starts_with(word, "title=")) { + title = word.substr(strlen("title=")); + prevWasTitle = true; + } else { + throw ParsingException("Unexpected token: " + word); + } + } +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::ingestPROJString( + const std::string &str) // throw ParsingException +{ + std::vector<Step> steps; + std::string title; + std::string vunits; + std::string vto_meter; + PROJStringSyntaxParser(str, steps, d->globalParamValues_, title, vunits, + vto_meter); + d->steps_.insert(d->steps_.begin(), steps.begin(), steps.end()); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::startInversion() { + PROJStringFormatter::Private::InversionStackElt elt; + elt.startIter = d->steps_.end(); + if (elt.startIter != d->steps_.begin()) { + elt.iterValid = true; + --elt.startIter; // point to the last valid element + } else { + elt.iterValid = false; + } + elt.currentInversionState = + !d->inversionStack_.back().currentInversionState; + d->inversionStack_.push_back(elt); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::stopInversion() { + assert(!d->inversionStack_.empty()); + auto startIter = d->inversionStack_.back().startIter; + if (!d->inversionStack_.back().iterValid) { + startIter = d->steps_.begin(); + } else { + ++startIter; // advance after the last valid element we marked above + } + // Invert the inversion status of the steps between the start point and + // the current end of steps + for (auto iter = startIter; iter != d->steps_.end(); ++iter) { + iter->inverted = !iter->inverted; + } + // And reverse the order of steps in that range as well. + std::reverse(startIter, d->steps_.end()); + d->inversionStack_.pop_back(); +} + +// --------------------------------------------------------------------------- + +bool PROJStringFormatter::isInverted() const { + return d->inversionStack_.back().currentInversionState; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::Private::addStep() { steps_.emplace_back(Step()); } + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addStep(const char *stepName) { + d->addStep(); + d->steps_.back().name.assign(stepName); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addStep(const std::string &stepName) { + d->addStep(); + d->steps_.back().name = stepName; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::setCurrentStepInverted(bool inverted) { + assert(!d->steps_.empty()); + d->steps_.back().inverted = inverted; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addParam(const std::string ¶mName) { + if (d->steps_.empty()) { + d->addStep(); + } + d->steps_.back().paramValues.push_back(Step::KeyValue(paramName)); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addParam(const char *paramName, int val) { + addParam(std::string(paramName), val); +} + +void PROJStringFormatter::addParam(const std::string ¶mName, int val) { + addParam(paramName, internal::toString(val)); +} + +// --------------------------------------------------------------------------- + +static std::string formatToString(double val) { + if (std::abs(val * 10 - std::round(val * 10)) < 1e-8) { + // For the purpose of + // https://www.epsg-registry.org/export.htm?wkt=urn:ogc:def:crs:EPSG::27561 + // Latitude of natural of origin to be properly rounded from 55 grad + // to + // 49.5 deg + val = std::round(val * 10) / 10; + } + return normalizeSerializedString(internal::toString(val)); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addParam(const char *paramName, double val) { + addParam(std::string(paramName), val); +} + +void PROJStringFormatter::addParam(const std::string ¶mName, double val) { + addParam(paramName, formatToString(val)); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addParam(const char *paramName, + const std::vector<double> &vals) { + std::string paramValue; + for (size_t i = 0; i < vals.size(); ++i) { + if (i > 0) { + paramValue += ","; + } + paramValue += formatToString(vals[i]); + } + addParam(paramName, paramValue); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addParam(const char *paramName, const char *val) { + addParam(std::string(paramName), val); +} + +void PROJStringFormatter::addParam(const char *paramName, + const std::string &val) { + addParam(std::string(paramName), val); +} + +void PROJStringFormatter::addParam(const std::string ¶mName, + const char *val) { + addParam(paramName, std::string(val)); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::addParam(const std::string ¶mName, + const std::string &val) { + if (d->steps_.empty()) { + d->addStep(); + } + d->steps_.back().paramValues.push_back(Step::KeyValue(paramName, val)); +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::setTOWGS84Parameters( + const std::vector<double> ¶ms) { + d->toWGS84Parameters_ = params; +} + +// --------------------------------------------------------------------------- + +const std::vector<double> &PROJStringFormatter::getTOWGS84Parameters() const { + return d->toWGS84Parameters_; +} + +// --------------------------------------------------------------------------- + +std::set<std::string> PROJStringFormatter::getUsedGridNames() const { + std::set<std::string> res; + for (const auto &step : d->steps_) { + for (const auto ¶m : step.paramValues) { + if (param.keyEquals("grids")) { + res.insert(param.value); + } + } + } + return res; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::setVDatumExtension(const std::string &filename) { + d->vDatumExtension_ = filename; +} + +// --------------------------------------------------------------------------- + +const std::string &PROJStringFormatter::getVDatumExtension() const { + return d->vDatumExtension_; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::setHDatumExtension(const std::string &filename) { + d->hDatumExtension_ = filename; +} + +// --------------------------------------------------------------------------- + +const std::string &PROJStringFormatter::getHDatumExtension() const { + return d->hDatumExtension_; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::setOmitProjLongLatIfPossible(bool omit) { + assert(d->omitProjLongLatIfPossible_ ^ omit); + d->omitProjLongLatIfPossible_ = omit; +} + +// --------------------------------------------------------------------------- + +bool PROJStringFormatter::omitProjLongLatIfPossible() const { + return d->omitProjLongLatIfPossible_; +} + +// --------------------------------------------------------------------------- + +void PROJStringFormatter::setOmitZUnitConversion(bool omit) { + assert(d->omitZUnitConversion_ ^ omit); + d->omitZUnitConversion_ = omit; +} + +// --------------------------------------------------------------------------- + +bool PROJStringFormatter::omitZUnitConversion() const { + return d->omitZUnitConversion_; +} + +// --------------------------------------------------------------------------- + +const DatabaseContextPtr &PROJStringFormatter::databaseContext() const { + return d->dbContext_; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +struct PROJStringParser::Private { + DatabaseContextPtr dbContext_{}; + std::vector<std::string> warningList_{}; + + std::vector<Step> steps_{}; + std::vector<Step::KeyValue> globalParamValues_{}; + std::string title_{}; + + template <class T> + // cppcheck-suppress functionStatic + bool hasParamValue(const Step &step, const T key) { + for (const auto &pair : globalParamValues_) { + if (ci_equal(pair.key, key)) { + return true; + } + } + for (const auto &pair : step.paramValues) { + if (ci_equal(pair.key, key)) { + return true; + } + } + return false; + } + + template <class T> + // cppcheck-suppress functionStatic + const std::string &getParamValue(const Step &step, const T key) { + for (const auto &pair : globalParamValues_) { + if (ci_equal(pair.key, key)) { + return pair.value; + } + } + for (const auto &pair : step.paramValues) { + if (ci_equal(pair.key, key)) { + return pair.value; + } + } + return emptyString; + } + + // cppcheck-suppress functionStatic + std::string guessBodyName(double a); + + PrimeMeridianNNPtr buildPrimeMeridian(const Step &step); + GeodeticReferenceFrameNNPtr buildDatum(const Step &step, + const std::string &title); + GeographicCRSNNPtr buildGeographicCRS(int iStep, int iUnitConvert, + int iAxisSwap, bool ignoreVUnits, + bool ignorePROJAxis); + GeodeticCRSNNPtr buildGeocentricCRS(int iStep, int iUnitConvert); + CRSNNPtr buildProjectedCRS(int iStep, GeographicCRSNNPtr geogCRS, + int iUnitConvert, int iAxisSwap); + CRSNNPtr buildBoundOrCompoundCRSIfNeeded(int iStep, CRSNNPtr crs); + UnitOfMeasure buildUnit(const Step &step, const std::string &unitsParamName, + const std::string &toMeterParamName); + CoordinateOperationNNPtr buildHelmertTransformation( + int iStep, int iFirstAxisSwap = -1, int iFirstUnitConvert = -1, + int iFirstGeogStep = -1, int iSecondGeogStep = -1, + int iSecondAxisSwap = -1, int iSecondUnitConvert = -1); + CoordinateOperationNNPtr buildMolodenskyTransformation( + int iStep, int iFirstAxisSwap = -1, int iFirstUnitConvert = -1, + int iFirstGeogStep = -1, int iSecondGeogStep = -1, + int iSecondAxisSwap = -1, int iSecondUnitConvert = -1); + + enum class AxisType { REGULAR, NORTH_POLE, SOUTH_POLE }; + + std::vector<CoordinateSystemAxisNNPtr> + processAxisSwap(const Step &step, const UnitOfMeasure &unit, int iAxisSwap, + AxisType axisType, bool ignorePROJAxis); + + EllipsoidalCSNNPtr buildEllipsoidalCS(int iStep, int iUnitConvert, + int iAxisSwap, bool ignoreVUnits, + bool ignorePROJAxis); +}; + +// --------------------------------------------------------------------------- + +PROJStringParser::PROJStringParser() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PROJStringParser::~PROJStringParser() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Attach a database context, to allow queries in it if needed. + */ +PROJStringParser & +PROJStringParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) { + d->dbContext_ = dbContext; + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the list of warnings found during parsing. + */ +std::vector<std::string> PROJStringParser::warningList() const { + return d->warningList_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +// --------------------------------------------------------------------------- + +static const struct LinearUnitDesc { + const char *projName; + const char *convToMeter; + const char *name; + int epsgCode; +} linearUnitDescs[] = { + {"mm", "0.001", "millimetre", 1025}, + {"cm", "0.01", "centimetre", 1033}, + {"m", "1.0", "metre", 9001}, + {"meter", "1.0", "metre", 9001}, // alternative + {"metre", "1.0", "metre", 9001}, // alternative + {"ft", "0.3048", "foot", 9002}, + {"us-ft", "0.3048006096012192", "US survey foot", 9003}, + {"fath", "1.8288", "fathom", 9014}, + {"kmi", "1852", "nautical mile", 9030}, + {"us-ch", "20.11684023368047", "US survey chain", 9033}, + {"us-mi", "1609.347218694437", "US survey mile", 9035}, + {"km", "1000.0", "kilometre", 9036}, + {"ind-ft", "0.30479841", "Indian foot (1937)", 9081}, + {"ind-yd", "0.91439523", "Indian yard (1937)", 9085}, + {"mi", "1609.344", "Statute mile", 9093}, + {"yd", "0.9144", "yard", 9096}, + {"ch", "20.1168", "chain", 9097}, + {"link", "0.201168", "link", 9098}, + {"dm", "0.1", "decimetre", 0}, // no EPSG equivalent + {"in", "0.0254", "inch", 0}, // no EPSG equivalent + {"us-in", "0.025400050800101", "US survey inch", 0}, // no EPSG equivalent + {"us-yd", "0.914401828803658", "US survey yard", 0}, // no EPSG equivalent + {"ind-ch", "20.11669506", "Indian chain", 0}, // no EPSG equivalent +}; + +static const LinearUnitDesc *getLinearUnits(const std::string &projName) { + for (const auto &desc : linearUnitDescs) { + if (desc.projName == projName) + return &desc; + } + return nullptr; +} + +static const LinearUnitDesc *getLinearUnits(double toMeter) { + for (const auto &desc : linearUnitDescs) { + if (std::fabs(c_locale_stod(desc.convToMeter) - toMeter) < + 1e-10 * toMeter) { + return &desc; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +static UnitOfMeasure _buildUnit(const LinearUnitDesc *unitsMatch) { + std::string unitsCode; + if (unitsMatch->epsgCode) { + std::ostringstream buffer; + buffer.imbue(std::locale::classic()); + buffer << unitsMatch->epsgCode; + unitsCode = buffer.str(); + } + return UnitOfMeasure( + unitsMatch->name, c_locale_stod(unitsMatch->convToMeter), + UnitOfMeasure::Type::LINEAR, + unitsMatch->epsgCode ? Identifier::EPSG : std::string(), unitsCode); +} + +// --------------------------------------------------------------------------- + +static UnitOfMeasure _buildUnit(double to_meter_value) { + // TODO: look-up in EPSG catalog + return UnitOfMeasure("unknown", to_meter_value, + UnitOfMeasure::Type::LINEAR); +} + +// --------------------------------------------------------------------------- + +UnitOfMeasure +PROJStringParser::Private::buildUnit(const Step &step, + const std::string &unitsParamName, + const std::string &toMeterParamName) { + UnitOfMeasure unit = UnitOfMeasure::METRE; + const LinearUnitDesc *unitsMatch = nullptr; + const auto &projUnits = getParamValue(step, unitsParamName); + if (!projUnits.empty()) { + unitsMatch = getLinearUnits(projUnits); + if (unitsMatch == nullptr) { + throw ParsingException("unhandled " + unitsParamName + "=" + + projUnits); + } + } + + const auto &toMeter = getParamValue(step, toMeterParamName); + if (!toMeter.empty()) { + double to_meter_value; + try { + to_meter_value = c_locale_stod(toMeter); + } catch (const std::invalid_argument &) { + throw ParsingException("invalid value for " + toMeterParamName); + } + unitsMatch = getLinearUnits(to_meter_value); + if (unitsMatch == nullptr) { + unit = _buildUnit(to_meter_value); + } + } + + if (unitsMatch) { + unit = _buildUnit(unitsMatch); + } + + return unit; +} + +// --------------------------------------------------------------------------- + +static const struct DatumDesc { + const char *projName; + const char *gcsName; + int gcsCode; + const char *datumName; + int datumCode; + const char *ellipsoidName; + int ellipsoidCode; + double a; + double rf; +} datumDescs[] = { + {"GGRS87", "GGRS87", 4121, "Greek Geodetic Reference System 1987", 6121, + "GRS 1980", 7019, 6378137, 298.257222101}, + {"postdam", "DHDN", 4314, "Deutsches Hauptdreiecksnetz", 6314, + "Bessel 1841", 7004, 6377397.155, 299.1528128}, + {"carthage", "Carthage", 4223, "Carthage", 6223, "Clarke 1880 (IGN)", 7011, + 6378249.2, 293.4660213}, + {"hermannskogel", "MGI", 4312, "Militar-Geographische Institut", 6312, + "Bessel 1841", 7004, 6377397.155, 299.1528128}, + {"ire65", "TM65", 4299, "TM65", 6299, "Airy Modified 1849", 7002, + 6377340.189, 299.3249646}, + {"nzgd49", "NZGD49", 4272, "New Zealand Geodetic Datum 1949", 6272, + "International 1924", 7022, 6378388, 297}, + {"OSGB36", "OSGB 1936", 4277, "OSGB 1936", 6277, "Airy 1830", 7001, + 6377563.396, 299.3249646}, +}; + +// --------------------------------------------------------------------------- + +static bool isGeodeticStep(const std::string &name) { + return name == "longlat" || name == "lonlat" || name == "latlong" || + name == "latlon"; +} + +// --------------------------------------------------------------------------- + +static bool isGeocentricStep(const std::string &name) { + return name == "geocent" || name == "cart"; +} + +// --------------------------------------------------------------------------- + +static bool isProjectedStep(const std::string &name) { + if (name == "etmerc" || name == "utm" || + !getMappingsFromPROJName(name).empty()) { + return true; + } + // IMPROVE ME: have a better way of distinguishing projections from + // other + // transformations. + if (name == "pipeline" || name == "geoc" || name == "deformation" || + name == "helmert" || name == "hgridshift" || name == "molodensky" || + name == "vgridshit") { + return false; + } + const auto *operations = proj_list_operations(); + for (int i = 0; operations[i].id != nullptr; ++i) { + if (name == operations[i].id) { + return true; + } + } + return false; +} + +// --------------------------------------------------------------------------- + +static PropertyMap createMapWithUnknownName() { + return PropertyMap().set(common::IdentifiedObject::NAME_KEY, "unknown"); +} + +// --------------------------------------------------------------------------- + +PrimeMeridianNNPtr +PROJStringParser::Private::buildPrimeMeridian(const Step &step) { + + PrimeMeridianNNPtr pm = PrimeMeridian::GREENWICH; + const auto &pmStr = getParamValue(step, "pm"); + if (!pmStr.empty()) { + try { + double pmValue = c_locale_stod(pmStr); + pm = PrimeMeridian::create(createMapWithUnknownName(), + Angle(pmValue)); + } catch (const std::invalid_argument &) { + bool found = false; + if (pmStr == "paris") { + found = true; + pm = PrimeMeridian::PARIS; + } + auto proj_prime_meridians = proj_list_prime_meridians(); + for (int i = 0; !found && proj_prime_meridians[i].id != nullptr; + i++) { + if (pmStr == proj_prime_meridians[i].id) { + found = true; + std::string name = static_cast<char>(::toupper(pmStr[0])) + + pmStr.substr(1); + double pmValue = + dmstor(proj_prime_meridians[i].defn, nullptr) * + RAD_TO_DEG; + pm = PrimeMeridian::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, name), + Angle(pmValue)); + break; + } + } + if (!found) { + throw ParsingException("unknown pm " + pmStr); + } + } + } + return pm; +} + +// --------------------------------------------------------------------------- + +std::string PROJStringParser::Private::guessBodyName(double a) { + return Ellipsoid::guessBodyName(dbContext_, a); +} + +// --------------------------------------------------------------------------- + +GeodeticReferenceFrameNNPtr +PROJStringParser::Private::buildDatum(const Step &step, + const std::string &title) { + + const auto &ellpsStr = getParamValue(step, "ellps"); + const auto &datumStr = getParamValue(step, "datum"); + const auto &aStr = getParamValue(step, "a"); + const auto &bStr = getParamValue(step, "b"); + const auto &rfStr = getParamValue(step, "rf"); + const auto &RStr = getParamValue(step, "R"); + const util::optional<std::string> optionalEmptyString{}; + + PrimeMeridianNNPtr pm(buildPrimeMeridian(step)); + PropertyMap grfMap; + + if (!datumStr.empty()) { + if (datumStr == "WGS84") { + return GeodeticReferenceFrame::EPSG_6326; + } else if (datumStr == "NAD83") { + return GeodeticReferenceFrame::EPSG_6269; + } else if (datumStr == "NAD27") { + return GeodeticReferenceFrame::EPSG_6267; + } else { + + for (const auto &datumDesc : datumDescs) { + if (datumStr == datumDesc.projName) { + (void)datumDesc.gcsName; // to please cppcheck + (void)datumDesc.gcsCode; // to please cppcheck + auto ellipsoid = Ellipsoid::createFlattenedSphere( + grfMap + .set(IdentifiedObject::NAME_KEY, + datumDesc.ellipsoidName) + .set(Identifier::CODESPACE_KEY, Identifier::EPSG) + .set(Identifier::CODE_KEY, datumDesc.ellipsoidCode), + Length(datumDesc.a), Scale(datumDesc.rf)); + return GeodeticReferenceFrame::create( + grfMap + .set(IdentifiedObject::NAME_KEY, + datumDesc.datumName) + .set(Identifier::CODESPACE_KEY, Identifier::EPSG) + .set(Identifier::CODE_KEY, datumDesc.datumCode), + ellipsoid, optionalEmptyString, pm); + } + } + } + throw ParsingException("unknown datum " + datumStr); + } + + else if (!ellpsStr.empty()) { + if (ellpsStr == "WGS84") { + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() ? "Unknown based on WGS84 ellipsoid" + : title.c_str()), + Ellipsoid::WGS84, optionalEmptyString, pm); + } else if (ellpsStr == "GRS80") { + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() ? "Unknown based on GRS80 ellipsoid" + : title.c_str()), + Ellipsoid::GRS1980, optionalEmptyString, pm); + } else { + auto proj_ellps = proj_list_ellps(); + for (int i = 0; proj_ellps[i].id != nullptr; i++) { + if (ellpsStr == proj_ellps[i].id) { + assert(strncmp(proj_ellps[i].major, "a=", 2) == 0); + const double a_iter = + c_locale_stod(proj_ellps[i].major + 2); + EllipsoidPtr ellipsoid; + PropertyMap ellpsMap; + if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) { + const double b_iter = + c_locale_stod(proj_ellps[i].ell + 2); + ellipsoid = Ellipsoid::createTwoAxis( + ellpsMap.set(IdentifiedObject::NAME_KEY, + proj_ellps[i].name), + Length(a_iter), Length(b_iter)) + .as_nullable(); + } else { + assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0); + const double rf_iter = + c_locale_stod(proj_ellps[i].ell + 3); + ellipsoid = Ellipsoid::createFlattenedSphere( + ellpsMap.set(IdentifiedObject::NAME_KEY, + proj_ellps[i].name), + Length(a_iter), Scale(rf_iter)) + .as_nullable(); + } + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() + ? std::string("Unknown based on ") + + proj_ellps[i].name + " ellipsoid" + : title), + NN_NO_CHECK(ellipsoid), optionalEmptyString, pm); + } + } + throw ParsingException("unknown ellipsoid " + ellpsStr); + } + } + + else if (!aStr.empty() && !bStr.empty()) { + double a; + try { + a = c_locale_stod(aStr); + } catch (const std::invalid_argument &) { + throw ParsingException("Invalid a value"); + } + double b; + try { + b = c_locale_stod(bStr); + } catch (const std::invalid_argument &) { + throw ParsingException("Invalid b value"); + } + auto ellipsoid = + Ellipsoid::createTwoAxis(createMapWithUnknownName(), Length(a), + Length(b), guessBodyName(a)) + ->identify(); + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title.c_str()), + ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); + } + + else if (!aStr.empty() && !rfStr.empty()) { + double a; + try { + a = c_locale_stod(aStr); + } catch (const std::invalid_argument &) { + throw ParsingException("Invalid a value"); + } + double rf; + try { + rf = c_locale_stod(rfStr); + } catch (const std::invalid_argument &) { + throw ParsingException("Invalid rf value"); + } + auto ellipsoid = Ellipsoid::createFlattenedSphere( + createMapWithUnknownName(), Length(a), Scale(rf), + guessBodyName(a)) + ->identify(); + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title.c_str()), + ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); + } + + else if (!RStr.empty()) { + double R; + try { + R = c_locale_stod(RStr); + } catch (const std::invalid_argument &) { + throw ParsingException("Invalid R value"); + } + auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(), + Length(R), guessBodyName(R)); + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title.c_str()), + ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); + } + + if (!aStr.empty() && bStr.empty() && rfStr.empty()) { + throw ParsingException("a found, but b or rf missing"); + } + + if (!bStr.empty() && aStr.empty()) { + throw ParsingException("b found, but a missing"); + } + + if (!rfStr.empty() && aStr.empty()) { + throw ParsingException("rf found, but a missing"); + } + + if (pm->_isEquivalentTo(PrimeMeridian::GREENWICH.get())) { + return GeodeticReferenceFrame::EPSG_6326; + } else { + return GeodeticReferenceFrame::create( + grfMap.set(IdentifiedObject::NAME_KEY, + "Unknown based on WGS84 ellipsoid"), + Ellipsoid::WGS84, optionalEmptyString, pm); + } +} + +// --------------------------------------------------------------------------- + +static const MeridianPtr nullMeridian{}; + +static CoordinateSystemAxisNNPtr +createAxis(const std::string &name, const std::string &abbreviation, + const AxisDirection &direction, const common::UnitOfMeasure &unit, + const MeridianPtr &meridian = nullMeridian) { + return CoordinateSystemAxis::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, name), abbreviation, + direction, unit, meridian); +} + +std::vector<CoordinateSystemAxisNNPtr> +PROJStringParser::Private::processAxisSwap(const Step &step, + const UnitOfMeasure &unit, + int iAxisSwap, AxisType axisType, + bool ignorePROJAxis) { + assert(iAxisSwap < 0 || ci_equal(steps_[iAxisSwap].name, "axisswap")); + + const bool isGeographic = unit.type() == UnitOfMeasure::Type::ANGULAR; + const auto &eastName = + isGeographic ? AxisName::Longitude : AxisName::Easting; + const auto &eastAbbev = + isGeographic ? AxisAbbreviation::lon : AxisAbbreviation::E; + const auto &eastDir = isGeographic + ? AxisDirection::EAST + : (axisType == AxisType::NORTH_POLE) + ? AxisDirection::SOUTH + : (axisType == AxisType::SOUTH_POLE) + ? AxisDirection::NORTH + : AxisDirection::EAST; + CoordinateSystemAxisNNPtr east = createAxis( + eastName, eastAbbev, eastDir, unit, + (!isGeographic && + (axisType == AxisType::NORTH_POLE || axisType == AxisType::SOUTH_POLE)) + ? Meridian::create(Angle(90, UnitOfMeasure::DEGREE)).as_nullable() + : nullMeridian); + + const auto &northName = + isGeographic ? AxisName::Latitude : AxisName::Northing; + const auto &northAbbev = + isGeographic ? AxisAbbreviation::lat : AxisAbbreviation::N; + const auto &northDir = isGeographic + ? AxisDirection::NORTH + : (axisType == AxisType::NORTH_POLE) + ? AxisDirection::SOUTH + : (axisType == AxisType::SOUTH_POLE) + ? AxisDirection::NORTH + : AxisDirection::NORTH; + CoordinateSystemAxisNNPtr north = createAxis( + northName, northAbbev, northDir, unit, + (!isGeographic && axisType == AxisType::NORTH_POLE) + ? Meridian::create(Angle(180, UnitOfMeasure::DEGREE)).as_nullable() + : (!isGeographic && axisType == AxisType::SOUTH_POLE) + ? Meridian::create(Angle(0, UnitOfMeasure::DEGREE)) + .as_nullable() + : nullMeridian); + + CoordinateSystemAxisNNPtr west = + createAxis(isGeographic ? AxisName::Longitude : AxisName::Westing, + isGeographic ? AxisAbbreviation::lon : std::string(), + AxisDirection::WEST, unit); + + CoordinateSystemAxisNNPtr south = + createAxis(isGeographic ? AxisName::Latitude : AxisName::Southing, + isGeographic ? AxisAbbreviation::lat : std::string(), + AxisDirection::SOUTH, unit); + + std::vector<CoordinateSystemAxisNNPtr> axis{east, north}; + + const auto &axisStr = getParamValue(step, "axis"); + if (!ignorePROJAxis && !axisStr.empty()) { + if (axisStr.size() == 3) { + for (int i = 0; i < 2; i++) { + if (axisStr[i] == 'n') { + axis[i] = north; + } else if (axisStr[i] == 's') { + axis[i] = south; + } else if (axisStr[i] == 'e') { + axis[i] = east; + } else if (axisStr[i] == 'w') { + axis[i] = west; + } else { + throw ParsingException("Unhandled axis=" + axisStr); + } + } + } else { + throw ParsingException("Unhandled axis=" + axisStr); + } + } else if (iAxisSwap >= 0) { + const auto &stepAxisSwap = steps_[iAxisSwap]; + const auto &orderStr = getParamValue(stepAxisSwap, "order"); + auto orderTab = split(orderStr, ','); + if (orderTab.size() != 2) { + throw ParsingException("Unhandled order=" + orderStr); + } + if (stepAxisSwap.inverted) { + throw ParsingException("Unhandled +inv for +proj=axisswap"); + } + + for (size_t i = 0; i < 2; i++) { + if (orderTab[i] == "1") { + axis[i] = east; + } else if (orderTab[i] == "-1") { + axis[i] = west; + } else if (orderTab[i] == "2") { + axis[i] = north; + } else if (orderTab[i] == "-2") { + axis[i] = south; + } else { + throw ParsingException("Unhandled order=" + orderStr); + } + } + } + return axis; +} + +// --------------------------------------------------------------------------- + +EllipsoidalCSNNPtr +PROJStringParser::Private::buildEllipsoidalCS(int iStep, int iUnitConvert, + int iAxisSwap, bool ignoreVUnits, + bool ignorePROJAxis) { + const auto &step = steps_[iStep]; + assert(iUnitConvert < 0 || + ci_equal(steps_[iUnitConvert].name, "unitconvert")); + + UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE; + if (iUnitConvert >= 0) { + const auto &stepUnitConvert = steps_[iUnitConvert]; + const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); + const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); + if (stepUnitConvert.inverted) { + std::swap(xy_in, xy_out); + } + if (iUnitConvert < iStep) { + std::swap(xy_in, xy_out); + } + if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" || + (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) { + throw ParsingException("unhandled values for xy_in and/or xy_out"); + } + if (*xy_out == "rad") { + angularUnit = UnitOfMeasure::RADIAN; + } else if (*xy_out == "grad") { + angularUnit = UnitOfMeasure::GRAD; + } + } + + std::vector<CoordinateSystemAxisNNPtr> axis = processAxisSwap( + step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis); + CoordinateSystemAxisNNPtr up = CoordinateSystemAxis::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + AxisName::Ellipsoidal_height), + AxisAbbreviation::h, AxisDirection::UP, + buildUnit(step, "vunits", "vto_meter")); + + return (!ignoreVUnits && !hasParamValue(step, "geoidgrids") && + (hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter"))) + ? EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1], up) + : EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1]); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr +PROJStringParser::Private::buildGeographicCRS(int iStep, int iUnitConvert, + int iAxisSwap, bool ignoreVUnits, + bool ignorePROJAxis) { + const auto &step = steps_[iStep]; + + const auto &title = isGeodeticStep(step.name) ? title_ : emptyString; + + auto datum = buildDatum(step, title); + + return GeographicCRS::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title), + datum, buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, ignoreVUnits, + ignorePROJAxis)); +} + +// --------------------------------------------------------------------------- + +GeodeticCRSNNPtr +PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) { + const auto &step = steps_[iStep]; + + assert(isGeocentricStep(step.name)); + assert(iUnitConvert < 0 || + ci_equal(steps_[iUnitConvert].name, "unitconvert")); + + const auto &title = title_; + + auto datum = buildDatum(step, title); + + UnitOfMeasure unit = UnitOfMeasure::METRE; + if (iUnitConvert >= 0) { + const auto &stepUnitConvert = steps_[iUnitConvert]; + const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); + const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); + const std::string *z_in = &getParamValue(stepUnitConvert, "z_in"); + const std::string *z_out = &getParamValue(stepUnitConvert, "z_out"); + if (stepUnitConvert.inverted) { + std::swap(xy_in, xy_out); + std::swap(z_in, z_out); + } + if (xy_in->empty() || xy_out->empty() || *xy_in != "m" || + *z_in != "m" || *xy_out != *z_out) { + throw ParsingException( + "unhandled values for xy_in, z_in, xy_out or z_out"); + } + + const LinearUnitDesc *unitsMatch = nullptr; + try { + double to_meter_value = c_locale_stod(*xy_out); + unitsMatch = getLinearUnits(to_meter_value); + if (unitsMatch == nullptr) { + unit = _buildUnit(to_meter_value); + } + } catch (const std::invalid_argument &) { + unitsMatch = getLinearUnits(*xy_out); + if (!unitsMatch) { + throw ParsingException( + "unhandled values for xy_in, z_in, xy_out or z_out"); + } + unit = _buildUnit(unitsMatch); + } + } + + auto cs = CartesianCS::createGeocentric(unit); + return GeodeticCRS::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title), + datum, cs); +} + +// --------------------------------------------------------------------------- + +CRSNNPtr +PROJStringParser::Private::buildBoundOrCompoundCRSIfNeeded(int iStep, + CRSNNPtr crs) { + const auto &step = steps_[iStep]; + const auto &towgs84 = getParamValue(step, "towgs84"); + if (!towgs84.empty()) { + std::vector<double> towgs84Values; + const auto tokens = split(towgs84, ','); + for (const auto &str : tokens) { + try { + towgs84Values.push_back(c_locale_stod(str)); + } catch (const std::invalid_argument &) { + throw ParsingException("Non numerical value in towgs84 clause"); + } + } + crs = BoundCRS::createFromTOWGS84(crs, towgs84Values); + } + + const auto &nadgrids = getParamValue(step, "nadgrids"); + if (!nadgrids.empty()) { + crs = BoundCRS::createFromNadgrids(crs, nadgrids); + } + + const auto &geoidgrids = getParamValue(step, "geoidgrids"); + if (!geoidgrids.empty()) { + auto vdatum = + VerticalReferenceFrame::create(createMapWithUnknownName()); + + const UnitOfMeasure unit = buildUnit(step, "vunits", "vto_meter"); + + auto vcrs = + VerticalCRS::create(createMapWithUnknownName(), vdatum, + VerticalCS::createGravityRelatedHeight(unit)); + + auto transformation = + Transformation::createGravityRelatedHeightToGeographic3D( + PropertyMap().set(IdentifiedObject::NAME_KEY, + "unknown to WGS84 ellipsoidal height"), + crs, GeographicCRS::EPSG_4979, geoidgrids, + std::vector<PositionalAccuracyNNPtr>()); + auto boundvcrs = + BoundCRS::create(vcrs, GeographicCRS::EPSG_4979, transformation); + + crs = CompoundCRS::create(createMapWithUnknownName(), + std::vector<CRSNNPtr>{crs, boundvcrs}); + } + + return crs; +} + +// --------------------------------------------------------------------------- + +static double getAngularValue(const std::string ¶mValue, + bool *pHasError = nullptr) { + char *endptr = nullptr; + double value = dmstor(paramValue.c_str(), &endptr) * RAD_TO_DEG; + if (value == HUGE_VAL || endptr != paramValue.c_str() + paramValue.size()) { + if (pHasError) + *pHasError = true; + return 0.0; + } + if (pHasError) + *pHasError = false; + return value; +} + +// --------------------------------------------------------------------------- + +static double getNumericValue(const std::string ¶mValue, + bool *pHasError = nullptr) { + try { + double value = c_locale_stod(paramValue); + if (pHasError) + *pHasError = false; + return value; + } catch (const std::invalid_argument &) { + if (pHasError) + *pHasError = true; + return 0.0; + } +} + +// --------------------------------------------------------------------------- + +CRSNNPtr PROJStringParser::Private::buildProjectedCRS( + int iStep, GeographicCRSNNPtr geogCRS, int iUnitConvert, int iAxisSwap) { + auto &step = steps_[iStep]; + auto mappings = getMappingsFromPROJName(step.name); + const MethodMapping *mapping = mappings.empty() ? nullptr : mappings[0]; + + assert(isProjectedStep(step.name)); + assert(iUnitConvert < 0 || + ci_equal(steps_[iUnitConvert].name, "unitconvert")); + + const auto &title = title_; + + if (!buildPrimeMeridian(step)->longitude()._isEquivalentTo( + geogCRS->primeMeridian()->longitude(), + util::IComparable::Criterion::EQUIVALENT)) { + throw ParsingException("inconsistant pm values between projectedCRS " + "and its base geographicalCRS"); + } + + auto axisType = AxisType::REGULAR; + + if (step.name == "tmerc" && getParamValue(step, "axis") == "wsu") { + mapping = + getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED); + } else if (step.name == "etmerc") { + mapping = getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR); + // TODO: we loose the link to the proj etmerc method here. Add some + // property to Conversion to keep it ? + } else if (step.name == "lcc") { + const auto &lat_0 = getParamValue(step, "lat_0"); + const auto &lat_1 = getParamValue(step, "lat_1"); + const auto &lat_2 = getParamValue(step, "lat_2"); + const auto &k_0 = getParamValue(step, "k_0"); + const auto &k = getParamValue(step, "k"); + if (lat_2.empty() && !lat_0.empty() && !lat_1.empty() && + getAngularValue(lat_0) == getAngularValue(lat_1)) { + mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); + } else if ((!k_0.empty() && getNumericValue(k_0) != 1.0) || + (!k.empty() && getNumericValue(k) != 1.0)) { + mapping = getMapping( + EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN); + } else { + mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP); + } + } else if (step.name == "aeqd" && hasParamValue(step, "guam")) { + mapping = getMapping(EPSG_CODE_METHOD_GUAM_PROJECTION); + } else if (step.name == "cea" && + !geogCRS->datum()->ellipsoid()->isSphere()) { + mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA); + } else if (step.name == "geos" && getParamValue(step, "sweep") == "x") { + mapping = + getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X); + } else if (step.name == "geos") { + mapping = + getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y); + } else if (step.name == "omerc") { + if (hasParamValue(step, "no_uoff") || hasParamValue(step, "no_off")) { + mapping = + getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A); + } else if (hasParamValue(step, "lat_1") && + hasParamValue(step, "lon_1") && + hasParamValue(step, "lat_2") && + hasParamValue(step, "lon_2")) { + mapping = getMapping( + PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN); + } else { + mapping = + getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B); + } + } else if (step.name == "somerc") { + mapping = + getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B); + step.paramValues.emplace_back(Step::KeyValue("alpha", "90")); + step.paramValues.emplace_back(Step::KeyValue("gamma", "90")); + step.paramValues.emplace_back( + Step::KeyValue("lonc", getParamValue(step, "lon_0"))); + } else if (step.name == "krovak" && getParamValue(step, "axis") == "swu") { + mapping = getMapping(EPSG_CODE_METHOD_KROVAK); + } else if (step.name == "merc") { + if (hasParamValue(step, "a") && hasParamValue(step, "b") && + getParamValue(step, "a") == getParamValue(step, "b") && + (!hasParamValue(step, "lat_ts") || + getAngularValue(getParamValue(step, "lat_ts")) == 0.0) && + (!hasParamValue(step, "k") || + getNumericValue(getParamValue(step, "k")) == 1.0) && + getParamValue(step, "nadgrids") == "@null") { + mapping = getMapping( + EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR); + for (size_t i = 0; i < step.paramValues.size(); ++i) { + if (ci_equal(step.paramValues[i].key, "nadgrids")) { + step.paramValues.erase(step.paramValues.begin() + i); + break; + } + } + } else if (hasParamValue(step, "lat_ts")) { + mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_B); + } else { + mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A); + } + } else if (step.name == "stere") { + if (hasParamValue(step, "lat_0") && + std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) - + 90.0) < 1e-10) { + const double lat_0 = getAngularValue(getParamValue(step, "lat_0")); + if (lat_0 > 0) { + axisType = AxisType::NORTH_POLE; + } else { + axisType = AxisType::SOUTH_POLE; + } + if (hasParamValue(step, "lat_ts")) { + mapping = + getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B); + } else { + mapping = + getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A); + } + } else { + mapping = getMapping(PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC); + } + } + + UnitOfMeasure unit = buildUnit(step, "units", "to_meter"); + if (iUnitConvert >= 0) { + const auto &stepUnitConvert = steps_[iUnitConvert]; + const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); + const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); + if (stepUnitConvert.inverted) { + std::swap(xy_in, xy_out); + } + if (xy_in->empty() || xy_out->empty() || *xy_in != "m") { + if (step.name != "ob_tran") { + throw ParsingException( + "unhandled values for xy_in and/or xy_out"); + } + } + + const LinearUnitDesc *unitsMatch = nullptr; + try { + double to_meter_value = c_locale_stod(*xy_out); + unitsMatch = getLinearUnits(to_meter_value); + if (unitsMatch == nullptr) { + unit = _buildUnit(to_meter_value); + } + } catch (const std::invalid_argument &) { + unitsMatch = getLinearUnits(*xy_out); + if (!unitsMatch) { + if (step.name != "ob_tran") { + throw ParsingException( + "unhandled values for xy_in and/or xy_out"); + } + } else { + unit = _buildUnit(unitsMatch); + } + } + } + + ConversionPtr conv; + + auto mapWithUnknownName = createMapWithUnknownName(); + + if (step.name == "utm") { + const int zone = std::atoi(getParamValue(step, "zone").c_str()); + const bool north = !hasParamValue(step, "south"); + conv = + Conversion::createUTM(emptyPropertyMap, zone, north).as_nullable(); + } else if (mapping) { + + auto methodMap = + PropertyMap().set(IdentifiedObject::NAME_KEY, mapping->wkt2_name); + if (mapping->epsg_code) { + methodMap.set(Identifier::CODESPACE_KEY, Identifier::EPSG) + .set(Identifier::CODE_KEY, mapping->epsg_code); + } + std::vector<OperationParameterNNPtr> parameters; + std::vector<ParameterValueNNPtr> values; + for (int i = 0; mapping->params[i] != nullptr; i++) { + const auto *param = mapping->params[i]; + std::string proj_name(param->proj_name ? param->proj_name : ""); + const std::string *paramValue = + !proj_name.empty() ? &getParamValue(step, proj_name) + : &emptyString; + // k and k_0 may be used indifferently + if (paramValue->empty() && proj_name == "k") { + paramValue = &getParamValue(step, "k_0"); + } else if (paramValue->empty() && proj_name == "k_0") { + paramValue = &getParamValue(step, "k"); + } + double value = 0; + if (!paramValue->empty()) { + bool hasError = false; + if (param->unit_type == UnitOfMeasure::Type::ANGULAR) { + value = getAngularValue(*paramValue, &hasError); + } else { + value = getNumericValue(*paramValue, &hasError); + } + if (hasError) { + throw ParsingException("invalid value for " + proj_name); + } + + } else if (param->unit_type == UnitOfMeasure::Type::SCALE) { + value = 1; + } else { + // For omerc, if gamma is missing, the default value is + // alpha + if (step.name == "omerc" && proj_name == "gamma") { + paramValue = &getParamValue(step, "alpha"); + if (!paramValue->empty()) { + value = getAngularValue(*paramValue); + } + } else if (step.name == "krovak") { + if (param->epsg_code == + EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS) { + value = 30.2881397222222; + } else if ( + param->epsg_code == + EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL) { + value = 78.5; + } + } + } + + PropertyMap propertiesParameter; + propertiesParameter.set(IdentifiedObject::NAME_KEY, + param->wkt2_name); + if (param->epsg_code) { + propertiesParameter.set(Identifier::CODE_KEY, param->epsg_code); + propertiesParameter.set(Identifier::CODESPACE_KEY, + Identifier::EPSG); + } + parameters.push_back( + OperationParameter::create(propertiesParameter)); + // In PROJ convention, angular parameters are always in degree + // and linear parameters always in metre. + double valRounded = + param->unit_type == UnitOfMeasure::Type::LINEAR + ? Length(value, UnitOfMeasure::METRE).convertToUnit(unit) + : value; + if (std::fabs(valRounded - std::round(valRounded)) < 1e-8) { + valRounded = std::round(valRounded); + } + values.push_back(ParameterValue::create(Measure( + valRounded, + param->unit_type == UnitOfMeasure::Type::ANGULAR + ? UnitOfMeasure::DEGREE + : param->unit_type == UnitOfMeasure::Type::LINEAR + ? unit + : param->unit_type == UnitOfMeasure::Type::SCALE + ? UnitOfMeasure::SCALE_UNITY + : UnitOfMeasure::NONE))); + } + + conv = Conversion::create(mapWithUnknownName, methodMap, parameters, + values) + .as_nullable(); + } else { + std::vector<OperationParameterNNPtr> parameters; + std::vector<ParameterValueNNPtr> values; + std::string methodName = "PROJ " + step.name; + for (const auto ¶m : step.paramValues) { + if (param.key == "wktext" || param.key == "no_defs" || + param.key == "datum" || param.key == "ellps" || + param.key == "a" || param.key == "b" || param.key == "R" || + param.key == "towgs84" || param.key == "nadgrids" || + param.key == "geoidgrids" || param.key == "units" || + param.key == "to_meter" || param.key == "vunits" || + param.key == "vto_meter") { + continue; + } + if (param.value.empty()) { + methodName += " " + param.key; + } else if (param.key == "o_proj") { + methodName += " " + param.key + "=" + param.value; + } else { + parameters.push_back(OperationParameter::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, param.key))); + bool hasError = false; + if (param.key == "x_0" || param.key == "y_0") { + double value = getNumericValue(param.value, &hasError); + values.push_back(ParameterValue::create( + Measure(value, UnitOfMeasure::METRE))); + } else if (param.key == "k" || param.key == "k_0") { + double value = getNumericValue(param.value, &hasError); + values.push_back(ParameterValue::create( + Measure(value, UnitOfMeasure::SCALE_UNITY))); + } else { + double value = getAngularValue(param.value, &hasError); + values.push_back(ParameterValue::create( + Measure(value, UnitOfMeasure::DEGREE))); + } + if (hasError) { + throw ParsingException("invalid value for " + param.key); + } + } + } + conv = Conversion::create( + mapWithUnknownName, + PropertyMap().set(IdentifiedObject::NAME_KEY, methodName), + parameters, values) + .as_nullable(); + + if (methodName == "PROJ ob_tran o_proj=longlat") { + return DerivedGeographicCRS::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), + geogCRS, NN_NO_CHECK(conv), + buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, false, + false)); + } + } + + std::vector<CoordinateSystemAxisNNPtr> axis = + processAxisSwap(step, unit, iAxisSwap, axisType, false); + + auto cs = CartesianCS::create(emptyPropertyMap, axis[0], axis[1]); + + CRSNNPtr crs = ProjectedCRS::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, + title.empty() ? "unknown" : title), + geogCRS, NN_NO_CHECK(conv), cs); + + if (!hasParamValue(step, "geoidgrids") && + (hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter"))) { + auto vdatum = VerticalReferenceFrame::create(mapWithUnknownName); + + const UnitOfMeasure vunit = buildUnit(step, "vunits", "vto_meter"); + + auto vcrs = + VerticalCRS::create(mapWithUnknownName, vdatum, + VerticalCS::createGravityRelatedHeight(vunit)); + + crs = CompoundCRS::create(mapWithUnknownName, + std::vector<CRSNNPtr>{crs, vcrs}); + } + + return crs; +} + +// --------------------------------------------------------------------------- + +static bool isDatumDefiningParam(const std::string ¶m) { + return (param == "datum" || param == "ellps" || param == "a" || + param == "b" || param == "rf" || param == "R"); +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr PROJStringParser::Private::buildHelmertTransformation( + int iStep, int iFirstAxisSwap, int iFirstUnitConvert, int iFirstGeogStep, + int iSecondGeogStep, int iSecondAxisSwap, int iSecondUnitConvert) { + auto &step = steps_[iStep]; + auto datum = buildDatum(step, std::string()); + auto cs = CartesianCS::createGeocentric(UnitOfMeasure::METRE); + + auto mapWithUnknownName = createMapWithUnknownName(); + + auto sourceCRS = + iFirstGeogStep >= 0 + ? util::nn_static_pointer_cast<crs::CRS>( + buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert, + iFirstAxisSwap, true, false)) + : util::nn_static_pointer_cast<crs::CRS>( + GeodeticCRS::create(mapWithUnknownName, datum, cs)); + auto targetCRS = + iSecondGeogStep >= 0 + ? util::nn_static_pointer_cast<crs::CRS>( + buildGeographicCRS(iSecondGeogStep, iSecondUnitConvert, + iSecondAxisSwap, true, false)) + : util::nn_static_pointer_cast<crs::CRS>( + GeodeticCRS::create(mapWithUnknownName, datum, cs)); + + double x = 0; + double y = 0; + double z = 0; + double rx = 0; + double ry = 0; + double rz = 0; + double s = 0; + double dx = 0; + double dy = 0; + double dz = 0; + double drx = 0; + double dry = 0; + double drz = 0; + double ds = 0; + double t_epoch = 0; + bool rotationTerms = false; + bool timeDependent = false; + bool conventionFound = false; + bool positionVectorConvention = false; + + struct Params { + double *pValue; + const char *name; + bool *pPresent; + }; + const Params knownParams[] = { + {&x, "x", nullptr}, + {&y, "y", nullptr}, + {&z, "z", nullptr}, + {&rx, "rx", &rotationTerms}, + {&ry, "ry", &rotationTerms}, + {&rz, "rz", &rotationTerms}, + {&s, "s", &rotationTerms}, + {&dx, "dx", &timeDependent}, + {&dy, "dy", &timeDependent}, + {&dz, "dz", &timeDependent}, + {&drx, "drx", &timeDependent}, + {&dry, "dry", &timeDependent}, + {&drz, "drz", &timeDependent}, + {&ds, "ds", &timeDependent}, + {&t_epoch, "t_epoch", &timeDependent}, + {nullptr, "exact", nullptr}, + }; + + for (const auto ¶m : step.paramValues) { + if (isDatumDefiningParam(param.key)) { + continue; + } + if (param.key == "convention") { + if (param.value == "position_vector") { + positionVectorConvention = true; + conventionFound = true; + } else if (param.value == "coordinate_frame") { + positionVectorConvention = false; + conventionFound = true; + } else { + throw ParsingException("unsupported convention"); + } + } else { + bool found = false; + for (auto &&knownParam : knownParams) { + if (param.key == knownParam.name) { + found = true; + if (knownParam.pValue) + *(knownParam.pValue) = getNumericValue(param.value); + if (knownParam.pPresent) + *(knownParam.pPresent) = true; + break; + } + } + if (!found) { + throw ParsingException("unsupported keyword for Helmert: " + + param.key); + } + } + } + + rotationTerms |= timeDependent; + if (rotationTerms && !conventionFound) { + throw ParsingException("missing convention"); + } + + std::vector<metadata::PositionalAccuracyNNPtr> emptyAccuracies; + + auto transf = ([&]() { + if (!rotationTerms) { + return Transformation::createGeocentricTranslations( + mapWithUnknownName, sourceCRS, targetCRS, x, y, z, + emptyAccuracies); + } else if (positionVectorConvention) { + if (timeDependent) { + return Transformation::createTimeDependentPositionVector( + mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, ry, + rz, s, dx, dy, dz, drx, dry, drz, ds, t_epoch, + emptyAccuracies); + } else { + return Transformation::createPositionVector( + mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, ry, + rz, s, emptyAccuracies); + } + } else { + if (timeDependent) { + return Transformation:: + createTimeDependentCoordinateFrameRotation( + mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, + ry, rz, s, dx, dy, dz, drx, dry, drz, ds, t_epoch, + emptyAccuracies); + } else { + return Transformation::createCoordinateFrameRotation( + mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, ry, + rz, s, emptyAccuracies); + } + } + })(); + + if (step.inverted) { + return util::nn_static_pointer_cast<CoordinateOperation>( + transf->inverse()); + } else { + return util::nn_static_pointer_cast<CoordinateOperation>(transf); + } +} + +// --------------------------------------------------------------------------- + +CoordinateOperationNNPtr +PROJStringParser::Private::buildMolodenskyTransformation( + int iStep, int iFirstAxisSwap, int iFirstUnitConvert, int iFirstGeogStep, + int iSecondGeogStep, int iSecondAxisSwap, int iSecondUnitConvert) { + auto &step = steps_[iStep]; + + double dx = 0; + double dy = 0; + double dz = 0; + double da = 0; + double df = 0; + + struct Params { + double *pValue; + const char *name; + }; + const Params knownParams[] = { + {&dx, "dx"}, {&dy, "dy"}, {&dz, "dz"}, {&da, "da"}, {&df, "df"}, + }; + bool abridged = false; + + for (const auto ¶m : step.paramValues) { + if (isDatumDefiningParam(param.key)) { + continue; + } else if (param.key == "abridged") { + abridged = true; + } else { + bool found = false; + for (auto &&knownParam : knownParams) { + if (param.key == knownParam.name) { + found = true; + if (knownParam.pValue) + *(knownParam.pValue) = getNumericValue(param.value); + break; + } + } + if (!found) { + throw ParsingException("unsupported keyword for Molodensky: " + + param.key); + } + } + } + + auto datum = buildDatum(step, std::string()); + auto sourceCRS = iFirstGeogStep >= 0 + ? buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert, + iFirstAxisSwap, true, false) + : buildGeographicCRS(iStep, -1, -1, true, false); + + const auto &ellps = sourceCRS->ellipsoid(); + const double a = ellps->semiMajorAxis().getSIValue(); + const double rf = ellps->computedInverseFlattening(); + const double target_a = a + da; + const double target_rf = 1.0 / (1.0 / rf + df); + + auto mapWithUnknownName = createMapWithUnknownName(); + + auto target_ellipsoid = + Ellipsoid::createFlattenedSphere(mapWithUnknownName, Length(target_a), + Scale(target_rf)) + ->identify(); + auto target_datum = GeodeticReferenceFrame::create( + mapWithUnknownName, target_ellipsoid, util::optional<std::string>(), + PrimeMeridian::GREENWICH); + + auto targetCRS = util::nn_static_pointer_cast<crs::CRS>( + iSecondGeogStep >= 0 + ? buildGeographicCRS(iSecondGeogStep, iSecondUnitConvert, + iSecondAxisSwap, true, false) + : GeographicCRS::create(mapWithUnknownName, target_datum, + EllipsoidalCS::createLongitudeLatitude( + UnitOfMeasure::DEGREE))); + + auto sourceCRS_as_CRS = util::nn_static_pointer_cast<crs::CRS>(sourceCRS); + + std::vector<metadata::PositionalAccuracyNNPtr> emptyAccuracies; + + auto transf = ([&]() { + if (abridged) { + return Transformation::createAbridgedMolodensky( + mapWithUnknownName, sourceCRS_as_CRS, targetCRS, dx, dy, dz, da, + df, emptyAccuracies); + } else { + return Transformation::createMolodensky( + mapWithUnknownName, sourceCRS_as_CRS, targetCRS, dx, dy, dz, da, + df, emptyAccuracies); + } + })(); + + if (step.inverted) { + return util::nn_static_pointer_cast<CoordinateOperation>( + transf->inverse()); + } else { + return util::nn_static_pointer_cast<CoordinateOperation>(transf); + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a sub-class of BaseObject from a PROJ string. + * @throw ParsingException + */ +BaseObjectNNPtr +PROJStringParser::createFromPROJString(const std::string &projString) { + std::string vunits; + std::string vto_meter; + + PROJStringSyntaxParser(projString, d->steps_, d->globalParamValues_, + d->title_, vunits, vto_meter); + + if (d->steps_.empty()) { + + if (!vunits.empty() || !vto_meter.empty()) { + Step fakeStep; + if (!vunits.empty()) { + fakeStep.paramValues.emplace_back( + Step::KeyValue("vunits", vunits)); + } + if (!vto_meter.empty()) { + fakeStep.paramValues.emplace_back( + Step::KeyValue("vto_meter", vto_meter)); + } + auto vdatum = + VerticalReferenceFrame::create(createMapWithUnknownName()); + auto vcrs = VerticalCRS::create( + createMapWithUnknownName(), vdatum, + VerticalCS::createGravityRelatedHeight( + d->buildUnit(fakeStep, "vunits", "vto_meter"))); + return vcrs; + } + } + + if ((d->steps_.size() == 1 || + (d->steps_.size() == 2 && d->steps_[1].name == "unitconvert")) && + isGeocentricStep(d->steps_[0].name)) { + return d->buildGeocentricCRS( + 0, (d->steps_.size() == 2 && d->steps_[1].name == "unitconvert") + ? 1 + : -1); + } + + int iFirstGeogStep = -1; + int iSecondGeogStep = -1; + int iProjStep = -1; + int iFirstUnitConvert = -1; + int iSecondUnitConvert = -1; + int iFirstAxisSwap = -1; + int iSecondAxisSwap = -1; + int iHelmert = -1; + int iFirstCart = -1; + int iSecondCart = -1; + int iMolodensky = -1; + bool unexpectedStructure = false; + for (int i = 0; i < static_cast<int>(d->steps_.size()); i++) { + const auto &stepName = d->steps_[i].name; + if (isGeodeticStep(stepName)) { + if (iFirstGeogStep < 0) { + iFirstGeogStep = i; + } else if (iSecondGeogStep < 0) { + iSecondGeogStep = i; + } else { + unexpectedStructure = true; + break; + } + } else if (ci_equal(stepName, "unitconvert")) { + if (iFirstUnitConvert < 0) { + iFirstUnitConvert = i; + } else if (iSecondUnitConvert < 0) { + iSecondUnitConvert = i; + } else { + unexpectedStructure = true; + break; + } + } else if (ci_equal(stepName, "axisswap")) { + if (iFirstAxisSwap < 0) { + iFirstAxisSwap = i; + } else if (iSecondAxisSwap < 0) { + iSecondAxisSwap = i; + } else { + unexpectedStructure = true; + break; + } + } else if (stepName == "helmert") { + if (iHelmert >= 0) { + unexpectedStructure = true; + break; + } + iHelmert = i; + } else if (stepName == "cart") { + if (iFirstCart < 0) { + iFirstCart = i; + } else if (iSecondCart < 0) { + iSecondCart = i; + } else { + unexpectedStructure = true; + break; + } + } else if (stepName == "molodensky") { + if (iMolodensky >= 0) { + unexpectedStructure = true; + break; + } + iMolodensky = i; + } else if (isProjectedStep(stepName)) { + if (iProjStep >= 0) { + unexpectedStructure = true; + break; + } + iProjStep = i; + } else { + unexpectedStructure = true; + break; + } + } + + if (!unexpectedStructure) { + if (iFirstGeogStep == 0 && iSecondGeogStep < 0 && iProjStep < 0 && + iHelmert < 0 && iFirstCart < 0 && iMolodensky < 0 && + (iFirstUnitConvert < 0 || iSecondUnitConvert < 0) && + (iFirstAxisSwap < 0 || iSecondAxisSwap < 0)) { + const bool ignoreVUnits = false; + return d->buildBoundOrCompoundCRSIfNeeded( + 0, d->buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert, + iFirstAxisSwap, ignoreVUnits, false)); + } + if (iProjStep >= 0 && !d->steps_[iProjStep].inverted && + (iFirstGeogStep < 0 || iFirstGeogStep + 1 == iProjStep) && + iMolodensky < 0 && iSecondGeogStep < 0 && iFirstCart < 0 && + iHelmert < 0) { + if (iFirstGeogStep < 0) + iFirstGeogStep = iProjStep; + const bool ignoreVUnits = true; + return d->buildBoundOrCompoundCRSIfNeeded( + iProjStep, + d->buildProjectedCRS( + iProjStep, + d->buildGeographicCRS( + iFirstGeogStep, + iFirstUnitConvert < iFirstGeogStep ? iFirstUnitConvert + : -1, + iFirstAxisSwap < iFirstGeogStep ? iFirstAxisSwap : -1, + ignoreVUnits, true), + iFirstUnitConvert < iFirstGeogStep ? iSecondUnitConvert + : iFirstUnitConvert, + iFirstAxisSwap < iFirstGeogStep ? iSecondAxisSwap + : iFirstAxisSwap)); + } + if (d->steps_.size() == 1 && iHelmert == 0) { + return d->buildHelmertTransformation(iHelmert); + } + + if (iProjStep < 0 && iHelmert > 0 && iMolodensky < 0 && + (iFirstGeogStep < 0 || iFirstGeogStep == iFirstCart - 1 || + (iFirstGeogStep == iSecondCart + 1 && iSecondGeogStep < 0)) && + iFirstCart == iHelmert - 1 && iSecondCart == iHelmert + 1 && + (iSecondGeogStep < 0 || iSecondGeogStep == iSecondCart + 1) && + !d->steps_[iFirstCart].inverted && + d->steps_[iSecondCart].inverted && iFirstAxisSwap < iHelmert && + iFirstUnitConvert < iHelmert && + (iSecondAxisSwap < 0 || iSecondAxisSwap > iHelmert) && + (iSecondUnitConvert < 0 || iSecondUnitConvert > iHelmert)) { + return d->buildHelmertTransformation( + iHelmert, iFirstAxisSwap, iFirstUnitConvert, + iFirstGeogStep >= 0 && iFirstGeogStep == iFirstCart - 1 + ? iFirstGeogStep + : iFirstCart, + iFirstGeogStep == iSecondCart + 1 + ? iFirstGeogStep + : iSecondGeogStep == iSecondCart + 1 ? iSecondGeogStep + : iSecondCart, + iSecondAxisSwap, iSecondUnitConvert); + } + + if (d->steps_.size() == 1 && iMolodensky == 0) { + return d->buildMolodenskyTransformation(iMolodensky); + } + + if (iProjStep < 0 && iHelmert < 0 && iMolodensky > 0 && + (iFirstGeogStep < 0 || iFirstGeogStep == iMolodensky - 1 || + (iFirstGeogStep == iMolodensky + 1 && iSecondGeogStep < 0)) && + (iSecondGeogStep < 0 || iSecondGeogStep == iMolodensky + 1) && + iFirstAxisSwap < iMolodensky && iFirstUnitConvert < iMolodensky && + (iSecondAxisSwap < 0 || iSecondAxisSwap > iMolodensky) && + (iSecondUnitConvert < 0 || iSecondUnitConvert > iMolodensky)) { + return d->buildMolodenskyTransformation( + iMolodensky, iFirstAxisSwap, iFirstUnitConvert, + iFirstGeogStep >= 0 && iFirstGeogStep == iMolodensky - 1 + ? iFirstGeogStep + : iMolodensky, + iFirstGeogStep == iMolodensky + 1 + ? iFirstGeogStep + : iSecondGeogStep == iMolodensky + 1 ? iSecondGeogStep + : iMolodensky, + iSecondAxisSwap, iSecondUnitConvert); + } + } + + struct Logger { + std::string msg{}; + + // cppcheck-suppress functionStatic + void setMessage(const char *msgIn) noexcept { + try { + msg = msgIn; + } catch (const std::exception &) { + } + } + + static void log(void *user_data, int level, const char *msg) { + if (level == PJ_LOG_ERROR) { + static_cast<Logger *>(user_data)->setMessage(msg); + } + } + }; + + // If the structure is not recognized, then try to instanciate the + // pipeline, and if successful, wrap it in a PROJBasedOperation + Logger logger; + auto pj_context = proj_context_create(); + if (!pj_context) { + throw ParsingException("out of memory"); + } + proj_log_func(pj_context, &logger, Logger::log); + auto pj = proj_create(pj_context, projString.c_str()); + bool valid = pj != nullptr; + proj_destroy(pj); + + if (!valid && logger.msg.empty()) { + logger.setMessage(proj_errno_string(proj_context_errno(pj_context))); + } + + proj_context_destroy(pj_context); + + if (!valid) { + throw ParsingException(logger.msg); + } + + auto props = PropertyMap(); + if (!d->title_.empty()) { + props.set(IdentifiedObject::NAME_KEY, d->title_); + } + return operation::SingleOperation::createPROJBased(props, projString, + nullptr, nullptr, {}); +} + +} // namespace io +NS_PROJ_END diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake index 6a287a43..6fe84944 100644 --- a/src/lib_proj.cmake +++ b/src/lib_proj.cmake @@ -32,6 +32,24 @@ elseif(USE_THREAD AND NOT Threads_FOUND) message(FATAL_ERROR "No thread library found and thread/mutex support is required by USE_THREAD option") endif() +option(ENABLE_LTO "Build library with LTO optimization (if available)." ON) +if(ENABLE_LTO) + if("${CMAKE_C_COMPILER_ID}" MATCHES "Clang") + include (CheckCXXSourceCompiles) + SET(CMAKE_REQUIRED_FLAGS "-Wl,-flto") + check_cxx_source_compiles("int main(){ return 0; }" COMPILER_SUPPORTS_FLTO_FLAG) + IF(COMPILER_SUPPORTS_FLTO_FLAG) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto") + ENDIF() + else() + include(CheckCXXCompilerFlag) + CHECK_CXX_COMPILER_FLAG("-flto" COMPILER_SUPPORTS_FLTO_FLAG) + if(COMPILER_SUPPORTS_FLTO_FLAG) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto") + endif() + endif() +endif() + ############################################## ### library source list and include_list ### @@ -227,11 +245,27 @@ SET(SRC_LIBPROJ_CORE vector1.c pj_strtod.c ${CMAKE_CURRENT_BINARY_DIR}/proj_config.h - ) +) + +set(SRC_LIBPROJ_CPP + static.cpp + common.cpp + coordinateoperation.cpp + coordinatesystem.cpp + crs.cpp + datum.cpp + io.cpp + metadata.cpp + util.cpp + internal.cpp + factory.cpp + c_api.cpp +) set(HEADERS_LIBPROJ proj_api.h proj.h + proj_constants.h geodesic.h ) @@ -239,6 +273,10 @@ set(HEADERS_LIBPROJ source_group("Header Files" FILES ${HEADERS_LIBPROJ}) source_group("Source Files\\Core" FILES ${SRC_LIBPROJ_CORE}) source_group("Source Files\\PJ" FILES ${SRC_LIBPROJ_PJ}) +source_group("Source Files\\C++" FILES ${SRC_LIBPROJ_CPP}) + +include_directories(${CMAKE_SOURCE_DIR}/include) + include_directories( ${CMAKE_CURRENT_BINARY_DIR}) source_group("CMake Files" FILES CMakeLists.txt) @@ -269,11 +307,8 @@ endif(JNI_SUPPORT) ################################################# ## targets: libproj and proj_config.h ################################################# -set(ALL_LIBPROJ_SOURCES ${SRC_LIBPROJ_PJ} ${SRC_LIBPROJ_CORE}) +set(ALL_LIBPROJ_SOURCES ${SRC_LIBPROJ_PJ} ${SRC_LIBPROJ_CORE} ${SRC_LIBPROJ_CPP}) set(ALL_LIBPROJ_HEADERS ${HEADERS_LIBPROJ} ) -if(WIN32 AND BUILD_LIBPROJ_SHARED) - set(ALL_LIBPROJ_SOURCES ${ALL_LIBPROJ_SOURCES} proj.def ) -endif(WIN32 AND BUILD_LIBPROJ_SHARED) # Core targets configuration string(TOLOWER "${PROJECT_INTERN_NAME}" PROJECTNAMEL) @@ -313,7 +348,7 @@ endif() set_target_properties(${PROJ_CORE_TARGET} PROPERTIES - LINKER_LANGUAGE C) + LINKER_LANGUAGE CXX) ############################################## # Link properties @@ -330,6 +365,12 @@ if(USE_THREAD AND Threads_FOUND AND CMAKE_USE_PTHREADS_INIT) TARGET_LINK_LIBRARIES(${PROJ_CORE_TARGET} ${CMAKE_THREAD_LIBS_INIT}) endif(USE_THREAD AND Threads_FOUND AND CMAKE_USE_PTHREADS_INIT) +include_directories(${SQLITE3_INCLUDE_DIR}) +TARGET_LINK_LIBRARIES(${PROJ_CORE_TARGET} ${SQLITE3_LIBRARY}) + +if(MSVC) + target_compile_definitions(${PROJ_CORE_TARGET} PRIVATE PROJ_MSVC_DLL_EXPORT=1) +endif() ############################################## # install diff --git a/src/metadata.cpp b/src/metadata.cpp new file mode 100644 index 00000000..033782c9 --- /dev/null +++ b/src/metadata.cpp @@ -0,0 +1,1233 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/metadata.hpp" +#include "proj/common.hpp" +#include "proj/io.hpp" +#include "proj/util.hpp" + +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include <algorithm> +#include <memory> +#include <string> +#include <vector> + +using namespace NS_PROJ::internal; +using namespace NS_PROJ::io; +using namespace NS_PROJ::util; + +#if 0 +namespace dropbox{ namespace oxygen { +template<> nn<std::shared_ptr<NS_PROJ::metadata::Citation>>::~nn() = default; +template<> nn<NS_PROJ::metadata::ExtentPtr>::~nn() = default; +template<> nn<NS_PROJ::metadata::GeographicBoundingBoxPtr>::~nn() = default; +template<> nn<NS_PROJ::metadata::GeographicExtentPtr>::~nn() = default; +template<> nn<NS_PROJ::metadata::VerticalExtentPtr>::~nn() = default; +template<> nn<NS_PROJ::metadata::TemporalExtentPtr>::~nn() = default; +template<> nn<NS_PROJ::metadata::IdentifierPtr>::~nn() = default; +template<> nn<NS_PROJ::metadata::PositionalAccuracyPtr>::~nn() = default; +}} +#endif + +NS_PROJ_START +namespace metadata { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Citation::Private { + optional<std::string> title{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Citation::Citation() : d(internal::make_unique<Private>()) {} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Constructs a citation by its title. */ +Citation::Citation(const std::string &titleIn) + : d(internal::make_unique<Private>()) { + d->title = titleIn; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Citation::Citation(const Citation &other) + : d(internal::make_unique<Private>(*(other.d))) {} + +// --------------------------------------------------------------------------- + +Citation::~Citation() = default; + +// --------------------------------------------------------------------------- + +Citation &Citation::operator=(const Citation &other) { + if (this != &other) { + *d = *other.d; + } + return *this; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns the name by which the cited resource is known. */ +const optional<std::string> &Citation::title() PROJ_CONST_DEFN { + return d->title; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeographicExtent::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +GeographicExtent::GeographicExtent() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeographicExtent::~GeographicExtent() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeographicBoundingBox::Private { + double west_{}; + double south_{}; + double east_{}; + double north_{}; + + Private(double west, double south, double east, double north) + : west_(west), south_(south), east_(east), north_(north) {} + + bool intersects(const Private &other) const; + + std::unique_ptr<Private> intersection(const Private &other) const; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +GeographicBoundingBox::GeographicBoundingBox(double west, double south, + double east, double north) + : GeographicExtent(), + d(internal::make_unique<Private>(west, south, east, north)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeographicBoundingBox::~GeographicBoundingBox() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns the western-most coordinate of the limit of the dataset + * extent. + * + * The unit is degrees. + * + * If eastBoundLongitude < westBoundLongitude(), then the bounding box crosses + * the anti-meridian. + */ +double GeographicBoundingBox::westBoundLongitude() PROJ_CONST_DEFN { + return d->west_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the southern-most coordinate of the limit of the dataset + * extent. + * + * The unit is degrees. + */ +double GeographicBoundingBox::southBoundLatitude() PROJ_CONST_DEFN { + return d->south_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the eastern-most coordinate of the limit of the dataset + * extent. + * + * The unit is degrees. + * + * If eastBoundLongitude < westBoundLongitude(), then the bounding box crosses + * the anti-meridian. + */ +double GeographicBoundingBox::eastBoundLongitude() PROJ_CONST_DEFN { + return d->east_; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the northern-most coordinate of the limit of the dataset + * extent. + * + * The unit is degrees. + */ +double GeographicBoundingBox::northBoundLatitude() PROJ_CONST_DEFN { + return d->north_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeographicBoundingBox. + * + * If east < west, then the bounding box crosses the anti-meridian. + * + * @param west Western-most coordinate of the limit of the dataset extent (in + * degrees). + * @param south Southern-most coordinate of the limit of the dataset extent (in + * degrees). + * @param east Eastern-most coordinate of the limit of the dataset extent (in + * degrees). + * @param north Northern-most coordinate of the limit of the dataset extent (in + * degrees). + * @return a new GeographicBoundingBox. + */ +GeographicBoundingBoxNNPtr GeographicBoundingBox::create(double west, + double south, + double east, + double north) { + return GeographicBoundingBox::nn_make_shared<GeographicBoundingBox>( + west, south, east, north); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool GeographicBoundingBox::_isEquivalentTo( + const util::IComparable *other, util::IComparable::Criterion) const { + auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other); + if (!otherExtent) + return false; + return d->west_ == otherExtent->d->west_ && + d->south_ == otherExtent->d->south_ && + d->east_ == otherExtent->d->east_ && + d->north_ == otherExtent->d->north_; +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool GeographicBoundingBox::contains(const GeographicExtentNNPtr &other) const { + auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other.get()); + if (!otherExtent) { + return false; + } + const double W = d->west_; + const double E = d->east_; + const double N = d->north_; + const double S = d->south_; + const double oW = otherExtent->d->west_; + const double oE = otherExtent->d->east_; + const double oN = otherExtent->d->north_; + const double oS = otherExtent->d->south_; + + if (!(S <= oS && N >= oN)) { + return false; + } + + if (W == -180.0 && E == 180.0) { + return true; + } + + if (oW == -180.0 && oE == 180.0) { + return false; + } + + // Normal bounding box ? + if (W < E) { + if (oW < oE) { + return W <= oW && E >= oE; + } else { + return false; + } + // No: crossing antimerian + } else { + if (oW < oE) { + if (oW >= W) { + return true; + } else if (oE <= E) { + return true; + } else { + return false; + } + } else { + return W <= oW && E >= oE; + } + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool GeographicBoundingBox::Private::intersects(const Private &other) const { + const double W = west_; + const double E = east_; + const double N = north_; + const double S = south_; + const double oW = other.west_; + const double oE = other.east_; + const double oN = other.north_; + const double oS = other.south_; + + if (N < oS || S > oN) { + return false; + } + + if (W == -180.0 && E == 180.0 && oW > oE) { + return true; + } + + if (oW == -180.0 && oE == 180.0 && W > E) { + return true; + } + + // Normal bounding box ? + if (W <= E) { + if (oW < oE) { + if (std::max(W, oW) < std::min(E, oE)) { + return true; + } + return false; + } + + return intersects(Private(oW, oS, 180.0, oN)) || + intersects(Private(-180.0, oS, oE, oN)); + + // No: crossing antimerian + } else { + if (oW <= oE) { + return other.intersects(*this); + } + + return true; + } +} +//! @endcond + +bool GeographicBoundingBox::intersects( + const GeographicExtentNNPtr &other) const { + auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other.get()); + if (!otherExtent) { + return false; + } + return d->intersects(*(otherExtent->d)); +} + +// --------------------------------------------------------------------------- + +GeographicExtentPtr +GeographicBoundingBox::intersection(const GeographicExtentNNPtr &other) const { + auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other.get()); + if (!otherExtent) { + return nullptr; + } + auto ret = d->intersection(*(otherExtent->d)); + if (ret) { + auto bbox = GeographicBoundingBox::create(ret->west_, ret->south_, + ret->east_, ret->north_); + return bbox.as_nullable(); + } + return nullptr; +} + +//! @cond Doxygen_Suppress +std::unique_ptr<GeographicBoundingBox::Private> +GeographicBoundingBox::Private::intersection(const Private &otherExtent) const { + const double W = west_; + const double E = east_; + const double N = north_; + const double S = south_; + const double oW = otherExtent.west_; + const double oE = otherExtent.east_; + const double oN = otherExtent.north_; + const double oS = otherExtent.south_; + + if (N < oS || S > oN) { + return nullptr; + } + + if (W == -180.0 && E == 180.0 && oW > oE) { + return internal::make_unique<Private>(oW, std::max(S, oS), oE, + std::min(N, oN)); + } + + if (oW == -180.0 && oE == 180.0 && W > E) { + return internal::make_unique<Private>(W, std::max(S, oS), E, + std::min(N, oN)); + } + + // Normal bounding box ? + if (W <= E) { + if (oW < oE) { + auto res = internal::make_unique<Private>( + std::max(W, oW), std::max(S, oS), std::min(E, oE), + std::min(N, oN)); + if (res->west_ < res->east_) { + return res; + } + return nullptr; + } + + // Return larger of two parts of the multipolygon + auto inter1 = intersection(Private(oW, oS, 180.0, oN)); + auto inter2 = intersection(Private(-180.0, oS, oE, oN)); + if (!inter1) { + return inter2; + } + if (!inter2) { + return inter1; + } + if (inter1->east_ - inter1->west_ > inter2->east_ - inter2->west_) { + return inter1; + } + return inter2; + // No: crossing antimerian + } else { + if (oW <= oE) { + return otherExtent.intersection(*this); + } + + return internal::make_unique<Private>(std::max(W, oW), std::max(S, oS), + std::min(E, oE), std::min(N, oN)); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct VerticalExtent::Private { + double minimum_{}; + double maximum_{}; + common::UnitOfMeasureNNPtr unit_; + + Private(double minimum, double maximum, + const common::UnitOfMeasureNNPtr &unit) + : minimum_(minimum), maximum_(maximum), unit_(unit) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +VerticalExtent::VerticalExtent(double minimumIn, double maximumIn, + const common::UnitOfMeasureNNPtr &unitIn) + : d(internal::make_unique<Private>(minimumIn, maximumIn, unitIn)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +VerticalExtent::~VerticalExtent() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns the minimum of the vertical extent. + */ +double VerticalExtent::minimumValue() PROJ_CONST_DEFN { return d->minimum_; } + +// --------------------------------------------------------------------------- + +/** \brief Returns the maximum of the vertical extent. + */ +double VerticalExtent::maximumValue() PROJ_CONST_DEFN { return d->maximum_; } + +// --------------------------------------------------------------------------- + +/** \brief Returns the unit of the vertical extent. + */ +common::UnitOfMeasureNNPtr &VerticalExtent::unit() PROJ_CONST_DEFN { + return d->unit_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a VerticalExtent. + * + * @param minimumIn minimum. + * @param maximumIn maximum. + * @param unitIn unit. + * @return a new VerticalExtent. + */ +VerticalExtentNNPtr +VerticalExtent::create(double minimumIn, double maximumIn, + const common::UnitOfMeasureNNPtr &unitIn) { + return VerticalExtent::nn_make_shared<VerticalExtent>(minimumIn, maximumIn, + unitIn); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool VerticalExtent::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion) const { + auto otherExtent = dynamic_cast<const VerticalExtent *>(other); + if (!otherExtent) + return false; + return d->minimum_ == otherExtent->d->minimum_ && + d->maximum_ == otherExtent->d->maximum_ && + d->unit_ == otherExtent->d->unit_; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns whether this extent contains the other one. + */ +bool VerticalExtent::contains(const VerticalExtentNNPtr &other) const { + const double thisUnitToSI = d->unit_->conversionToSI(); + const double otherUnitToSI = other->d->unit_->conversionToSI(); + return d->minimum_ * thisUnitToSI <= other->d->minimum_ * otherUnitToSI && + d->maximum_ * thisUnitToSI >= other->d->maximum_ * otherUnitToSI; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns whether this extent intersects the other one. + */ +bool VerticalExtent::intersects(const VerticalExtentNNPtr &other) const { + const double thisUnitToSI = d->unit_->conversionToSI(); + const double otherUnitToSI = other->d->unit_->conversionToSI(); + return d->minimum_ * thisUnitToSI <= other->d->maximum_ * otherUnitToSI && + d->maximum_ * thisUnitToSI >= other->d->minimum_ * otherUnitToSI; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct TemporalExtent::Private { + std::string start_{}; + std::string stop_{}; + + Private(const std::string &start, const std::string &stop) + : start_(start), stop_(stop) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +TemporalExtent::TemporalExtent(const std::string &startIn, + const std::string &stopIn) + : d(internal::make_unique<Private>(startIn, stopIn)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalExtent::~TemporalExtent() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns the start of the temporal extent. + */ +const std::string &TemporalExtent::start() PROJ_CONST_DEFN { return d->start_; } + +// --------------------------------------------------------------------------- + +/** \brief Returns the end of the temporal extent. + */ +const std::string &TemporalExtent::stop() PROJ_CONST_DEFN { return d->stop_; } + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a TemporalExtent. + * + * @param start start. + * @param stop stop. + * @return a new TemporalExtent. + */ +TemporalExtentNNPtr TemporalExtent::create(const std::string &start, + const std::string &stop) { + return TemporalExtent::nn_make_shared<TemporalExtent>(start, stop); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool TemporalExtent::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion) const { + auto otherExtent = dynamic_cast<const TemporalExtent *>(other); + if (!otherExtent) + return false; + return start() == otherExtent->start() && stop() == otherExtent->stop(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns whether this extent contains the other one. + */ +bool TemporalExtent::contains(const TemporalExtentNNPtr &other) const { + return start() <= other->start() && stop() >= other->stop(); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns whether this extent intersects the other one. + */ +bool TemporalExtent::intersects(const TemporalExtentNNPtr &other) const { + return start() <= other->stop() && stop() >= other->start(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Extent::Private { + optional<std::string> description_{}; + std::vector<GeographicExtentNNPtr> geographicElements_{}; + std::vector<VerticalExtentNNPtr> verticalElements_{}; + std::vector<TemporalExtentNNPtr> temporalElements_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Extent::Extent() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +Extent::Extent(const Extent &other) + : d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +Extent::~Extent() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** Return a textual description of the extent. + * + * @return the description, or empty. + */ +const optional<std::string> &Extent::description() PROJ_CONST_DEFN { + return d->description_; +} + +// --------------------------------------------------------------------------- + +/** Return the geographic element(s) of the extent + * + * @return the geographic element(s), or empty. + */ +const std::vector<GeographicExtentNNPtr> & +Extent::geographicElements() PROJ_CONST_DEFN { + return d->geographicElements_; +} + +// --------------------------------------------------------------------------- + +/** Return the vertical element(s) of the extent + * + * @return the vertical element(s), or empty. + */ +const std::vector<VerticalExtentNNPtr> & +Extent::verticalElements() PROJ_CONST_DEFN { + return d->verticalElements_; +} + +// --------------------------------------------------------------------------- + +/** Return the temporal element(s) of the extent + * + * @return the temporal element(s), or empty. + */ +const std::vector<TemporalExtentNNPtr> & +Extent::temporalElements() PROJ_CONST_DEFN { + return d->temporalElements_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Extent. + * + * @param descriptionIn Textual description, or empty. + * @param geographicElementsIn Geographic element(s), or empty. + * @param verticalElementsIn Vertical element(s), or empty. + * @param temporalElementsIn Temporal element(s), or empty. + * @return a new Extent. + */ +ExtentNNPtr +Extent::create(const optional<std::string> &descriptionIn, + const std::vector<GeographicExtentNNPtr> &geographicElementsIn, + const std::vector<VerticalExtentNNPtr> &verticalElementsIn, + const std::vector<TemporalExtentNNPtr> &temporalElementsIn) { + auto extent = Extent::nn_make_shared<Extent>(); + extent->assignSelf(extent); + extent->d->description_ = descriptionIn; + extent->d->geographicElements_ = geographicElementsIn; + extent->d->verticalElements_ = verticalElementsIn; + extent->d->temporalElements_ = temporalElementsIn; + return extent; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Extent from a bounding box + * + * @param west Western-most coordinate of the limit of the dataset extent (in + * degrees). + * @param south Southern-most coordinate of the limit of the dataset extent (in + * degrees). + * @param east Eastern-most coordinate of the limit of the dataset extent (in + * degrees). + * @param north Northern-most coordinate of the limit of the dataset extent (in + * degrees). + * @param descriptionIn Textual description, or empty. + * @return a new Extent. + */ +ExtentNNPtr +Extent::createFromBBOX(double west, double south, double east, double north, + const util::optional<std::string> &descriptionIn) { + return create( + descriptionIn, + std::vector<GeographicExtentNNPtr>{ + nn_static_pointer_cast<GeographicExtent>( + GeographicBoundingBox::create(west, south, east, north))}, + std::vector<VerticalExtentNNPtr>(), std::vector<TemporalExtentNNPtr>()); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool Extent::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherExtent = dynamic_cast<const Extent *>(other); + bool ret = + (otherExtent && + description().has_value() == otherExtent->description().has_value() && + *description() == *otherExtent->description() && + d->geographicElements_.size() == + otherExtent->d->geographicElements_.size() && + d->verticalElements_.size() == + otherExtent->d->verticalElements_.size() && + d->temporalElements_.size() == + otherExtent->d->temporalElements_.size()); + if (ret) { + for (size_t i = 0; ret && i < d->geographicElements_.size(); ++i) { + ret = d->geographicElements_[i]->_isEquivalentTo( + otherExtent->d->geographicElements_[i].get(), criterion); + } + for (size_t i = 0; ret && i < d->verticalElements_.size(); ++i) { + ret = d->verticalElements_[i]->_isEquivalentTo( + otherExtent->d->verticalElements_[i].get(), criterion); + } + for (size_t i = 0; ret && i < d->temporalElements_.size(); ++i) { + ret = d->temporalElements_[i]->_isEquivalentTo( + otherExtent->d->temporalElements_[i].get(), criterion); + } + } + return ret; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns whether this extent contains the other one. + * + * Behaviour only well specified if each sub-extent category as at most + * one element. + */ +bool Extent::contains(const ExtentNNPtr &other) const { + bool res = true; + if (d->geographicElements_.size() == 1 && + other->d->geographicElements_.size() == 1) { + res = d->geographicElements_[0]->contains( + other->d->geographicElements_[0]); + } + if (res && d->verticalElements_.size() == 1 && + other->d->verticalElements_.size() == 1) { + res = d->verticalElements_[0]->contains(other->d->verticalElements_[0]); + } + if (res && d->temporalElements_.size() == 1 && + other->d->temporalElements_.size() == 1) { + res = d->temporalElements_[0]->contains(other->d->temporalElements_[0]); + } + return res; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns whether this extent intersects the other one. + * + * Behaviour only well specified if each sub-extent category as at most + * one element. + */ +bool Extent::intersects(const ExtentNNPtr &other) const { + bool res = true; + if (d->geographicElements_.size() == 1 && + other->d->geographicElements_.size() == 1) { + res = d->geographicElements_[0]->intersects( + other->d->geographicElements_[0]); + } + if (res && d->verticalElements_.size() == 1 && + other->d->verticalElements_.size() == 1) { + res = + d->verticalElements_[0]->intersects(other->d->verticalElements_[0]); + } + if (res && d->temporalElements_.size() == 1 && + other->d->temporalElements_.size() == 1) { + res = + d->temporalElements_[0]->intersects(other->d->temporalElements_[0]); + } + return res; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the intersection of this extent with another one. + * + * Behaviour only well specified if there is one single GeographicExtent + * in each object. + * Returns nullptr otherwise. + */ +ExtentPtr Extent::intersection(const ExtentNNPtr &other) const { + if (d->geographicElements_.size() == 1 && + other->d->geographicElements_.size() == 1) { + if (contains(other)) { + return other.as_nullable(); + } + auto self = util::nn_static_pointer_cast<Extent>(shared_from_this()); + if (other->contains(self)) { + return self.as_nullable(); + } + auto geogIntersection = d->geographicElements_[0]->intersection( + other->d->geographicElements_[0]); + if (geogIntersection) { + return create(util::optional<std::string>(), + std::vector<GeographicExtentNNPtr>{ + NN_NO_CHECK(geogIntersection)}, + std::vector<VerticalExtentNNPtr>{}, + std::vector<TemporalExtentNNPtr>{}); + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct Identifier::Private { + optional<Citation> authority_{}; + std::string code_{}; + optional<std::string> codeSpace_{}; + optional<std::string> version_{}; + optional<std::string> description_{}; + optional<std::string> uri_{}; + + Private(const std::string &codeIn, const PropertyMap &properties) + : code_(codeIn) { + setProperties(properties); + } + + private: + // cppcheck-suppress functionStatic + void setProperties(const PropertyMap &properties); +}; + +// --------------------------------------------------------------------------- + +void Identifier::Private::setProperties( + const PropertyMap &properties) // throw(InvalidValueTypeException) +{ + { + auto oIter = properties.find(AUTHORITY_KEY); + if (oIter != properties.end()) { + if (auto genVal = + dynamic_cast<const BoxedValue *>(oIter->second.get())) { + if (genVal->type() == BoxedValue::Type::STRING) { + authority_ = Citation(genVal->stringValue()); + } else { + throw InvalidValueTypeException("Invalid value type for " + + AUTHORITY_KEY); + } + } else { + if (auto citation = + dynamic_cast<const Citation *>(oIter->second.get())) { + authority_ = *citation; + } else { + throw InvalidValueTypeException("Invalid value type for " + + AUTHORITY_KEY); + } + } + } + } + + { + auto oIter = properties.find(CODE_KEY); + if (oIter != properties.end()) { + if (auto genVal = + dynamic_cast<const BoxedValue *>(oIter->second.get())) { + if (genVal->type() == BoxedValue::Type::INTEGER) { + code_ = toString(genVal->integerValue()); + } else if (genVal->type() == BoxedValue::Type::STRING) { + code_ = genVal->stringValue(); + } else { + throw InvalidValueTypeException("Invalid value type for " + + CODE_KEY); + } + } else { + throw InvalidValueTypeException("Invalid value type for " + + CODE_KEY); + } + } + } + + { + std::string temp; + if (properties.getStringValue(CODESPACE_KEY, temp)) { + codeSpace_ = temp; + } + } + + { + std::string temp; + if (properties.getStringValue(VERSION_KEY, temp)) { + version_ = temp; + } + } + + { + std::string temp; + if (properties.getStringValue(DESCRIPTION_KEY, temp)) { + description_ = temp; + } + } + + { + std::string temp; + if (properties.getStringValue(URI_KEY, temp)) { + uri_ = temp; + } + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + +Identifier::Identifier(const std::string &codeIn, const PropertyMap &properties) + : d(internal::make_unique<Private>(codeIn, properties)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Identifier::Identifier(const Identifier &other) + : d(internal::make_unique<Private>(*(other.d))) {} + +// --------------------------------------------------------------------------- + +Identifier::~Identifier() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a Identifier. + * + * @param codeIn Alphanumeric value identifying an instance in the codespace + * @param properties See \ref general_properties. + * Generally, the Identifier::CODESPACE_KEY should be set. + * @return a new Identifier. + */ +IdentifierNNPtr Identifier::create(const std::string &codeIn, + const PropertyMap &properties) { + return Identifier::nn_make_shared<Identifier>(codeIn, properties); +} + +// --------------------------------------------------------------------------- + +/** \brief Return a citation for the organization responsible for definition and + * maintenance of the code. + * + * @return the citation for the authority, or empty. + */ +const optional<Citation> &Identifier::authority() PROJ_CONST_DEFN { + return d->authority_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the alphanumeric value identifying an instance in the + * codespace. + * + * e.g. "4326" (for EPSG:4326 WGS 84 GeographicCRS) + * + * @return the code. + */ +const std::string &Identifier::code() PROJ_CONST_DEFN { return d->code_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the organization responsible for definition and maintenance of + * the code. + * + * e.g "EPSG" + * + * @return the authority codespace, or empty. + */ +const optional<std::string> &Identifier::codeSpace() PROJ_CONST_DEFN { + return d->codeSpace_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the version identifier for the namespace. + * + * When appropriate, the edition is identified by the effective date, coded + * using ISO 8601 date format. + * + * @return the version or empty. + */ +const optional<std::string> &Identifier::version() PROJ_CONST_DEFN { + return d->version_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the natural language description of the meaning of the code + * value. + * + * @return the description or empty. + */ +const optional<std::string> &Identifier::description() PROJ_CONST_DEFN { + return d->description_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the URI of the identifier. + * + * @return the URI or empty. + */ +const optional<std::string> &Identifier::uri() PROJ_CONST_DEFN { + return d->uri_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void Identifier::_exportToWKT(WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2; + const std::string &l_code = code(); + const std::string &l_codeSpace = *codeSpace(); + if (!l_codeSpace.empty() && !l_code.empty()) { + if (isWKT2) { + formatter->startNode(WKTConstants::ID, false); + formatter->addQuotedString(l_codeSpace); + try { + (void)std::stoi(l_code); + formatter->add(l_code); + } catch (const std::exception &) { + formatter->addQuotedString(l_code); + } + if (version().has_value()) { + auto l_version = *(version()); + try { + (void)c_locale_stod(l_version); + formatter->add(l_version); + } catch (const std::exception &) { + formatter->addQuotedString(l_version); + } + } + if (authority().has_value() && + *(authority()->title()) != l_codeSpace) { + formatter->startNode(WKTConstants::CITATION, false); + formatter->addQuotedString(*(authority()->title())); + formatter->endNode(); + } + if (uri().has_value()) { + formatter->startNode(WKTConstants::URI, false); + formatter->addQuotedString(*(uri())); + formatter->endNode(); + } + formatter->endNode(); + } else { + formatter->startNode(WKTConstants::AUTHORITY, false); + formatter->addQuotedString(l_codeSpace); + formatter->addQuotedString(l_code); + formatter->endNode(); + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static bool isIgnoredChar(char ch) { + return ch == ' ' || ch == '_' || ch == '-' || ch == '/' || ch == '(' || + ch == ')'; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::string Identifier::canonicalizeName(const std::string &str) { + std::string res; + const char *c_str = str.c_str(); + for (size_t i = 0; c_str[i] != 0; ++i) { + const auto ch = c_str[i]; + if (ch == ' ' && c_str[i + 1] == '+' && c_str[i + 2] == ' ') { + i += 2; + continue; + } + if (ch == '1' && !res.empty() && + !(res.back() >= '0' && res.back() <= '9') && c_str[i + 1] == '9' && + c_str[i + 2] >= '0' && c_str[i + 2] <= '9') { + ++i; + continue; + } + if (!isIgnoredChar(ch)) { + res.push_back(static_cast<char>(::tolower(ch))); + } + } + return res; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns whether two names are considered equivalent. + * + * Two names are equivalent by removing any space, underscore, dash, slash, + * { or } character from them, and comparing in a case insensitive way. + */ +bool Identifier::isEquivalentName(const char *a, const char *b) noexcept { + size_t i = 0; + size_t j = 0; + char lastValidA = 0; + char lastValidB = 0; + while (a[i] != 0 && b[j] != 0) { + const char aCh = a[i]; + const char bCh = b[j]; + if (aCh == ' ' && a[i + 1] == '+' && a[i + 2] == ' ') { + i += 3; + continue; + } + if (bCh == ' ' && b[j + 1] == '+' && b[j + 2] == ' ') { + j += 3; + continue; + } + if (isIgnoredChar(aCh)) { + ++i; + continue; + } + if (isIgnoredChar(bCh)) { + ++j; + continue; + } + if (aCh == '1' && !(lastValidA >= '0' && lastValidA <= '9') && + a[i + 1] == '9' && a[i + 2] >= '0' && a[i + 2] <= '9') { + i += 2; + lastValidA = '9'; + continue; + } + if (bCh == '1' && !(lastValidB >= '0' && lastValidB <= '9') && + b[j + 1] == '9' && b[j + 2] >= '0' && b[j + 2] <= '9') { + j += 2; + lastValidB = '9'; + continue; + } + if (::tolower(aCh) != ::tolower(bCh)) { + return false; + } + lastValidA = aCh; + lastValidB = bCh; + ++i; + ++j; + } + while (a[i] != 0 && isIgnoredChar(a[i])) { + ++i; + } + while (b[j] != 0 && isIgnoredChar(b[j])) { + ++j; + } + return a[i] == b[j]; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct PositionalAccuracy::Private { + std::string value_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +PositionalAccuracy::PositionalAccuracy(const std::string &valueIn) + : d(internal::make_unique<Private>()) { + d->value_ = valueIn; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PositionalAccuracy::~PositionalAccuracy() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the value of the positional accuracy. + */ +const std::string &PositionalAccuracy::value() PROJ_CONST_DEFN { + return d->value_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a PositionalAccuracy. + * + * @param valueIn positional accuracy value. + * @return a new PositionalAccuracy. + */ +PositionalAccuracyNNPtr PositionalAccuracy::create(const std::string &valueIn) { + return PositionalAccuracy::nn_make_shared<PositionalAccuracy>(valueIn); +} + +} // namespace metadata +NS_PROJ_END diff --git a/src/pj_ctx.c b/src/pj_ctx.c index d77bc88a..6626d5ee 100644 --- a/src/pj_ctx.c +++ b/src/pj_ctx.c @@ -29,6 +29,7 @@ #include <stdlib.h> #include <string.h> +#include "proj_internal.h" #include "projects.h" static projCtx_t default_context; @@ -83,6 +84,7 @@ projCtx pj_get_default_ctx() default_context.logger = pj_stderr_logger; default_context.app_data = NULL; default_context.fileapi = pj_get_default_fileapi(); + default_context.cpp_context = NULL; if( getenv("PROJ_DEBUG") != NULL ) { @@ -111,6 +113,7 @@ projCtx pj_ctx_alloc() return 0; memcpy( ctx, pj_get_default_ctx(), sizeof(projCtx_t) ); ctx->last_errno = 0; + ctx->cpp_context = NULL; return ctx; } @@ -122,6 +125,7 @@ projCtx pj_ctx_alloc() void pj_ctx_free( projCtx ctx ) { + proj_context_delete_cpp_context( ctx->cpp_context ); pj_dealloc( ctx ); } diff --git a/src/pj_ellps.c b/src/pj_ellps.c index 5128d47e..08b81c3f 100644 --- a/src/pj_ellps.c +++ b/src/pj_ellps.c @@ -12,8 +12,8 @@ pj_ellps[] = { {"GRS80", "a=6378137.0", "rf=298.257222101", "GRS 1980(IUGG, 1980)"}, {"IAU76", "a=6378140.0", "rf=298.257", "IAU 1976"}, {"airy", "a=6377563.396", "b=6356256.910", "Airy 1830"}, -{"APL4.9", "a=6378137.0.", "rf=298.25", "Appl. Physics. 1965"}, -{"NWL9D", "a=6378145.0.", "rf=298.25", "Naval Weapons Lab., 1965"}, +{"APL4.9", "a=6378137.0", "rf=298.25", "Appl. Physics. 1965"}, +{"NWL9D", "a=6378145.0", "rf=298.25", "Naval Weapons Lab., 1965"}, {"mod_airy", "a=6377340.189", "b=6356034.446", "Modified Airy"}, {"andrae", "a=6377104.43", "rf=300.0", "Andrae 1876 (Den., Iclnd.)"}, {"danish", "a=6377019.2563", "rf=300.0", "Andrae 1876 (Denmark, Iceland)"}, diff --git a/src/pj_units.c b/src/pj_units.c index de43f986..877758a3 100644 --- a/src/pj_units.c +++ b/src/pj_units.c @@ -40,3 +40,19 @@ const PJ_UNITS *proj_list_units() { return pj_units; } + +/* M_PI / 200 */ +#define GRAD_TO_RAD 0.015707963267948967 + +const struct PJ_UNITS +pj_angular_units[] = { + {"rad", "1.0", "Radian", 1.0}, + {"deg", "0.017453292519943296", "Degree", DEG_TO_RAD}, + {"grad", "0.015707963267948967", "Grad", GRAD_TO_RAD}, + {NULL, NULL, NULL, 0.0} +}; + +const PJ_UNITS *proj_list_angular_units() +{ + return pj_angular_units; +} diff --git a/src/proj.def b/src/proj.def deleted file mode 100644 index 80db7d25..00000000 --- a/src/proj.def +++ /dev/null @@ -1,156 +0,0 @@ -VERSION 1.2 -EXPORTS - pj_init @1 - pj_fwd @2 - pj_inv @3 - pj_free @4 - pj_transform @5 - pj_geocentric_to_geodetic @6 - pj_geodetic_to_geocentric @7 - pj_deallocate_grids @8 - pj_init_plus @9 - pj_latlong_from_proj @10 - pj_is_latlong @11 - pj_get_errno_ref @12 - pj_set_finder @13 - pj_strerrno @14 - pj_errno @15 - pj_get_def @16 - pj_dalloc @17 - pj_is_geocent @18 - pj_get_release @19 - pj_malloc @20 - pj_pr_list @21 - pj_compare_datums @22 - pj_apply_gridshift @23 - pj_datum_transform @24 - pj_set_searchpath @25 - dmstor @26 - pj_get_datums_ref @28 - rtodms @32 - set_rtodms @33 - pj_factors @34 - mk_cheby @35 - adjlon @36 - pj_param @37 - pj_ell_set @38 - pj_mkparam @39 - pj_init_ctx @40 - pj_init_plus_ctx @41 - pj_get_default_ctx @42 - pj_get_ctx @43 - pj_set_ctx @44 - pj_ctx_alloc @45 - pj_ctx_free @46 - pj_ctx_get_errno @47 - pj_ctx_set_errno @48 - pj_ctx_set_debug @49 - pj_ctx_set_logger @50 - pj_ctx_set_app_data @51 - pj_ctx_get_app_data @52 - pj_log @53 - pj_clear_initcache @54 - geod_init @55 - geod_lineinit @56 - geod_genposition @57 - geod_position @58 - geod_gendirect @59 - geod_direct @60 - geod_geninverse @61 - geod_inverse @62 - geod_polygonarea @63 - pj_get_spheroid_defn @64 - pj_get_default_fileapi @65 - pj_ctx_set_fileapi @66 - pj_ctx_get_fileapi @67 - pj_ctx_fopen @68 - pj_ctx_fread @69 - pj_ctx_ftell @70 - pj_ctx_fclose @71 - pj_open_lib @72 - pj_atof @73 - pj_strtod @74 - pj_fwd3d @75 - pj_inv3d @76 - geod_gensetdistance @77 - geod_setdistance @78 - geod_gendirectline @79 - geod_directline @80 - geod_inverseline @81 - geod_polygon_init @82 - geod_polygon_addedge @83 - geod_polygon_addpoint @84 - geod_polygon_compute @85 - geod_polygon_testedge @86 - geod_polygon_testpoint @87 - geod_polygon_clear @88 - - pj_find_file @89 - pj_chomp @90 - pj_shrink @91 - pj_approx_2D_trans @92 - pj_approx_3D_trans @93 - pj_has_inverse @94 - pj_param_exists @95 - - proj_create @96 - proj_create_argv @97 - proj_create_crs_to_crs @98 - proj_destroy @99 - - proj_trans @100 - proj_trans_array @101 - proj_trans_generic @102 - proj_roundtrip @103 - - proj_coord @104 - proj_coord_error @105 - - proj_errno @106 - proj_errno_set @107 - proj_errno_reset @108 - proj_errno_restore @109 - proj_context_errno_set @110 - - proj_context_create @111 - proj_context_set @112 - proj_context_inherit @113 - proj_context_destroy @114 - - proj_lp_dist @115 - proj_lpz_dist @116 - proj_xy_dist @117 - proj_xyz_dist @118 - - proj_log_level @119 - proj_log_func @120 - proj_log_error @121 - proj_log_debug @122 - proj_log_trace @123 - - proj_info @124 - proj_pj_info @125 - proj_grid_info @126 - proj_init_info @127 - - proj_torad @128 - proj_todeg @129 - proj_rtodms @131 - proj_dmstor @132 - - proj_factors @133 - - proj_list_operations @134 - proj_list_ellps @135 - proj_list_units @136 - proj_list_prime_meridians @137 - - proj_angular_input @138 - proj_angular_output @139 - - proj_geod @140 - proj_context_errno @141 - - pj_isnan @142 - - proj_errno_string @143 @@ -123,18 +123,42 @@ #error proj.h must be included before proj_api.h #endif +#ifdef PROJ_RENAME_SYMBOLS +#include "proj_symbol_rename.h" +#endif + #ifndef PROJ_H #define PROJ_H #ifdef __cplusplus extern "C" { #endif +/** + * \file proj.h + * + * C API new generation + */ + +/*! @cond Doxygen_Suppress */ + +#ifndef PROJ_DLL +#ifdef PROJ_MSVC_DLL_EXPORT +#define PROJ_DLL __declspec(dllexport) +#elif defined(PROJ_MSVC_DLL_IMPORT) +#define PROJ_DLL __declspec(dllimport) +#elif defined(__GNUC__) +#define PROJ_DLL __attribute__ ((visibility("default"))) +#else +#define PROJ_DLL +#endif +#endif + /* The version numbers should be updated with every release! **/ #define PROJ_VERSION_MAJOR 6 #define PROJ_VERSION_MINOR 0 #define PROJ_VERSION_PATCH 0 -extern char const pj_release[]; /* global release id string */ +extern char const PROJ_DLL pj_release[]; /* global release id string */ /* first forward declare everything needed */ @@ -308,15 +332,15 @@ typedef struct projCtx_t PJ_CONTEXT; /* Functionality for handling thread contexts */ #define PJ_DEFAULT_CTX 0 -PJ_CONTEXT *proj_context_create (void); -PJ_CONTEXT *proj_context_destroy (PJ_CONTEXT *ctx); +PJ_CONTEXT PROJ_DLL *proj_context_create (void); +PJ_CONTEXT PROJ_DLL *proj_context_destroy (PJ_CONTEXT *ctx); /* Manage the transformation definition object PJ */ -PJ *proj_create (PJ_CONTEXT *ctx, const char *definition); -PJ *proj_create_argv (PJ_CONTEXT *ctx, int argc, char **argv); -PJ *proj_create_crs_to_crs(PJ_CONTEXT *ctx, const char *srid_from, const char *srid_to, PJ_AREA *area); -PJ *proj_destroy (PJ *P); +PJ PROJ_DLL *proj_create (PJ_CONTEXT *ctx, const char *definition); +PJ PROJ_DLL *proj_create_argv (PJ_CONTEXT *ctx, int argc, char **argv); +PJ PROJ_DLL *proj_create_crs_to_crs(PJ_CONTEXT *ctx, const char *srid_from, const char *srid_to, PJ_AREA *area); +PJ PROJ_DLL *proj_destroy (PJ *P); /* Setter-functions for the opaque PJ_AREA struct */ /* Uncomment these when implementing support for area-based transformations. @@ -333,13 +357,13 @@ enum PJ_DIRECTION { typedef enum PJ_DIRECTION PJ_DIRECTION; -int proj_angular_input (PJ *P, enum PJ_DIRECTION dir); -int proj_angular_output (PJ *P, enum PJ_DIRECTION dir); +int PROJ_DLL proj_angular_input (PJ *P, enum PJ_DIRECTION dir); +int PROJ_DLL proj_angular_output (PJ *P, enum PJ_DIRECTION dir); -PJ_COORD proj_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coord); -int proj_trans_array (PJ *P, PJ_DIRECTION direction, size_t n, PJ_COORD *coord); -size_t proj_trans_generic ( +PJ_COORD PROJ_DLL proj_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coord); +int PROJ_DLL proj_trans_array (PJ *P, PJ_DIRECTION direction, size_t n, PJ_COORD *coord); +size_t PROJ_DLL proj_trans_generic ( PJ *P, PJ_DIRECTION direction, double *x, size_t sx, size_t nx, @@ -350,62 +374,1058 @@ size_t proj_trans_generic ( /* Initializers */ -PJ_COORD proj_coord (double x, double y, double z, double t); +PJ_COORD PROJ_DLL proj_coord (double x, double y, double z, double t); /* Measure internal consistency - in forward or inverse direction */ -double proj_roundtrip (PJ *P, PJ_DIRECTION direction, int n, PJ_COORD *coord); +double PROJ_DLL proj_roundtrip (PJ *P, PJ_DIRECTION direction, int n, PJ_COORD *coord); /* Geodesic distance between two points with angular 2D coordinates */ -double proj_lp_dist (const PJ *P, PJ_COORD a, PJ_COORD b); +double PROJ_DLL proj_lp_dist (const PJ *P, PJ_COORD a, PJ_COORD b); /* The geodesic distance AND the vertical offset */ -double proj_lpz_dist (const PJ *P, PJ_COORD a, PJ_COORD b); +double PROJ_DLL proj_lpz_dist (const PJ *P, PJ_COORD a, PJ_COORD b); /* Euclidean distance between two points with linear 2D coordinates */ -double proj_xy_dist (PJ_COORD a, PJ_COORD b); +double PROJ_DLL proj_xy_dist (PJ_COORD a, PJ_COORD b); /* Euclidean distance between two points with linear 3D coordinates */ -double proj_xyz_dist (PJ_COORD a, PJ_COORD b); +double PROJ_DLL proj_xyz_dist (PJ_COORD a, PJ_COORD b); /* Geodesic distance (in meter) + fwd and rev azimuth between two points on the ellipsoid */ -PJ_COORD proj_geod (const PJ *P, PJ_COORD a, PJ_COORD b); +PJ_COORD PROJ_DLL proj_geod (const PJ *P, PJ_COORD a, PJ_COORD b); /* Set or read error level */ -int proj_context_errno (PJ_CONTEXT *ctx); -int proj_errno (const PJ *P); -int proj_errno_set (const PJ *P, int err); -int proj_errno_reset (const PJ *P); -int proj_errno_restore (const PJ *P, int err); -const char* proj_errno_string (int err); +int PROJ_DLL proj_context_errno (PJ_CONTEXT *ctx); +int PROJ_DLL proj_errno (const PJ *P); +int PROJ_DLL proj_errno_set (const PJ *P, int err); +int PROJ_DLL proj_errno_reset (const PJ *P); +int PROJ_DLL proj_errno_restore (const PJ *P, int err); +const char PROJ_DLL * proj_errno_string (int err); -PJ_LOG_LEVEL proj_log_level (PJ_CONTEXT *ctx, PJ_LOG_LEVEL log_level); -void proj_log_func (PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf); +PJ_LOG_LEVEL PROJ_DLL proj_log_level (PJ_CONTEXT *ctx, PJ_LOG_LEVEL log_level); +void PROJ_DLL proj_log_func (PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf); /* Scaling and angular distortion factors */ -PJ_FACTORS proj_factors(PJ *P, PJ_COORD lp); +PJ_FACTORS PROJ_DLL proj_factors(PJ *P, PJ_COORD lp); /* Info functions - get information about various PROJ.4 entities */ -PJ_INFO proj_info(void); -PJ_PROJ_INFO proj_pj_info(PJ *P); -PJ_GRID_INFO proj_grid_info(const char *gridname); -PJ_INIT_INFO proj_init_info(const char *initname); +PJ_INFO PROJ_DLL proj_info(void); +PJ_PROJ_INFO PROJ_DLL proj_pj_info(PJ *P); +PJ_GRID_INFO PROJ_DLL proj_grid_info(const char *gridname); +PJ_INIT_INFO PROJ_DLL proj_init_info(const char *initname); /* List functions: */ /* Get lists of operations, ellipsoids, units and prime meridians. */ -const PJ_OPERATIONS *proj_list_operations(void); -const PJ_ELLPS *proj_list_ellps(void); -const PJ_UNITS *proj_list_units(void); -const PJ_PRIME_MERIDIANS *proj_list_prime_meridians(void); +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); +const PJ_PRIME_MERIDIANS PROJ_DLL *proj_list_prime_meridians(void); /* These are trivial, and while occasionally useful in real code, primarily here to */ /* simplify demo code, and in acknowledgement of the proj-internal discrepancy between */ /* angular units expected by classical proj, and by Charles Karney's geodesics subsystem */ -double proj_torad (double angle_in_degrees); -double proj_todeg (double angle_in_radians); +double PROJ_DLL proj_torad (double angle_in_degrees); +double PROJ_DLL proj_todeg (double angle_in_radians); + +double PROJ_DLL proj_dmstor(const char *is, char **rs); +char PROJ_DLL * proj_rtodms(char *s, double r, int pos, int neg); + +/*! @endcond */ + +/* ------------------------------------------------------------------------- */ +/* Binding in C of C++ API */ +/* ------------------------------------------------------------------------- */ + +/*! @cond Doxygen_Suppress */ +typedef struct PJ_OBJ PJ_OBJ; +/*! @endcond */ + +/*! @cond Doxygen_Suppress */ +typedef struct PJ_OBJ_LIST PJ_OBJ_LIST; +/*! @endcond */ + +int PROJ_DLL proj_context_set_database_path(PJ_CONTEXT *ctx, + const char *dbPath, + const char *const *auxDbPaths, + const char* const *options); + +const char PROJ_DLL *proj_context_get_database_path(PJ_CONTEXT *ctx); + +/** \brief Guessed WKT "dialect". */ +typedef enum +{ + /** \ref WKT2_2018 */ + PJ_GUESSED_WKT2_2018, + + /** \ref WKT2_2015 */ + PJ_GUESSED_WKT2_2015, + + /** \ref WKT1 */ + PJ_GUESSED_WKT1_GDAL, + + /** ESRI variant of WKT1 */ + PJ_GUESSED_WKT1_ESRI, + + /** Not WKT / unrecognized */ + PJ_GUESSED_NOT_WKT +} PJ_GUESSED_WKT_DIALECT; + +PJ_GUESSED_WKT_DIALECT PROJ_DLL proj_context_guess_wkt_dialect(PJ_CONTEXT *ctx, + const char *wkt); + +PJ_OBJ PROJ_DLL *proj_obj_create_from_user_input(PJ_CONTEXT *ctx, + const char *text, + const char* const *options); + +PJ_OBJ PROJ_DLL *proj_obj_create_from_wkt(PJ_CONTEXT *ctx, const char *wkt, + const char* const *options); + +PJ_OBJ PROJ_DLL *proj_obj_create_from_proj_string(PJ_CONTEXT *ctx, + const char *proj_string, + const char* const *options); + +/** \brief Object category. */ +typedef enum +{ + PJ_OBJ_CATEGORY_ELLIPSOID, + PJ_OBJ_CATEGORY_DATUM, + PJ_OBJ_CATEGORY_CRS, + PJ_OBJ_CATEGORY_COORDINATE_OPERATION +} PJ_OBJ_CATEGORY; + +PJ_OBJ PROJ_DLL *proj_obj_create_from_database(PJ_CONTEXT *ctx, + const char *auth_name, + const char *code, + PJ_OBJ_CATEGORY category, + int usePROJAlternativeGridNames, + const char* const *options); + +void PROJ_DLL proj_obj_unref(PJ_OBJ *obj); + +/** \brief Object type. */ +typedef enum +{ + PJ_OBJ_TYPE_ELLIPSOID, + + PJ_OBJ_TYPE_GEODETIC_REFERENCE_FRAME, + PJ_OBJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME, + PJ_OBJ_TYPE_VERTICAL_REFERENCE_FRAME, + PJ_OBJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME, + PJ_OBJ_TYPE_DATUM_ENSEMBLE, + + /** Abstract type, not returned by proj_obj_get_type() */ + PJ_OBJ_TYPE_CRS, + + PJ_OBJ_TYPE_GEODETIC_CRS, + PJ_OBJ_TYPE_GEOCENTRIC_CRS, + + /** proj_obj_get_type() will never return that type, but + * PJ_OBJ_TYPE_GEOGRAPHIC_2D_CRS or PJ_OBJ_TYPE_GEOGRAPHIC_3D_CRS. */ + PJ_OBJ_TYPE_GEOGRAPHIC_CRS, + + PJ_OBJ_TYPE_GEOGRAPHIC_2D_CRS, + PJ_OBJ_TYPE_GEOGRAPHIC_3D_CRS, + PJ_OBJ_TYPE_VERTICAL_CRS, + PJ_OBJ_TYPE_PROJECTED_CRS, + PJ_OBJ_TYPE_COMPOUND_CRS, + PJ_OBJ_TYPE_TEMPORAL_CRS, + PJ_OBJ_TYPE_BOUND_CRS, + PJ_OBJ_TYPE_OTHER_CRS, + + PJ_OBJ_TYPE_CONVERSION, + PJ_OBJ_TYPE_TRANSFORMATION, + PJ_OBJ_TYPE_CONCATENATED_OPERATION, + PJ_OBJ_TYPE_OTHER_COORDINATE_OPERATION, + + PJ_OBJ_TYPE_UNKNOWN +} PJ_OBJ_TYPE; + +PJ_OBJ_LIST PROJ_DLL *proj_obj_create_from_name(PJ_CONTEXT *ctx, + const char *auth_name, + const char *searchedName, + const PJ_OBJ_TYPE* types, + size_t typesCount, + int approximateMatch, + size_t limitResultCount, + const char* const *options); + +PJ_OBJ PROJ_DLL *proj_obj_create_geographic_crs( + PJ_CONTEXT *ctx, + const char *geogName, + const char *datumName, + const char *ellipsoidName, + double semiMajorMetre, double invFlattening, + const char *primeMeridianName, + double primeMeridianOffset, + const char *angularUnits, + double angularUnitsConv, + int latLongOrder); + +/* BEGIN: Generated by scripts/create_c_api_projections.py*/ +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_UTM( + PJ_OBJ* geodetic_crs, const char* crs_name, + int zone, + int north +); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_TransverseMercator( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double scale, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_GaussSchreiberTransverseMercator( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double scale, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_TransverseMercatorSouthOriented( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double scale, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_TwoPointEquidistant( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeFirstPoint, + double longitudeFirstPoint, + double latitudeSecondPoint, + double longitudeSeconPoint, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_TunisiaMappingGrid( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_AlbersEqualArea( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeFalseOrigin, + double longitudeFalseOrigin, + double latitudeFirstParallel, + double latitudeSecondParallel, + double eastingFalseOrigin, + double northingFalseOrigin, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_LambertConicConformal_1SP( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double scale, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_LambertConicConformal_2SP( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeFalseOrigin, + double longitudeFalseOrigin, + double latitudeFirstParallel, + double latitudeSecondParallel, + double eastingFalseOrigin, + double northingFalseOrigin, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_LambertConicConformal_2SP_Michigan( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeFalseOrigin, + double longitudeFalseOrigin, + double latitudeFirstParallel, + double latitudeSecondParallel, + double eastingFalseOrigin, + double northingFalseOrigin, + double ellipsoidScalingFactor, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_LambertConicConformal_2SP_Belgium( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeFalseOrigin, + double longitudeFalseOrigin, + double latitudeFirstParallel, + double latitudeSecondParallel, + double eastingFalseOrigin, + double northingFalseOrigin, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_AzimuthalEquidistant( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeNatOrigin, + double longitudeNatOrigin, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_GuamProjection( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeNatOrigin, + double longitudeNatOrigin, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_Bonne( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeNatOrigin, + double longitudeNatOrigin, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_LambertCylindricalEqualAreaSpherical( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeFirstParallel, + double longitudeNatOrigin, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_LambertCylindricalEqualArea( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeFirstParallel, + double longitudeNatOrigin, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_CassiniSoldner( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_EquidistantConic( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double latitudeFirstParallel, + double latitudeSecondParallel, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_EckertI( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_EckertII( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_EckertIII( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_EckertIV( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_EckertV( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_EckertVI( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_EquidistantCylindrical( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeFirstParallel, + double longitudeNatOrigin, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_EquidistantCylindricalSpherical( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeFirstParallel, + double longitudeNatOrigin, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_Gall( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_GoodeHomolosine( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_InterruptedGoodeHomolosine( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_GeostationarySatelliteSweepX( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double height, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_GeostationarySatelliteSweepY( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double height, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_Gnomonic( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_HotineObliqueMercatorVariantA( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeProjectionCentre, + double longitudeProjectionCentre, + double azimuthInitialLine, + double angleFromRectifiedToSkrewGrid, + double scale, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_HotineObliqueMercatorVariantB( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeProjectionCentre, + double longitudeProjectionCentre, + double azimuthInitialLine, + double angleFromRectifiedToSkrewGrid, + double scale, + double eastingProjectionCentre, + double northingProjectionCentre, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_HotineObliqueMercatorTwoPointNaturalOrigin( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeProjectionCentre, + double latitudePoint1, + double longitudePoint1, + double latitudePoint2, + double longitudePoint2, + double scale, + double eastingProjectionCentre, + double northingProjectionCentre, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_InternationalMapWorldPolyconic( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double latitudeFirstParallel, + double latitudeSecondParallel, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_KrovakNorthOriented( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeProjectionCentre, + double longitudeOfOrigin, + double colatitudeConeAxis, + double latitudePseudoStandardParallel, + double scaleFactorPseudoStandardParallel, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_Krovak( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeProjectionCentre, + double longitudeOfOrigin, + double colatitudeConeAxis, + double latitudePseudoStandardParallel, + double scaleFactorPseudoStandardParallel, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_LambertAzimuthalEqualArea( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeNatOrigin, + double longitudeNatOrigin, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_MillerCylindrical( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_MercatorVariantA( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double scale, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_MercatorVariantB( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeFirstParallel, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_PopularVisualisationPseudoMercator( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_Mollweide( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_NewZealandMappingGrid( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_ObliqueStereographic( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double scale, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_Orthographic( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_AmericanPolyconic( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_PolarStereographicVariantA( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double scale, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_PolarStereographicVariantB( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeStandardParallel, + double longitudeOfOrigin, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_Robinson( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_Sinusoidal( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_Stereographic( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double scale, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_VanDerGrinten( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_WagnerI( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_WagnerII( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_WagnerIII( + PJ_OBJ* geodetic_crs, const char* crs_name, + double latitudeTrueScale, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_WagnerIV( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_WagnerV( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_WagnerVI( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_WagnerVII( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_QuadrilateralizedSphericalCube( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLat, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_SphericalCrossTrackHeight( + PJ_OBJ* geodetic_crs, const char* crs_name, + double pegPointLat, + double pegPointLong, + double pegPointHeading, + double pegPointHeight, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +PJ_OBJ PROJ_DLL *proj_obj_create_projected_crs_EqualEarth( + PJ_OBJ* geodetic_crs, const char* crs_name, + double centerLong, + double falseEasting, + double falseNorthing, + const char* angUnitName, double angUnitConvFactor, + const char* linearUnitName, double linearUnitConvFactor); + +/* END: Generated by scripts/create_c_api_projections.py*/ + +PJ_OBJ_TYPE PROJ_DLL proj_obj_get_type(PJ_OBJ *obj); + +int PROJ_DLL proj_obj_is_deprecated(PJ_OBJ *obj); + +/** Comparison criterion. */ +typedef enum +{ + /** All properties are identical. */ + PJ_COMP_STRICT, + + /** The objects are equivalent for the purpose of coordinate + * operations. They can differ by the name of their objects, + * identifiers, other metadata. + * Parameters may be expressed in different units, provided that the + * value is (with some tolerance) the same once expressed in a + * common unit. + */ + PJ_COMP_EQUIVALENT, + + /** Same as EQUIVALENT, relaxed with an exception that the axis order + * of the base CRS of a DerivedCRS/ProjectedCRS or the axis order of + * a GeographicCRS is ignored. Only to be used + * with DerivedCRS/ProjectedCRS/GeographicCRS */ + PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, +} PJ_COMPARISON_CRITERION; + +int PROJ_DLL proj_obj_is_equivalent_to(PJ_OBJ *obj, PJ_OBJ* other, + PJ_COMPARISON_CRITERION criterion); + +int PROJ_DLL proj_obj_is_crs(PJ_OBJ *obj); + +const char PROJ_DLL* proj_obj_get_name(PJ_OBJ *obj); + +const char PROJ_DLL* proj_obj_get_id_auth_name(PJ_OBJ *obj, int index); + +const char PROJ_DLL* proj_obj_get_id_code(PJ_OBJ *obj, int index); + +int PROJ_DLL proj_obj_get_area_of_use(PJ_OBJ *obj, + double* p_west_lon, + double* p_south_lat, + double* p_east_lon, + double* p_north_lat, + const char **p_area_name); + +/** \brief WKT version. */ +typedef enum +{ + /** cf osgeo::proj::io::WKTFormatter::Convention::WKT2 */ + PJ_WKT2_2015, + /** cf osgeo::proj::io::WKTFormatter::Convention::WKT2_SIMPLIFIED */ + PJ_WKT2_2015_SIMPLIFIED, + /** cf osgeo::proj::io::WKTFormatter::Convention::WKT2_2018 */ + PJ_WKT2_2018, + /** cf osgeo::proj::io::WKTFormatter::Convention::WKT2_2018_SIMPLIFIED */ + PJ_WKT2_2018_SIMPLIFIED, + /** cf osgeo::proj::io::WKTFormatter::Convention::WKT1_GDAL */ + PJ_WKT1_GDAL, + /** cf osgeo::proj::io::WKTFormatter::Convention::WKT1_ESRI */ + PJ_WKT1_ESRI +} PJ_WKT_TYPE; + +const char PROJ_DLL* proj_obj_as_wkt(PJ_OBJ *obj, PJ_WKT_TYPE type, + const char* const *options); + +/** \brief PROJ string version. */ +typedef enum +{ + /** cf osgeo::proj::io::PROJStringFormatter::Convention::PROJ_5 */ + PJ_PROJ_5, + /** cf osgeo::proj::io::PROJStringFormatter::Convention::PROJ_4 */ + PJ_PROJ_4 +} PJ_PROJ_STRING_TYPE; + +const char PROJ_DLL* proj_obj_as_proj_string(PJ_OBJ *obj, + PJ_PROJ_STRING_TYPE type, + const char* const *options); + +PJ_OBJ PROJ_DLL *proj_obj_get_source_crs(PJ_OBJ *obj); + +PJ_OBJ PROJ_DLL *proj_obj_get_target_crs(PJ_OBJ *obj); + +PJ_OBJ_LIST PROJ_DLL *proj_obj_identify(PJ_OBJ* obj, + const char *auth_name, + const char* const *options, + int **confidence); + +void PROJ_DLL proj_free_int_list(int* list); + +/* ------------------------------------------------------------------------- */ + +/** \brief Type representing a NULL terminated list of NUL-terminate strings. */ +typedef char **PROJ_STRING_LIST; + +PROJ_STRING_LIST PROJ_DLL proj_get_authorities_from_database(PJ_CONTEXT *ctx); + +PROJ_STRING_LIST PROJ_DLL proj_get_codes_from_database(PJ_CONTEXT *ctx, + const char *auth_name, + PJ_OBJ_TYPE type, + int allow_deprecated); + +void PROJ_DLL proj_free_string_list(PROJ_STRING_LIST list); + +/* ------------------------------------------------------------------------- */ + + +/*! @cond Doxygen_Suppress */ +typedef struct PJ_OPERATION_FACTORY_CONTEXT PJ_OPERATION_FACTORY_CONTEXT; +/*! @endcond */ + +PJ_OPERATION_FACTORY_CONTEXT PROJ_DLL *proj_create_operation_factory_context( + PJ_CONTEXT *ctx, + const char *authority); + +void PROJ_DLL proj_operation_factory_context_unref( + PJ_OPERATION_FACTORY_CONTEXT *ctxt); + +void PROJ_DLL proj_operation_factory_context_set_desired_accuracy( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, + double accuracy); + +void PROJ_DLL proj_operation_factory_context_set_area_of_interest( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, + double west_lon, + double south_lat, + double east_lon, + double north_lat); + +/** Specify how source and target CRS extent should be used to restrict + * candidate operations (only taken into account if no explicit area of + * interest is specified. */ +typedef enum +{ + /** Ignore CRS extent */ + PJ_CRS_EXTENT_NONE, + + /** Test coordinate operation extent against both CRS extent. */ + PJ_CRS_EXTENT_BOTH, + + /** Test coordinate operation extent against the intersection of both + CRS extent. */ + PJ_CRS_EXTENT_INTERSECTION, + + /** Test coordinate operation against the smallest of both CRS extent. */ + PJ_CRS_EXTENT_SMALLEST +} PROJ_CRS_EXTENT_USE; + +void PROJ_DLL proj_operation_factory_context_set_crs_extent_use( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, + PROJ_CRS_EXTENT_USE use); + +/** Spatial criterion to restrict candiate operations. */ +typedef enum { + /** The area of validity of transforms should strictly contain the + * are of interest. */ + PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT, + + /** The area of validity of transforms should at least intersect the + * area of interest. */ + PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION +} PROJ_SPATIAL_CRITERION; + +void PROJ_DLL proj_operation_factory_context_set_spatial_criterion( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, + PROJ_SPATIAL_CRITERION criterion); + + +/** Describe how grid availability is used. */ +typedef enum { + /** Grid availability is only used for sorting results. Operations + * where some grids are missing will be sorted last. */ + PROJ_GRID_AVAILABILITY_USED_FOR_SORTING, + + /** Completely discard an operation if a required grid is missing. */ + PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID, + + /** Ignore grid availability at all. Results will be presented as if + * all grids were available. */ + PROJ_GRID_AVAILABILITY_IGNORED, +} PROJ_GRID_AVAILABILITY_USE; + +void PROJ_DLL proj_operation_factory_context_set_grid_availability_use( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, + PROJ_GRID_AVAILABILITY_USE use); + +void PROJ_DLL proj_operation_factory_context_set_use_proj_alternative_grid_names( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, + int usePROJNames); + +void PROJ_DLL proj_operation_factory_context_set_allow_use_intermediate_crs( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, int allow); + +void PROJ_DLL proj_operation_factory_context_set_allowed_intermediate_crs( + PJ_OPERATION_FACTORY_CONTEXT *ctxt, + const char* const *list_of_auth_name_codes); + +/* ------------------------------------------------------------------------- */ + + +PJ_OBJ_LIST PROJ_DLL *proj_obj_create_operations( + PJ_OBJ *source_crs, + PJ_OBJ *target_crs, + PJ_OPERATION_FACTORY_CONTEXT *operationContext); + +int PROJ_DLL proj_obj_list_get_count(PJ_OBJ_LIST *result); + +PJ_OBJ PROJ_DLL *proj_obj_list_get(PJ_OBJ_LIST *result, + int index); + +void PROJ_DLL proj_obj_list_unref(PJ_OBJ_LIST *result); + +/* ------------------------------------------------------------------------- */ + +PJ_OBJ PROJ_DLL *proj_obj_crs_get_geodetic_crs(PJ_OBJ *crs); + +PJ_OBJ PROJ_DLL *proj_obj_crs_get_horizontal_datum(PJ_OBJ *crs); + +PJ_OBJ PROJ_DLL *proj_obj_crs_get_sub_crs(PJ_OBJ *crs, int index); + +PJ_OBJ PROJ_DLL *proj_obj_crs_create_bound_crs_to_WGS84(PJ_OBJ *crs); + +PJ_OBJ PROJ_DLL *proj_obj_get_ellipsoid(PJ_OBJ *obj); + +int PROJ_DLL proj_obj_ellipsoid_get_parameters(PJ_OBJ *ellipsoid, + double *pSemiMajorMetre, + double *pSemiMinorMetre, + int *pIsSemiMinorComputed, + double *pInverseFlattening); + +PJ_OBJ PROJ_DLL *proj_obj_get_prime_meridian(PJ_OBJ *obj); + +int PROJ_DLL proj_obj_prime_meridian_get_parameters(PJ_OBJ *prime_meridian, + double *pLongitude, + double *pLongitudeUnitConvFactor, + const char **pLongitudeUnitName); + +PJ_OBJ PROJ_DLL *proj_obj_crs_get_coordoperation(PJ_OBJ *crs, + const char **pMethodName, + const char **pMethodAuthorityName, + const char **pMethodCode); + +int PROJ_DLL proj_coordoperation_is_instanciable(PJ_OBJ *coordoperation); + +int PROJ_DLL proj_coordoperation_get_param_count(PJ_OBJ *coordoperation); + +int PROJ_DLL proj_coordoperation_get_param_index(PJ_OBJ *coordoperation, + const char *name); + +int PROJ_DLL proj_coordoperation_get_param(PJ_OBJ *coordoperation, + int index, + const char **pName, + const char **pNameAuthorityName, + const char **pNameCode, + double *pValue, + const char **pValueString, + double *pValueUnitConvFactor, + const char **pValueUnitName); + +int PROJ_DLL proj_coordoperation_get_grid_used_count(PJ_OBJ *coordoperation); + +int PROJ_DLL proj_coordoperation_get_grid_used(PJ_OBJ *coordoperation, + int index, + const char **pShortName, + const char **pFullName, + const char **pPackageName, + const char **pURL, + int *pDirectDownload, + int *pOpenLicense, + int *pAvailable); -double proj_dmstor(const char *is, char **rs); -char* proj_rtodms(char *s, double r, int pos, int neg); +double PROJ_DLL proj_coordoperation_get_accuracy(PJ_OBJ* obj); #ifdef __cplusplus } diff --git a/src/proj_api.h b/src/proj_api.h index 000a0baf..a8a7f106 100644 --- a/src/proj_api.h +++ b/src/proj_api.h @@ -41,6 +41,10 @@ #define PJ_VERSION 600 #endif +#ifdef PROJ_RENAME_SYMBOLS +#include "proj_symbol_rename.h" +#endif + /* If we're not asked for PJ_VERSION only, give them everything */ #ifndef PROJ_API_INCLUDED_FOR_PJ_VERSION_ONLY @@ -53,6 +57,17 @@ #include <stddef.h> #include <stdlib.h> +#ifndef PROJ_DLL +#ifdef PROJ_MSVC_DLL_EXPORT +#define PROJ_DLL __declspec(dllexport) +#elif defined(PROJ_MSVC_DLL_IMPORT) +#define PROJ_DLL __declspec(dllimport) +#elif defined(__GNUC__) +#define PROJ_DLL __attribute__ ((visibility("default"))) +#else +#define PROJ_DLL +#endif +#endif #ifdef __cplusplus extern "C" { @@ -73,8 +88,8 @@ extern "C" { -extern char const pj_release[]; /* global release id string */ -extern int pj_errno; /* global error return code */ +extern char const PROJ_DLL pj_release[]; /* global release id string */ +PROJ_DLL extern int pj_errno; /* global error return code */ #ifndef PROJ_INTERNAL_H /* replaced by enum proj_log_level in proj_internal.h */ @@ -128,87 +143,87 @@ typedef struct projFileAPI_t { /* procedure prototypes */ -projCtx pj_get_default_ctx(void); -projCtx pj_get_ctx( projPJ ); +projCtx PROJ_DLL pj_get_default_ctx(void); +projCtx PROJ_DLL pj_get_ctx( projPJ ); -projXY pj_fwd(projLP, projPJ); -projLP pj_inv(projXY, projPJ); +projXY PROJ_DLL pj_fwd(projLP, projPJ); +projLP PROJ_DLL pj_inv(projXY, projPJ); -projXYZ pj_fwd3d(projLPZ, projPJ); -projLPZ pj_inv3d(projXYZ, projPJ); +projXYZ PROJ_DLL pj_fwd3d(projLPZ, projPJ); +projLPZ PROJ_DLL pj_inv3d(projXYZ, projPJ); -int pj_transform( projPJ src, projPJ dst, long point_count, int point_offset, +int PROJ_DLL pj_transform( projPJ src, projPJ dst, long point_count, int point_offset, double *x, double *y, double *z ); -int pj_datum_transform( projPJ src, projPJ dst, long point_count, int point_offset, +int PROJ_DLL pj_datum_transform( projPJ src, projPJ dst, long point_count, int point_offset, double *x, double *y, double *z ); -int pj_geocentric_to_geodetic( double a, double es, +int PROJ_DLL pj_geocentric_to_geodetic( double a, double es, long point_count, int point_offset, double *x, double *y, double *z ); -int pj_geodetic_to_geocentric( double a, double es, +int PROJ_DLL pj_geodetic_to_geocentric( double a, double es, long point_count, int point_offset, double *x, double *y, double *z ); -int pj_compare_datums( projPJ srcdefn, projPJ dstdefn ); -int pj_apply_gridshift( projCtx, const char *, int, +int PROJ_DLL pj_compare_datums( projPJ srcdefn, projPJ dstdefn ); +int PROJ_DLL pj_apply_gridshift( projCtx, const char *, int, long point_count, int point_offset, double *x, double *y, double *z ); -void pj_deallocate_grids(void); -void pj_clear_initcache(void); -int pj_is_latlong(projPJ); -int pj_is_geocent(projPJ); -void pj_get_spheroid_defn(projPJ defn, double *major_axis, double *eccentricity_squared); -void pj_pr_list(projPJ); -void pj_free(projPJ); -void pj_set_finder( const char *(*)(const char *) ); -void pj_set_searchpath ( int count, const char **path ); -projPJ pj_init(int, char **); -projPJ pj_init_plus(const char *); -projPJ pj_init_ctx( projCtx, int, char ** ); -projPJ pj_init_plus_ctx( projCtx, const char * ); -char *pj_get_def(projPJ, int); -projPJ pj_latlong_from_proj( projPJ ); -int pj_has_inverse(projPJ); - - -void *pj_malloc(size_t); -void pj_dalloc(void *); -void *pj_calloc (size_t n, size_t size); -void *pj_dealloc (void *ptr); -char *pj_strdup(const char *str); -char *pj_strerrno(int); -int *pj_get_errno_ref(void); -const char *pj_get_release(void); -void pj_acquire_lock(void); -void pj_release_lock(void); -void pj_cleanup_lock(void); - -void pj_set_ctx( projPJ, projCtx ); -projCtx pj_ctx_alloc(void); -void pj_ctx_free( projCtx ); -int pj_ctx_get_errno( projCtx ); -void pj_ctx_set_errno( projCtx, int ); -void pj_ctx_set_debug( projCtx, int ); -void pj_ctx_set_logger( projCtx, void (*)(void *, int, const char *) ); -void pj_ctx_set_app_data( projCtx, void * ); -void *pj_ctx_get_app_data( projCtx ); -void pj_ctx_set_fileapi( projCtx, projFileAPI *); -projFileAPI *pj_ctx_get_fileapi( projCtx ); - -void pj_log( projCtx ctx, int level, const char *fmt, ... ); -void pj_stderr_logger( void *, int, const char * ); +void PROJ_DLL pj_deallocate_grids(void); +void PROJ_DLL pj_clear_initcache(void); +int PROJ_DLL pj_is_latlong(projPJ); +int PROJ_DLL pj_is_geocent(projPJ); +void PROJ_DLL pj_get_spheroid_defn(projPJ defn, double *major_axis, double *eccentricity_squared); +void PROJ_DLL pj_pr_list(projPJ); +void PROJ_DLL pj_free(projPJ); +void PROJ_DLL pj_set_finder( const char *(*)(const char *) ); +void PROJ_DLL pj_set_searchpath ( int count, const char **path ); +projPJ PROJ_DLL pj_init(int, char **); +projPJ PROJ_DLL pj_init_plus(const char *); +projPJ PROJ_DLL pj_init_ctx( projCtx, int, char ** ); +projPJ PROJ_DLL pj_init_plus_ctx( projCtx, const char * ); +char PROJ_DLL *pj_get_def(projPJ, int); +projPJ PROJ_DLL pj_latlong_from_proj( projPJ ); +int PROJ_DLL pj_has_inverse(projPJ); + + +void PROJ_DLL *pj_malloc(size_t); +void PROJ_DLL pj_dalloc(void *); +void PROJ_DLL *pj_calloc (size_t n, size_t size); +void PROJ_DLL *pj_dealloc (void *ptr); +char PROJ_DLL *pj_strdup(const char *str); +char PROJ_DLL *pj_strerrno(int); +int PROJ_DLL *pj_get_errno_ref(void); +const char PROJ_DLL *pj_get_release(void); +void PROJ_DLL pj_acquire_lock(void); +void PROJ_DLL pj_release_lock(void); +void PROJ_DLL pj_cleanup_lock(void); + +void PROJ_DLL pj_set_ctx( projPJ, projCtx ); +projCtx PROJ_DLL pj_ctx_alloc(void); +void PROJ_DLL pj_ctx_free( projCtx ); +int PROJ_DLL pj_ctx_get_errno( projCtx ); +void PROJ_DLL pj_ctx_set_errno( projCtx, int ); +void PROJ_DLL pj_ctx_set_debug( projCtx, int ); +void PROJ_DLL pj_ctx_set_logger( projCtx, void (*)(void *, int, const char *) ); +void PROJ_DLL pj_ctx_set_app_data( projCtx, void * ); +void PROJ_DLL *pj_ctx_get_app_data( projCtx ); +void PROJ_DLL pj_ctx_set_fileapi( projCtx, projFileAPI *); +projFileAPI PROJ_DLL *pj_ctx_get_fileapi( projCtx ); + +void PROJ_DLL pj_log( projCtx ctx, int level, const char *fmt, ... ); +void PROJ_DLL pj_stderr_logger( void *, int, const char * ); /* file api */ -projFileAPI *pj_get_default_fileapi(void); +projFileAPI PROJ_DLL *pj_get_default_fileapi(void); -PAFile pj_ctx_fopen(projCtx ctx, const char *filename, const char *access); -size_t pj_ctx_fread(projCtx ctx, void *buffer, size_t size, size_t nmemb, PAFile file); -int pj_ctx_fseek(projCtx ctx, PAFile file, long offset, int whence); -long pj_ctx_ftell(projCtx ctx, PAFile file); -void pj_ctx_fclose(projCtx ctx, PAFile file); -char *pj_ctx_fgets(projCtx ctx, char *line, int size, PAFile file); +PAFile PROJ_DLL pj_ctx_fopen(projCtx ctx, const char *filename, const char *access); +size_t PROJ_DLL pj_ctx_fread(projCtx ctx, void *buffer, size_t size, size_t nmemb, PAFile file); +int PROJ_DLL pj_ctx_fseek(projCtx ctx, PAFile file, long offset, int whence); +long PROJ_DLL pj_ctx_ftell(projCtx ctx, PAFile file); +void PROJ_DLL pj_ctx_fclose(projCtx ctx, PAFile file); +char PROJ_DLL *pj_ctx_fgets(projCtx ctx, char *line, int size, PAFile file); -PAFile pj_open_lib(projCtx, const char *, const char *); -int pj_find_file(projCtx ctx, const char *short_filename, +PAFile PROJ_DLL pj_open_lib(projCtx, const char *, const char *); +int PROJ_DLL pj_find_file(projCtx ctx, const char *short_filename, char* out_full_filename, size_t out_full_filename_size); #ifdef __cplusplus diff --git a/src/proj_constants.h b/src/proj_constants.h new file mode 100644 index 00000000..d3f33c73 --- /dev/null +++ b/src/proj_constants.h @@ -0,0 +1,580 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: Constants + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef PROJ_CONSTANTS_INCLUDED +#define PROJ_CONSTANTS_INCLUDED + +/* Projection methods */ + +#define EPSG_NAME_METHOD_TRANSVERSE_MERCATOR "Transverse Mercator" +#define EPSG_CODE_METHOD_TRANSVERSE_MERCATOR 9807 + +#define EPSG_NAME_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED \ + "Transverse Mercator (South Orientated)" +#define EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED 9808 + +#define PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT "Two Point Equidistant" + +#define EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP \ + "Lambert Conic Conformal (1SP)" +#define EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP 9801 + +#define EPSG_NAME_METHOD_NZMG "New Zealand Map Grid" +#define EPSG_CODE_METHOD_NZMG 9811 + +#define EPSG_NAME_METHOD_TUNISIA_MAPPING_GRID "Tunisia Mapping Grid" +#define EPSG_CODE_METHOD_TUNISIA_MAPPING_GRID 9816 + +#define EPSG_NAME_METHOD_ALBERS_EQUAL_AREA "Albers Equal Area" +#define EPSG_CODE_METHOD_ALBERS_EQUAL_AREA 9822 + +#define EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP \ + "Lambert Conic Conformal (2SP)" +#define EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP 9802 + +#define EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM \ + "Lambert Conic Conformal (2SP Belgium)" +#define EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM 9803 + +#define EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN \ + "Lambert Conic Conformal (2SP Michigan)" +#define EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN 1051 + +#define EPSG_NAME_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT \ + "Modified Azimuthal Equidistant" +#define EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT 9832 + +#define EPSG_NAME_METHOD_GUAM_PROJECTION "Guam Projection" +#define EPSG_CODE_METHOD_GUAM_PROJECTION 9831 + +#define EPSG_NAME_METHOD_BONNE "Bonne" +#define EPSG_CODE_METHOD_BONNE 9827 + +#define EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL \ + "Lambert Cylindrical Equal Area (Spherical)" +#define EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL 9834 + +#define EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA \ + "Lambert Cylindrical Equal Area" +#define EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA 9835 + +#define EPSG_NAME_METHOD_CASSINI_SOLDNER "Cassini-Soldner" +#define EPSG_CODE_METHOD_CASSINI_SOLDNER 9806 + +#define PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC "Equidistant Conic" + +#define PROJ_WKT2_NAME_METHOD_ECKERT_I "Eckert I" + +#define PROJ_WKT2_NAME_METHOD_ECKERT_II "Eckert II" + +#define PROJ_WKT2_NAME_METHOD_ECKERT_III "Eckert III" + +#define PROJ_WKT2_NAME_METHOD_ECKERT_IV "Eckert IV" + +#define PROJ_WKT2_NAME_METHOD_ECKERT_V "Eckert V" + +#define PROJ_WKT2_NAME_METHOD_ECKERT_VI "Eckert VI" + +#define EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL "Equidistant Cylindrical" +#define EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL 1028 + +#define EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL \ + "Equidistant Cylindrical (Spherical)" +#define EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL 1029 + +#define PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC "Gall Stereographic" + +#define PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE "Goode Homolosine" + +#define PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE \ + "Interrupted Goode Homolosine" + +#define PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X \ + "Geostationary Satellite (Sweep X)" + +#define PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y \ + "Geostationary Satellite (Sweep Y)" + +#define PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR \ + "Gauss Schreiber Transverse Mercator" + +#define PROJ_WKT2_NAME_METHOD_GNOMONIC "Gnomonic" + +#define EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A \ + "Hotine Oblique Mercator (variant A)" +#define EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A 9812 + +#define EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B \ + "Hotine Oblique Mercator (variant B)" +#define EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B 9815 + +#define PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN \ + "Hotine Oblique Mercator Two Point Natural Origin" + +#define PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC \ + "International Map of the World Polyconic" + +#define EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED "Krovak (North Orientated)" +#define EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED 1041 + +#define EPSG_NAME_METHOD_KROVAK "Krovak" +#define EPSG_CODE_METHOD_KROVAK 9819 + +#define EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA \ + "Lambert Azimuthal Equal Area" +#define EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA 9820 + +#define EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL \ + "Lambert Azimuthal Equal Area (Spherical)" +#define EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL 1027 + +#define PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL "Miller Cylindrical" + +#define EPSG_CODE_METHOD_MERCATOR_VARIANT_A 9804 +#define EPSG_NAME_METHOD_MERCATOR_VARIANT_A "Mercator (variant A)" + +#define EPSG_CODE_METHOD_MERCATOR_VARIANT_B 9805 +#define EPSG_NAME_METHOD_MERCATOR_VARIANT_B "Mercator (variant B)" + +#define EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR \ + "Popular Visualisation Pseudo Mercator" +#define EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR 1024 + +#define PROJ_WKT2_NAME_METHOD_MOLLWEIDE "Mollweide" + +#define EPSG_NAME_METHOD_OBLIQUE_STEREOGRAPHIC "Oblique Stereographic" +#define EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC 9809 + +#define EPSG_NAME_METHOD_ORTHOGRAPHIC "Orthographic" +#define EPSG_CODE_METHOD_ORTHOGRAPHIC 9840 + +#define EPSG_NAME_METHOD_AMERICAN_POLYCONIC "American Polyconic" +#define EPSG_CODE_METHOD_AMERICAN_POLYCONIC 9818 + +#define EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A \ + "Polar Stereographic (variant A)" +#define EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A 9810 + +#define EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B \ + "Polar Stereographic (variant B)" +#define EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B 9829 + +#define PROJ_WKT2_NAME_METHOD_ROBINSON "Robinson" + +#define PROJ_WKT2_NAME_METHOD_SINUSOIDAL "Sinusoidal" + +#define PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC "Stereographic" + +#define PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN "Van Der Grinten" + +#define PROJ_WKT2_NAME_METHOD_WAGNER_I "Wagner I" +#define PROJ_WKT2_NAME_METHOD_WAGNER_II "Wagner II" +#define PROJ_WKT2_NAME_METHOD_WAGNER_III "Wagner III" +#define PROJ_WKT2_NAME_METHOD_WAGNER_IV "Wagner IV" +#define PROJ_WKT2_NAME_METHOD_WAGNER_V "Wagner V" +#define PROJ_WKT2_NAME_METHOD_WAGNER_VI "Wagner VI" +#define PROJ_WKT2_NAME_METHOD_WAGNER_VII "Wagner VII" + +#define PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE \ + "Quadrilateralized Spherical Cube" + +#define PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT \ + "Spherical Cross-Track Height" + +#define EPSG_NAME_METHOD_EQUAL_EARTH "Equal Earth" +#define EPSG_CODE_METHOD_EQUAL_EARTH 1078 + +#define EPSG_NAME_METHOD_LABORDE_OBLIQUE_MERCATOR "Laborde Oblique Mercator" +#define EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR 9813 + +/* ------------------------------------------------------------------------ */ + +/* Projection parameters */ + +#define EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS "Co-latitude of cone axis" +#define EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS 1036 + +#define EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN \ + "Latitude of natural origin" +#define EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN 8801 + +#define EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN \ + "Longitude of natural origin" +#define EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN 8802 + +#define EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN \ + "Scale factor at natural origin" +#define EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN 8805 + +#define EPSG_NAME_PARAMETER_FALSE_EASTING "False easting" +#define EPSG_CODE_PARAMETER_FALSE_EASTING 8806 + +#define EPSG_NAME_PARAMETER_FALSE_NORTHING "False northing" +#define EPSG_CODE_PARAMETER_FALSE_NORTHING 8807 + +#define EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE \ + "Latitude of projection centre" +#define EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE 8811 + +#define EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE \ + "Longitude of projection centre" +#define EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE 8812 + +#define EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE "Azimuth of initial line" +#define EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE 8813 + +#define EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID \ + "Angle from Rectified to Skew Grid" +#define EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID 8814 + +#define EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE \ + "Scale factor on initial line" +#define EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE 8815 + +#define EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE \ + "Easting at projection centre" +#define EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE 8816 + +#define EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE \ + "Northing at projection centre" +#define EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE 8817 + +#define EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL \ + "Latitude of pseudo standard parallel" +#define EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL 8818 + +#define EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL \ + "Scale factor on pseudo standard parallel" +#define EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL 8819 + +#define EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN "Latitude of false origin" +#define EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN 8821 + +#define EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN "Longitude of false origin" +#define EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN 8822 + +#define EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL \ + "Latitude of 1st standard parallel" +#define EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL 8823 + +#define EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL \ + "Latitude of 2nd standard parallel" +#define EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL 8824 + +#define EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN "Easting at false origin" +#define EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN 8826 + +#define EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN "Northing at false origin" +#define EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN 8827 + +#define EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL "Latitude of standard parallel" +#define EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL 8832 + +#define EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN "Longitude of origin" +#define EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN 8833 + +#define EPSG_NAME_PARAMETER_ELLIPSOID_SCALE_FACTOR "Ellipsoid scaling factor" +#define EPSG_CODE_PARAMETER_ELLIPSOID_SCALE_FACTOR 1038 + +/* ------------------------------------------------------------------------ */ + +/* Other conversions and transformations */ + +#define EPSG_NAME_METHOD_COORDINATE_FRAME_GEOCENTRIC \ + "Coordinate Frame rotation (geocentric domain)" +#define EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC 1032 + +#define EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D \ + "Coordinate Frame rotation (geog2D domain)" +#define EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D 9607 + +#define EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D \ + "Coordinate Frame rotation (geog3D domain)" +#define EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D 1038 + +#define EPSG_NAME_METHOD_POSITION_VECTOR_GEOCENTRIC \ + "Position Vector transformation (geocentric domain)" +#define EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC 1033 + +#define EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D \ + "Position Vector transformation (geog2D domain)" +#define EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D 9606 + +#define EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D \ + "Position Vector transformation (geog3D domain)" +#define EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D 1037 + +#define EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC \ + "Geocentric translations (geocentric domain)" +#define EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC 1031 + +#define EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D \ + "Geocentric translations (geog2D domain)" +#define EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D 9603 + +#define EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D \ + "Geocentric translations (geog3D domain)" +#define EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D 1035 + +#define EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC \ + "Time-dependent Position Vector tfm (geocentric)" +#define EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC 1053 + +#define EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D \ + "Time-dependent Position Vector tfm (geog2D)" +#define EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D 1054 + +#define EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D \ + "Time-dependent Position Vector tfm (geog3D)" +#define EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D 1055 + +#define EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC \ + "Time-dependent Coordinate Frame rotation geocen)" +#define EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC \ + 1056 + +#define EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D \ + "Time-dependent Coordinate Frame rotation (geog2D)" +#define EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D 1057 + +#define EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D \ + "Time-dependent Coordinate Frame rotation (geog3D)" +#define EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D 1058 + +#define EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC \ + "Molodensky-Badekas (CF geocentric domain)" +#define EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC 1034 + +#define EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC \ + "Molodensky-Badekas (PV geocentric domain)" +#define EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC 1061 + +#define EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D \ + "Molodensky-Badekas (CF geog3D domain)" +#define EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D 1039 + +#define EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D \ + "Molodensky-Badekas (PV geog3D domain)" +#define EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D 1062 + +#define EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D \ + "Molodensky-Badekas (CF geog2D domain)" +#define EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D 9636 + +#define EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D \ + "Molodensky-Badekas (PV geog2D domain)" +#define EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D 1063 + +#define EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION 8605 +#define EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION 8606 +#define EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION 8607 +#define EPSG_CODE_PARAMETER_X_AXIS_ROTATION 8608 +#define EPSG_CODE_PARAMETER_Y_AXIS_ROTATION 8609 +#define EPSG_CODE_PARAMETER_Z_AXIS_ROTATION 8610 +#define EPSG_CODE_PARAMETER_SCALE_DIFFERENCE 8611 +#define EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION 1040 +#define EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION 1041 +#define EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION 1042 +#define EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION 1043 +#define EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION 1044 +#define EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION 1045 +#define EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE 1046 +#define EPSG_CODE_PARAMETER_REFERENCE_EPOCH 1047 +#define EPSG_CODE_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH 1049 + +#define EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION "X-axis translation" +#define EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION "Y-axis translation" +#define EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION "Z-axis translation" +#define EPSG_NAME_PARAMETER_X_AXIS_ROTATION "X-axis rotation" +#define EPSG_NAME_PARAMETER_Y_AXIS_ROTATION "Y-axis rotation" +#define EPSG_NAME_PARAMETER_Z_AXIS_ROTATION "Z-axis rotation" +#define EPSG_NAME_PARAMETER_SCALE_DIFFERENCE "Scale difference" + +#define EPSG_NAME_PARAMETER_RATE_X_AXIS_TRANSLATION \ + "Rate of change of X-axis translation" +#define EPSG_NAME_PARAMETER_RATE_Y_AXIS_TRANSLATION \ + "Rate of change of Y-axis translation" +#define EPSG_NAME_PARAMETER_RATE_Z_AXIS_TRANSLATION \ + "Rate of change of Z-axis translation" +#define EPSG_NAME_PARAMETER_RATE_X_AXIS_ROTATION \ + "Rate of change of X-axis rotation" +#define EPSG_NAME_PARAMETER_RATE_Y_AXIS_ROTATION \ + "Rate of change of Y-axis rotation" +#define EPSG_NAME_PARAMETER_RATE_Z_AXIS_ROTATION \ + "Rate of change of Z-axis rotation" +#define EPSG_NAME_PARAMETER_RATE_SCALE_DIFFERENCE \ + "Rate of change of Scale difference" +#define EPSG_NAME_PARAMETER_REFERENCE_EPOCH "Parameter reference epoch" + +#define EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT 8617 +#define EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT 8618 +#define EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT 8667 + +#define \ + EPSG_NAME_PARAMETER_ORDINATE_1_EVAL_POINT "Ordinate 1 of evaluation point" +#define \ + EPSG_NAME_PARAMETER_ORDINATE_2_EVAL_POINT "Ordinate 2 of evaluation point" +#define \ + EPSG_NAME_PARAMETER_ORDINATE_3_EVAL_POINT "Ordinate 3 of evaluation point" + +#define EPSG_NAME_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH \ + "Transformation reference epoch" + +#define EPSG_NAME_METHOD_MOLODENSKY "Molodensky" +#define EPSG_CODE_METHOD_MOLODENSKY 9604 + +#define EPSG_NAME_METHOD_ABRIDGED_MOLODENSKY "Abridged Molodensky" +#define EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY 9605 + +#define EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE 8654 +#define EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE 8655 + +#define EPSG_NAME_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE \ + "Semi-major axis length difference" +#define \ + EPSG_NAME_PARAMETER_FLATTENING_DIFFERENCE "Flattening difference" + +/* ------------------------------------------------------------------------ */ + +#define EPSG_CODE_METHOD_NTV1 9614 +#define EPSG_NAME_METHOD_NTV1 "NTv1" + +#define EPSG_CODE_METHOD_NTV2 9615 +#define EPSG_NAME_METHOD_NTV2 "NTv2" + +#define EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE 8656 +#define EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE \ + "Latitude and longitude difference file" + +#define EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME \ + "Geoid (height correction) model file" +#define EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME 8666 + +/* ------------------------------------------------------------------------ */ + +#define PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D \ + "GravityRelatedHeight to Geographic3D" + +#define PROJ_WKT2_NAME_METHOD_CTABLE2 "CTABLE2" + +/* ------------------------------------------------------------------------ */ + +#define EPSG_CODE_METHOD_VERTCON 9658 +#define EPSG_NAME_METHOD_VERTCON "VERTCON" + +#define EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE "Vertical offset file" +#define EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE 8732 + +/* ------------------------------------------------------------------------ */ + +#define EPSG_CODE_METHOD_NADCON 9613 +#define EPSG_NAME_METHOD_NADCON "NADCON" + +#define EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE "Latitude difference file" +#define EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE 8657 + +#define \ + EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE "Longitude difference file" +#define EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE 8658 + +/* ------------------------------------------------------------------------ */ + +#define EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT 1069 +#define EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT "Change of Vertical Unit" + +#define EPSG_NAME_PARAMETER_UNIT_CONVERSION_SCALAR "Unit conversion scalar" +#define EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR 1051 + +/* ------------------------------------------------------------------------ */ + +#define EPSG_CODE_METHOD_LONGITUDE_ROTATION 9601 +#define EPSG_NAME_METHOD_LONGITUDE_ROTATION "Longitude rotation" + +#define EPSG_CODE_METHOD_VERTICAL_OFFSET 9616 +#define EPSG_NAME_METHOD_VERTICAL_OFFSET "Vertical Offset" + +#define EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS 9619 +#define EPSG_NAME_METHOD_GEOGRAPHIC2D_OFFSETS "Geographic2D offsets" + +#define EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS 9618 +#define EPSG_NAME_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS \ + "Geographic2D with Height Offsets" + +#define EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS 9660 +#define EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSETS "Geographic3D offsets" + +#define EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC 9602 +#define \ + EPSG_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC "Geographic/geocentric conversions" + +#define EPSG_NAME_PARAMETER_LATITUDE_OFFSET "Latitude offset" +#define EPSG_CODE_PARAMETER_LATITUDE_OFFSET 8601 + +#define EPSG_NAME_PARAMETER_LONGITUDE_OFFSET "Longitude offset" +#define EPSG_CODE_PARAMETER_LONGITUDE_OFFSET 8602 + +#define EPSG_NAME_PARAMETER_VERTICAL_OFFSET "Vertical Offset" +#define EPSG_CODE_PARAMETER_VERTICAL_OFFSET 8603 + +#define EPSG_NAME_PARAMETER_GEOID_UNDULATION "Geoid undulation" +#define EPSG_CODE_PARAMETER_GEOID_UNDULATION 8604 + +/* ------------------------------------------------------------------------ */ + +#define EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION 9624 +#define EPSG_NAME_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION \ + "Affine parametric transformation" + +#define EPSG_NAME_PARAMETER_A0 "A0" +#define EPSG_CODE_PARAMETER_A0 8623 + +#define EPSG_NAME_PARAMETER_A1 "A1" +#define EPSG_CODE_PARAMETER_A1 8624 + +#define EPSG_NAME_PARAMETER_A2 "A2" +#define EPSG_CODE_PARAMETER_A2 8625 + +#define EPSG_NAME_PARAMETER_B0 "B0" +#define EPSG_CODE_PARAMETER_B0 8639 + +#define EPSG_NAME_PARAMETER_B1 "B1" +#define EPSG_CODE_PARAMETER_B1 8640 + +#define EPSG_NAME_PARAMETER_B2 "B2" +#define EPSG_CODE_PARAMETER_B2 8641 + +/* ------------------------------------------------------------------------ */ + +#define EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D 9843 +#define EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_2D "Axis Order Reversal (2D)" + +#define EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D 9844 +#define EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_3D \ + "Axis Order Reversal (Geographic3D horizontal)" + +#endif /* PROJ_CONSTANTS_INCLUDED */ diff --git a/src/proj_internal.h b/src/proj_internal.h index 76f5d952..d5154f78 100644 --- a/src/proj_internal.h +++ b/src/proj_internal.h @@ -44,6 +44,10 @@ #error proj_internal.h must be included before proj_api.h #endif +#ifdef PROJ_RENAME_SYMBOLS +#include "proj_symbol_rename.h" +#endif + #ifndef PROJ_INTERNAL_H #define PROJ_INTERNAL_H #ifdef __cplusplus @@ -79,17 +83,21 @@ enum pj_io_units { enum pj_io_units pj_left (PJ *P); enum pj_io_units pj_right (PJ *P); -PJ_COORD proj_coord_error (void); +PJ_COORD PROJ_DLL proj_coord_error (void); void proj_context_errno_set (PJ_CONTEXT *ctx, int err); -void proj_context_set (PJ *P, PJ_CONTEXT *ctx); +void PROJ_DLL proj_context_set (PJ *P, PJ_CONTEXT *ctx); void proj_context_inherit (PJ *parent, PJ *child); +struct projCppContext; +/* not sure why we need to export it, but mingw needs it */ +void PROJ_DLL proj_context_delete_cpp_context(struct projCppContext* cppContext); + PJ_COORD pj_fwd4d (PJ_COORD coo, PJ *P); PJ_COORD pj_inv4d (PJ_COORD coo, PJ *P); -PJ_COORD pj_approx_2D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo); -PJ_COORD pj_approx_3D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo); +PJ_COORD PROJ_DLL pj_approx_2D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo); +PJ_COORD PROJ_DLL pj_approx_3D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo); /* Grid functionality */ @@ -99,7 +107,7 @@ double proj_vgrid_value(PJ *P, PJ_LP lp); PJ_LP proj_hgrid_value(PJ *P, PJ_LP lp); PJ_LP proj_hgrid_apply(PJ *P, PJ_LP lp, PJ_DIRECTION direction); -void proj_log_error (PJ *P, const char *fmt, ...); +void PROJ_DLL proj_log_error (PJ *P, const char *fmt, ...); void proj_log_debug (PJ *P, const char *fmt, ...); void proj_log_trace (PJ *P, const char *fmt, ...); @@ -111,8 +119,8 @@ int pj_calc_ellipsoid_params (PJ *P, double a, double es); /* Geographical to geocentric latitude - another of the "simple, but useful" */ PJ_COORD pj_geocentric_latitude (const PJ *P, PJ_DIRECTION direction, PJ_COORD coord); -char *pj_chomp (char *c); -char *pj_shrink (char *c); +char PROJ_DLL *pj_chomp (char *c); +char PROJ_DLL *pj_shrink (char *c); size_t pj_trim_argc (char *args); char **pj_trim_argv (size_t argc, char *args); char *pj_make_args (size_t argc, char **argv); diff --git a/src/proj_math.h b/src/proj_math.h index f6dd7e09..5aea494d 100644 --- a/src/proj_math.h +++ b/src/proj_math.h @@ -37,6 +37,22 @@ extern "C" { #if !(defined(HAVE_C99_MATH) && HAVE_C99_MATH) +#ifndef PROJ_DLL +#ifdef PROJ_MSVC_DLL_EXPORT +#define PROJ_DLL __declspec(dllexport) +#elif defined(PROJ_MSVC_DLL_IMPORT) +#define PROJ_DLL __declspec(dllimport) +#elif defined(__GNUC__) +#define PROJ_DLL __attribute__ ((visibility("default"))) +#else +#define PROJ_DLL +#endif +#endif + +#ifdef PROJ_RENAME_SYMBOLS +#include "proj_symbol_rename.h" +#endif + #ifndef NAN #ifdef _WIN32 #define NAN sqrt(-1.0) @@ -50,7 +66,7 @@ double pj_log1p(double x); double pj_asinh(double x); double pj_round(double x); long pj_lround(double x); -int pj_isnan(double x); +int PROJ_DLL pj_isnan(double x); #define hypot pj_hypot #define log1p pj_log1p diff --git a/src/proj_symbol_rename.h b/src/proj_symbol_rename.h new file mode 100644 index 00000000..d0dd09eb --- /dev/null +++ b/src/proj_symbol_rename.h @@ -0,0 +1,273 @@ +/* This is a generated file by create_proj_symbol_rename.sh. *DO NOT EDIT MANUALLY !* */ +#ifndef PROJ_SYMBOL_RENAME_H +#define PROJ_SYMBOL_RENAME_H +#define adjlon internal_adjlon +#define dmstor internal_dmstor +#define emess internal_emess +#define geod_direct internal_geod_direct +#define geod_directline internal_geod_directline +#define geod_gendirect internal_geod_gendirect +#define geod_gendirectline internal_geod_gendirectline +#define geod_geninverse internal_geod_geninverse +#define geod_genposition internal_geod_genposition +#define geod_gensetdistance internal_geod_gensetdistance +#define geod_init internal_geod_init +#define geod_inverse internal_geod_inverse +#define geod_inverseline internal_geod_inverseline +#define geod_lineinit internal_geod_lineinit +#define geod_polygon_addedge internal_geod_polygon_addedge +#define geod_polygon_addpoint internal_geod_polygon_addpoint +#define geod_polygonarea internal_geod_polygonarea +#define geod_polygon_clear internal_geod_polygon_clear +#define geod_polygon_compute internal_geod_polygon_compute +#define geod_polygon_init internal_geod_polygon_init +#define geod_polygon_testedge internal_geod_polygon_testedge +#define geod_polygon_testpoint internal_geod_polygon_testpoint +#define geod_position internal_geod_position +#define geod_setdistance internal_geod_setdistance +#define mk_cheby internal_mk_cheby +#define pj_acquire_lock internal_pj_acquire_lock +#define pj_apply_gridshift internal_pj_apply_gridshift +#define pj_approx_2D_trans internal_pj_approx_2D_trans +#define pj_approx_3D_trans internal_pj_approx_3D_trans +#define pj_atof internal_pj_atof +#define pj_calloc internal_pj_calloc +#define pj_chomp internal_pj_chomp +#define pj_cleanup_lock internal_pj_cleanup_lock +#define pj_clear_initcache internal_pj_clear_initcache +#define pj_compare_datums internal_pj_compare_datums +#define pj_ctx_alloc internal_pj_ctx_alloc +#define pj_ctx_fclose internal_pj_ctx_fclose +#define pj_ctx_fgets internal_pj_ctx_fgets +#define pj_ctx_fopen internal_pj_ctx_fopen +#define pj_ctx_fread internal_pj_ctx_fread +#define pj_ctx_free internal_pj_ctx_free +#define pj_ctx_fseek internal_pj_ctx_fseek +#define pj_ctx_ftell internal_pj_ctx_ftell +#define pj_ctx_get_app_data internal_pj_ctx_get_app_data +#define pj_ctx_get_errno internal_pj_ctx_get_errno +#define pj_ctx_get_fileapi internal_pj_ctx_get_fileapi +#define pj_ctx_set_app_data internal_pj_ctx_set_app_data +#define pj_ctx_set_debug internal_pj_ctx_set_debug +#define pj_ctx_set_errno internal_pj_ctx_set_errno +#define pj_ctx_set_fileapi internal_pj_ctx_set_fileapi +#define pj_ctx_set_logger internal_pj_ctx_set_logger +#define pj_dalloc internal_pj_dalloc +#define pj_datum_transform internal_pj_datum_transform +#define pj_dealloc internal_pj_dealloc +#define pj_deallocate_grids internal_pj_deallocate_grids +#define pj_ell_set internal_pj_ell_set +#define pj_find_file internal_pj_find_file +#define pj_free internal_pj_free +#define pj_fwd internal_pj_fwd +#define pj_fwd3d internal_pj_fwd3d +#define pj_geocentric_to_geodetic internal_pj_geocentric_to_geodetic +#define pj_geodetic_to_geocentric internal_pj_geodetic_to_geocentric +#define pj_get_ctx internal_pj_get_ctx +#define pj_get_datums_ref internal_pj_get_datums_ref +#define pj_get_def internal_pj_get_def +#define pj_get_default_ctx internal_pj_get_default_ctx +#define pj_get_default_fileapi internal_pj_get_default_fileapi +#define pj_get_errno_ref internal_pj_get_errno_ref +#define pj_get_release internal_pj_get_release +#define pj_get_spheroid_defn internal_pj_get_spheroid_defn +#define pj_has_inverse internal_pj_has_inverse +#define pj_init internal_pj_init +#define pj_init_ctx internal_pj_init_ctx +#define pj_init_plus internal_pj_init_plus +#define pj_init_plus_ctx internal_pj_init_plus_ctx +#define pj_inv internal_pj_inv +#define pj_inv3d internal_pj_inv3d +#define pj_is_geocent internal_pj_is_geocent +#define pj_is_latlong internal_pj_is_latlong +#define pj_isnan internal_pj_isnan +#define pj_latlong_from_proj internal_pj_latlong_from_proj +#define pj_log internal_pj_log +#define pj_malloc internal_pj_malloc +#define pj_mkparam internal_pj_mkparam +#define pj_open_lib internal_pj_open_lib +#define pj_param internal_pj_param +#define pj_param_exists internal_pj_param_exists +#define pj_phi2 internal_pj_phi2 +#define pj_pr_list internal_pj_pr_list +#define pj_release_lock internal_pj_release_lock +#define pj_set_ctx internal_pj_set_ctx +#define pj_set_finder internal_pj_set_finder +#define pj_set_searchpath internal_pj_set_searchpath +#define pj_shrink internal_pj_shrink +#define pj_stderr_logger internal_pj_stderr_logger +#define pj_strdup internal_pj_strdup +#define pj_strerrno internal_pj_strerrno +#define pj_transform internal_pj_transform +#define proj_angular_input internal_proj_angular_input +#define proj_angular_output internal_proj_angular_output +#define proj_context_create internal_proj_context_create +#define proj_context_delete_cpp_context internal_proj_context_delete_cpp_context +#define proj_context_destroy internal_proj_context_destroy +#define proj_context_errno internal_proj_context_errno +#define proj_context_get_database_path internal_proj_context_get_database_path +#define proj_context_guess_wkt_dialect internal_proj_context_guess_wkt_dialect +#define proj_context_set internal_proj_context_set +#define proj_context_set_database_path internal_proj_context_set_database_path +#define proj_coord internal_proj_coord +#define proj_coord_error internal_proj_coord_error +#define proj_coordoperation_get_accuracy internal_proj_coordoperation_get_accuracy +#define proj_coordoperation_get_grid_used internal_proj_coordoperation_get_grid_used +#define proj_coordoperation_get_grid_used_count internal_proj_coordoperation_get_grid_used_count +#define proj_coordoperation_get_param internal_proj_coordoperation_get_param +#define proj_coordoperation_get_param_count internal_proj_coordoperation_get_param_count +#define proj_coordoperation_get_param_index internal_proj_coordoperation_get_param_index +#define proj_coordoperation_is_instanciable internal_proj_coordoperation_is_instanciable +#define proj_create internal_proj_create +#define proj_create_argv internal_proj_create_argv +#define proj_create_crs_to_crs internal_proj_create_crs_to_crs +#define proj_create_operation_factory_context internal_proj_create_operation_factory_context +#define proj_destroy internal_proj_destroy +#define proj_dmstor internal_proj_dmstor +#define proj_errno internal_proj_errno +#define proj_errno_reset internal_proj_errno_reset +#define proj_errno_restore internal_proj_errno_restore +#define proj_errno_set internal_proj_errno_set +#define proj_errno_string internal_proj_errno_string +#define proj_factors internal_proj_factors +#define proj_free_int_list internal_proj_free_int_list +#define proj_free_string_list internal_proj_free_string_list +#define proj_geocentric_latitude internal_proj_geocentric_latitude +#define proj_geod internal_proj_geod +#define proj_get_authorities_from_database internal_proj_get_authorities_from_database +#define proj_get_codes_from_database internal_proj_get_codes_from_database +#define proj_grid_info internal_proj_grid_info +#define proj_info internal_proj_info +#define proj_init_info internal_proj_init_info +#define proj_list_angular_units internal_proj_list_angular_units +#define proj_list_ellps internal_proj_list_ellps +#define proj_list_operations internal_proj_list_operations +#define proj_list_prime_meridians internal_proj_list_prime_meridians +#define proj_list_units internal_proj_list_units +#define proj_log_error internal_proj_log_error +#define proj_log_func internal_proj_log_func +#define proj_log_level internal_proj_log_level +#define proj_lp_dist internal_proj_lp_dist +#define proj_lpz_dist internal_proj_lpz_dist +#define proj_obj_as_proj_string internal_proj_obj_as_proj_string +#define proj_obj_as_wkt internal_proj_obj_as_wkt +#define proj_obj_create_from_database internal_proj_obj_create_from_database +#define proj_obj_create_from_name internal_proj_obj_create_from_name +#define proj_obj_create_from_proj_string internal_proj_obj_create_from_proj_string +#define proj_obj_create_from_user_input internal_proj_obj_create_from_user_input +#define proj_obj_create_from_wkt internal_proj_obj_create_from_wkt +#define proj_obj_create_geographic_crs internal_proj_obj_create_geographic_crs +#define proj_obj_create_operations internal_proj_obj_create_operations +#define proj_obj_create_projected_crs_AlbersEqualArea internal_proj_obj_create_projected_crs_AlbersEqualArea +#define proj_obj_create_projected_crs_AmericanPolyconic internal_proj_obj_create_projected_crs_AmericanPolyconic +#define proj_obj_create_projected_crs_AzimuthalEquidistant internal_proj_obj_create_projected_crs_AzimuthalEquidistant +#define proj_obj_create_projected_crs_Bonne internal_proj_obj_create_projected_crs_Bonne +#define proj_obj_create_projected_crs_CassiniSoldner internal_proj_obj_create_projected_crs_CassiniSoldner +#define proj_obj_create_projected_crs_EckertI internal_proj_obj_create_projected_crs_EckertI +#define proj_obj_create_projected_crs_EckertII internal_proj_obj_create_projected_crs_EckertII +#define proj_obj_create_projected_crs_EckertIII internal_proj_obj_create_projected_crs_EckertIII +#define proj_obj_create_projected_crs_EckertIV internal_proj_obj_create_projected_crs_EckertIV +#define proj_obj_create_projected_crs_EckertV internal_proj_obj_create_projected_crs_EckertV +#define proj_obj_create_projected_crs_EckertVI internal_proj_obj_create_projected_crs_EckertVI +#define proj_obj_create_projected_crs_EqualEarth internal_proj_obj_create_projected_crs_EqualEarth +#define proj_obj_create_projected_crs_EquidistantConic internal_proj_obj_create_projected_crs_EquidistantConic +#define proj_obj_create_projected_crs_EquidistantCylindrical internal_proj_obj_create_projected_crs_EquidistantCylindrical +#define proj_obj_create_projected_crs_EquidistantCylindricalSpherical internal_proj_obj_create_projected_crs_EquidistantCylindricalSpherical +#define proj_obj_create_projected_crs_Gall internal_proj_obj_create_projected_crs_Gall +#define proj_obj_create_projected_crs_GaussSchreiberTransverseMercator internal_proj_obj_create_projected_crs_GaussSchreiberTransverseMercator +#define proj_obj_create_projected_crs_GeostationarySatelliteSweepX internal_proj_obj_create_projected_crs_GeostationarySatelliteSweepX +#define proj_obj_create_projected_crs_GeostationarySatelliteSweepY internal_proj_obj_create_projected_crs_GeostationarySatelliteSweepY +#define proj_obj_create_projected_crs_Gnomonic internal_proj_obj_create_projected_crs_Gnomonic +#define proj_obj_create_projected_crs_GoodeHomolosine internal_proj_obj_create_projected_crs_GoodeHomolosine +#define proj_obj_create_projected_crs_GuamProjection internal_proj_obj_create_projected_crs_GuamProjection +#define proj_obj_create_projected_crs_HotineObliqueMercatorTwoPointNaturalOrigin internal_proj_obj_create_projected_crs_HotineObliqueMercatorTwoPointNaturalOrigin +#define proj_obj_create_projected_crs_HotineObliqueMercatorVariantA internal_proj_obj_create_projected_crs_HotineObliqueMercatorVariantA +#define proj_obj_create_projected_crs_HotineObliqueMercatorVariantB internal_proj_obj_create_projected_crs_HotineObliqueMercatorVariantB +#define proj_obj_create_projected_crs_InternationalMapWorldPolyconic internal_proj_obj_create_projected_crs_InternationalMapWorldPolyconic +#define proj_obj_create_projected_crs_InterruptedGoodeHomolosine internal_proj_obj_create_projected_crs_InterruptedGoodeHomolosine +#define proj_obj_create_projected_crs_Krovak internal_proj_obj_create_projected_crs_Krovak +#define proj_obj_create_projected_crs_KrovakNorthOriented internal_proj_obj_create_projected_crs_KrovakNorthOriented +#define proj_obj_create_projected_crs_LambertAzimuthalEqualArea internal_proj_obj_create_projected_crs_LambertAzimuthalEqualArea +#define proj_obj_create_projected_crs_LambertConicConformal_1SP internal_proj_obj_create_projected_crs_LambertConicConformal_1SP +#define proj_obj_create_projected_crs_LambertConicConformal_2SP internal_proj_obj_create_projected_crs_LambertConicConformal_2SP +#define proj_obj_create_projected_crs_LambertConicConformal_2SP_Belgium internal_proj_obj_create_projected_crs_LambertConicConformal_2SP_Belgium +#define proj_obj_create_projected_crs_LambertConicConformal_2SP_Michigan internal_proj_obj_create_projected_crs_LambertConicConformal_2SP_Michigan +#define proj_obj_create_projected_crs_LambertCylindricalEqualArea internal_proj_obj_create_projected_crs_LambertCylindricalEqualArea +#define proj_obj_create_projected_crs_LambertCylindricalEqualAreaSpherical internal_proj_obj_create_projected_crs_LambertCylindricalEqualAreaSpherical +#define proj_obj_create_projected_crs_MercatorVariantA internal_proj_obj_create_projected_crs_MercatorVariantA +#define proj_obj_create_projected_crs_MercatorVariantB internal_proj_obj_create_projected_crs_MercatorVariantB +#define proj_obj_create_projected_crs_MillerCylindrical internal_proj_obj_create_projected_crs_MillerCylindrical +#define proj_obj_create_projected_crs_Mollweide internal_proj_obj_create_projected_crs_Mollweide +#define proj_obj_create_projected_crs_NewZealandMappingGrid internal_proj_obj_create_projected_crs_NewZealandMappingGrid +#define proj_obj_create_projected_crs_ObliqueStereographic internal_proj_obj_create_projected_crs_ObliqueStereographic +#define proj_obj_create_projected_crs_Orthographic internal_proj_obj_create_projected_crs_Orthographic +#define proj_obj_create_projected_crs_PolarStereographicVariantA internal_proj_obj_create_projected_crs_PolarStereographicVariantA +#define proj_obj_create_projected_crs_PolarStereographicVariantB internal_proj_obj_create_projected_crs_PolarStereographicVariantB +#define proj_obj_create_projected_crs_PopularVisualisationPseudoMercator internal_proj_obj_create_projected_crs_PopularVisualisationPseudoMercator +#define proj_obj_create_projected_crs_QuadrilateralizedSphericalCube internal_proj_obj_create_projected_crs_QuadrilateralizedSphericalCube +#define proj_obj_create_projected_crs_Robinson internal_proj_obj_create_projected_crs_Robinson +#define proj_obj_create_projected_crs_Sinusoidal internal_proj_obj_create_projected_crs_Sinusoidal +#define proj_obj_create_projected_crs_SphericalCrossTrackHeight internal_proj_obj_create_projected_crs_SphericalCrossTrackHeight +#define proj_obj_create_projected_crs_Stereographic internal_proj_obj_create_projected_crs_Stereographic +#define proj_obj_create_projected_crs_TransverseMercator internal_proj_obj_create_projected_crs_TransverseMercator +#define proj_obj_create_projected_crs_TransverseMercatorSouthOriented internal_proj_obj_create_projected_crs_TransverseMercatorSouthOriented +#define proj_obj_create_projected_crs_TunisiaMappingGrid internal_proj_obj_create_projected_crs_TunisiaMappingGrid +#define proj_obj_create_projected_crs_TwoPointEquidistant internal_proj_obj_create_projected_crs_TwoPointEquidistant +#define proj_obj_create_projected_crs_UTM internal_proj_obj_create_projected_crs_UTM +#define proj_obj_create_projected_crs_VanDerGrinten internal_proj_obj_create_projected_crs_VanDerGrinten +#define proj_obj_create_projected_crs_WagnerI internal_proj_obj_create_projected_crs_WagnerI +#define proj_obj_create_projected_crs_WagnerII internal_proj_obj_create_projected_crs_WagnerII +#define proj_obj_create_projected_crs_WagnerIII internal_proj_obj_create_projected_crs_WagnerIII +#define proj_obj_create_projected_crs_WagnerIV internal_proj_obj_create_projected_crs_WagnerIV +#define proj_obj_create_projected_crs_WagnerV internal_proj_obj_create_projected_crs_WagnerV +#define proj_obj_create_projected_crs_WagnerVI internal_proj_obj_create_projected_crs_WagnerVI +#define proj_obj_create_projected_crs_WagnerVII internal_proj_obj_create_projected_crs_WagnerVII +#define proj_obj_crs_create_bound_crs_to_WGS84 internal_proj_obj_crs_create_bound_crs_to_WGS84 +#define proj_obj_crs_get_coordoperation internal_proj_obj_crs_get_coordoperation +#define proj_obj_crs_get_geodetic_crs internal_proj_obj_crs_get_geodetic_crs +#define proj_obj_crs_get_horizontal_datum internal_proj_obj_crs_get_horizontal_datum +#define proj_obj_crs_get_sub_crs internal_proj_obj_crs_get_sub_crs +#define proj_obj_ellipsoid_get_parameters internal_proj_obj_ellipsoid_get_parameters +#define proj_obj_get_area_of_use internal_proj_obj_get_area_of_use +#define proj_obj_get_ellipsoid internal_proj_obj_get_ellipsoid +#define proj_obj_get_id_auth_name internal_proj_obj_get_id_auth_name +#define proj_obj_get_id_code internal_proj_obj_get_id_code +#define proj_obj_get_name internal_proj_obj_get_name +#define proj_obj_get_prime_meridian internal_proj_obj_get_prime_meridian +#define proj_obj_get_source_crs internal_proj_obj_get_source_crs +#define proj_obj_get_target_crs internal_proj_obj_get_target_crs +#define proj_obj_get_type internal_proj_obj_get_type +#define proj_obj_identify internal_proj_obj_identify +#define proj_obj_is_crs internal_proj_obj_is_crs +#define proj_obj_is_deprecated internal_proj_obj_is_deprecated +#define proj_obj_is_equivalent_to internal_proj_obj_is_equivalent_to +#define proj_obj_list_get internal_proj_obj_list_get +#define proj_obj_list_get_count internal_proj_obj_list_get_count +#define proj_obj_list_unref internal_proj_obj_list_unref +#define proj_obj_prime_meridian_get_parameters internal_proj_obj_prime_meridian_get_parameters +#define proj_obj_unref internal_proj_obj_unref +#define proj_operation_factory_context_set_allowed_intermediate_crs internal_proj_operation_factory_context_set_allowed_intermediate_crs +#define proj_operation_factory_context_set_allow_use_intermediate_crs internal_proj_operation_factory_context_set_allow_use_intermediate_crs +#define proj_operation_factory_context_set_area_of_interest internal_proj_operation_factory_context_set_area_of_interest +#define proj_operation_factory_context_set_crs_extent_use internal_proj_operation_factory_context_set_crs_extent_use +#define proj_operation_factory_context_set_desired_accuracy internal_proj_operation_factory_context_set_desired_accuracy +#define proj_operation_factory_context_set_grid_availability_use internal_proj_operation_factory_context_set_grid_availability_use +#define proj_operation_factory_context_set_spatial_criterion internal_proj_operation_factory_context_set_spatial_criterion +#define proj_operation_factory_context_set_use_proj_alternative_grid_names internal_proj_operation_factory_context_set_use_proj_alternative_grid_names +#define proj_operation_factory_context_unref internal_proj_operation_factory_context_unref +#define proj_pj_info internal_proj_pj_info +#define proj_roundtrip internal_proj_roundtrip +#define proj_rtodms internal_proj_rtodms +#define proj_todeg internal_proj_todeg +#define proj_torad internal_proj_torad +#define proj_trans internal_proj_trans +#define proj_trans_array internal_proj_trans_array +#define proj_trans_generic internal_proj_trans_generic +#define proj_xy_dist internal_proj_xy_dist +#define proj_xyz_dist internal_proj_xyz_dist +#define rtodms internal_rtodms +#define set_rtodms internal_set_rtodms +#define pj_release internal_pj_release +#define pj_errno internal_pj_errno +#define emess_dat internal_emess_dat +#endif /* PROJ_SYMBOL_RENAME_H */ diff --git a/src/projects.h b/src/projects.h index 409702c4..e34fc9e0 100644 --- a/src/projects.h +++ b/src/projects.h @@ -46,6 +46,10 @@ # endif #endif +#ifdef PROJ_RENAME_SYMBOLS +#include "proj_symbol_rename.h" +#endif + /* standard inclusions */ #include <limits.h> #include <math.h> @@ -53,6 +57,18 @@ #include <stdlib.h> #include <string.h> +#ifndef PROJ_DLL +#ifdef PROJ_MSVC_DLL_EXPORT +#define PROJ_DLL __declspec(dllexport) +#elif defined(PROJ_MSVC_DLL_IMPORT) +#define PROJ_DLL __declspec(dllimport) +#elif defined(__GNUC__) +#define PROJ_DLL __attribute__ ((visibility("default"))) +#else +#define PROJ_DLL +#endif +#endif + #ifdef __cplusplus #define C_NAMESPACE extern "C" #define C_NAMESPACE_VAR extern "C" @@ -570,6 +586,8 @@ struct FACTORS { struct projFileAPI_t; +struct projCppContext; + /* proj thread context */ struct projCtx_t { int last_errno; @@ -577,6 +595,7 @@ struct projCtx_t { void (*logger)(void *, int, const char *); void *app_data; struct projFileAPI_t *fileapi; + struct projCppContext* cpp_context; /* internal context for C++ code */ }; /* classic public API */ @@ -678,20 +697,20 @@ typedef struct _PJ_GridCatalog { } PJ_GridCatalog; /* procedure prototypes */ -double dmstor(const char *, char **); +double PROJ_DLL dmstor(const char *, char **); double dmstor_ctx(projCtx ctx, const char *, char **); -void set_rtodms(int, int); -char *rtodms(char *, double, int, int); -double adjlon(double); +void PROJ_DLL set_rtodms(int, int); +char PROJ_DLL *rtodms(char *, double, int, int); +double PROJ_DLL adjlon(double); double aacos(projCtx,double), aasin(projCtx,double), asqrt(double), aatan2(double, double); -PROJVALUE pj_param(projCtx ctx, paralist *, const char *); -paralist *pj_param_exists (paralist *list, const char *parameter); -paralist *pj_mkparam(const char *); +PROJVALUE PROJ_DLL pj_param(projCtx ctx, paralist *, const char *); +paralist PROJ_DLL *pj_param_exists (paralist *list, const char *parameter); +paralist PROJ_DLL *pj_mkparam(const char *); paralist *pj_mkparam_ws (const char *str); -int pj_ell_set(projCtx ctx, paralist *, double *, double *); +int PROJ_DLL pj_ell_set(projCtx ctx, paralist *, double *, double *); int pj_datum_set(projCtx,paralist *, PJ *); int pj_angular_units_set(paralist *, PJ *); @@ -709,7 +728,7 @@ double pj_inv_mlfn(projCtx, double, double, double *); double pj_qsfn(double, double, double); double pj_tsfn(double, double, double); double pj_msfn(double, double, double); -double pj_phi2(projCtx, double, double); +double PROJ_DLL pj_phi2(projCtx, double, double); double pj_qsfn_(double, PJ *); double *pj_authset(double); double pj_authlat(double, double *); @@ -734,7 +753,7 @@ typedef struct { /* Chebyshev or Power series structure */ int power; /* != 0 if power series, else Chebyshev */ } Tseries; -Tseries *mk_cheby(projUV, projUV, double, projUV *, projUV (*)(projUV), int, int, int); +Tseries PROJ_DLL *mk_cheby(projUV, projUV, double, projUV *, projUV (*)(projUV), int, int, int); projUV bpseval(projUV, Tseries *); projUV bcheval(projUV, Tseries *); projUV biveval(projUV, Tseries *); @@ -771,7 +790,7 @@ int pj_apply_gridshift_3( projCtx ctx, double *x, double *y, double *z ); PJ_GRIDINFO **pj_gridlist_from_nadgrids( projCtx, const char *, int * ); -void pj_deallocate_grids(); +void PROJ_DLL pj_deallocate_grids(); PJ_GRIDINFO *pj_gridinfo_init( projCtx, const char * ); int pj_gridinfo_load( projCtx, PJ_GRIDINFO * ); @@ -802,13 +821,13 @@ void *pj_gauss_ini(double, double, double *,double *); LP pj_gauss(projCtx, LP, const void *); LP pj_inv_gauss(projCtx, LP, const void *); -extern char const pj_release[]; +extern char const PROJ_DLL pj_release[]; -struct PJ_DATUMS *pj_get_datums_ref( void ); +struct PJ_DATUMS PROJ_DLL *pj_get_datums_ref( void ); void *pj_default_destructor (PJ *P, int errlev); -double pj_atof( const char* nptr ); +double PROJ_DLL pj_atof( const char* nptr ); double pj_strtod( const char *nptr, char **endptr ); void pj_freeup_plain (PJ *P); diff --git a/src/projinfo.cpp b/src/projinfo.cpp new file mode 100644 index 00000000..dbbcdae2 --- /dev/null +++ b/src/projinfo.cpp @@ -0,0 +1,914 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: projinfo utility + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +//! @cond Doxygen_Suppress + +#define FROM_PROJ_CPP + +#include <cstdlib> +#include <fstream> // std::ifstream +#include <iostream> +#include <utility> + +#include "projects.h" + +#include <proj/coordinateoperation.hpp> +#include <proj/crs.hpp> +#include <proj/io.hpp> +#include <proj/metadata.hpp> +#include <proj/util.hpp> + +#include "proj/internal/internal.hpp" // for split + +using namespace NS_PROJ::crs; +using namespace NS_PROJ::io; +using namespace NS_PROJ::metadata; +using namespace NS_PROJ::operation; +using namespace NS_PROJ::util; +using namespace NS_PROJ::internal; + +// --------------------------------------------------------------------------- + +struct OutputOptions { + bool quiet = false; + bool PROJ5 = false; + bool PROJ4 = false; + bool WKT2_2018 = false; + bool WKT2_2018_SIMPLIFIED = false; + bool WKT2_2015 = false; + bool WKT2_2015_SIMPLIFIED = false; + bool WKT1_GDAL = false; + bool WKT1_ESRI = false; + bool c_ify = false; +}; + +// --------------------------------------------------------------------------- + +static void usage() { + std::cerr + << "usage: projinfo [-o formats] [-k crs|operation] [--summary] [-q]" + << std::endl + << " [--bbox min_long,min_lat,max_long,max_lat] " + << std::endl + << " [--spatial-test contains|intersects]" << std::endl + << " [--crs-extent-use none|both|intersection|smallest]" + << std::endl + << " [--grid-check none|discard_missing|sort]" + << std::endl + << " [--pivot-crs none|{auth:code[,auth:code]*}]" + << std::endl + << " [--boundcrs-to-wgs84]" << std::endl + << " [--main-db-path path] [--aux-db-path path]*" + << std::endl + << " [--identify]" << std::endl + << " {object_definition} | (-s {srs_def} -t {srs_def})" + << std::endl; + std::cerr << std::endl; + std::cerr << "-o: formats is a comma separated combination of: " + "all,default,PROJ4,PROJ,WKT_ALL,WKT2_2015,WKT2_2018,WKT1_GDAL," + "WKT1_ESRI" + << std::endl; + std::cerr << " Except 'all' and 'default', other format can be preceded " + "by '-' to disable them" + << std::endl; + std::cerr << std::endl; + std::cerr << "{object_definition} might be a PROJ string, a WKT string, " + " a AUTHORITY:CODE, or urn:ogc:def:OBJECT_TYPE:AUTHORITY::CODE" + << std::endl; + std::exit(1); +} + +// --------------------------------------------------------------------------- + +static std::string un_c_ify_string(const std::string &str) { + std::string out(str); + out = out.substr(1, out.size() - 2); + out = replaceAll(out, "\\\"", "{ESCAPED_DOUBLE_QUOTE}"); + out = replaceAll(out, "\\n\"", ""); + out = replaceAll(out, "\"", ""); + out = replaceAll(out, "{ESCAPED_DOUBLE_QUOTE}", "\""); + return out; +} + +// --------------------------------------------------------------------------- + +static std::string c_ify_string(const std::string &str) { + std::string out(str); + out = replaceAll(out, "\"", "{DOUBLE_QUOTE}"); + out = replaceAll(out, "\n", "\\n\"\n\""); + out = replaceAll(out, "{DOUBLE_QUOTE}", "\\\""); + return "\"" + out + "\""; +} + +// --------------------------------------------------------------------------- + +static BaseObjectNNPtr buildObject(DatabaseContextPtr dbContext, + const std::string &user_string, + bool kindIsCRS, const std::string &context, + bool buildBoundCRSToWGS84) { + BaseObjectPtr obj; + + std::string l_user_string(user_string); + if (!user_string.empty() && user_string[0] == '@') { + std::ifstream fs; + auto filename = user_string.substr(1); + fs.open(filename, std::fstream::in | std::fstream::binary); + if (!fs.is_open()) { + std::cerr << context << ": cannot open " << filename << std::endl; + std::exit(1); + } + l_user_string.clear(); + while (!fs.eof()) { + char buffer[256]; + fs.read(buffer, sizeof(buffer)); + l_user_string.append(buffer, static_cast<size_t>(fs.gcount())); + if (l_user_string.size() > 100 * 1000) { + fs.close(); + std::cerr << context << ": too big file" << std::endl; + std::exit(1); + } + } + fs.close(); + } + if (!l_user_string.empty() && l_user_string.back() == '\n') { + l_user_string.resize(l_user_string.size() - 1); + } + if (!l_user_string.empty() && l_user_string.back() == '\r') { + l_user_string.resize(l_user_string.size() - 1); + } + + try { + auto tokens = split(l_user_string, ':'); + if (!kindIsCRS && tokens.size() == 2) { + auto urn = "urn:ogc:def:coordinateOperation:" + 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] == '"' && + l_user_string.back() == '"' && + l_user_string.find("\\\"") != std::string::npos) { + l_user_string = un_c_ify_string(l_user_string); + } + obj = createFromUserInput(l_user_string, dbContext).as_nullable(); + } + } catch (const std::exception &e) { + std::cerr << context << ": parsing of user string failed: " << e.what() + << std::endl; + std::exit(1); + } + + if (buildBoundCRSToWGS84) { + auto crs = std::dynamic_pointer_cast<CRS>(obj); + if (crs) { + obj = crs->createBoundCRSToWGS84IfPossible(dbContext).as_nullable(); + } + } + + return NN_NO_CHECK(obj); +} + +// --------------------------------------------------------------------------- + +static void outputObject(DatabaseContextPtr dbContext, BaseObjectNNPtr obj, + const OutputOptions &outputOpt) { + auto projStringExportable = + nn_dynamic_pointer_cast<IPROJStringExportable>(obj); + bool alreadyOutputed = false; + if (projStringExportable) { + if (outputOpt.PROJ5) { + try { + if (!outputOpt.quiet) { + std::cout << "PROJ string: " << std::endl; + } + std::cout << projStringExportable->exportToPROJString( + PROJStringFormatter::create( + PROJStringFormatter::Convention::PROJ_5, + dbContext) + .get()) + << std::endl; + } catch (const std::exception &e) { + std::cerr << "Error when exporting to PROJ string: " << e.what() + << std::endl; + } + alreadyOutputed = true; + } + + if (outputOpt.PROJ4) { + try { + if (alreadyOutputed) { + std::cout << std::endl; + } + if (!outputOpt.quiet) { + std::cout << "PROJ.4 string: " << std::endl; + } + + auto crs = nn_dynamic_pointer_cast<CRS>(obj); + std::shared_ptr<IPROJStringExportable> objToExport; + if (crs) { + objToExport = + nn_dynamic_pointer_cast<IPROJStringExportable>( + crs->createBoundCRSToWGS84IfPossible(dbContext)); + } + if (!objToExport) { + objToExport = projStringExportable; + } + + std::cout << objToExport->exportToPROJString( + PROJStringFormatter::create( + PROJStringFormatter::Convention::PROJ_4, + dbContext) + .get()) + << std::endl; + } catch (const std::exception &e) { + std::cerr << "Error when exporting to PROJ string: " << e.what() + << std::endl; + } + alreadyOutputed = true; + } + } + + auto wktExportable = nn_dynamic_pointer_cast<IWKTExportable>(obj); + if (wktExportable) { + if (outputOpt.WKT2_2015) { + try { + if (alreadyOutputed) { + std::cout << std::endl; + } + if (!outputOpt.quiet) { + std::cout << "WKT2_2015 string: " << std::endl; + } + auto wkt = wktExportable->exportToWKT( + WKTFormatter::create(WKTFormatter::Convention::WKT2_2015) + .get()); + if (outputOpt.c_ify) { + wkt = c_ify_string(wkt); + } + std::cout << wkt << std::endl; + } catch (const std::exception &e) { + std::cerr << "Error when exporting to WKT2_2015: " << e.what() + << std::endl; + } + alreadyOutputed = true; + } + + if (outputOpt.WKT2_2015_SIMPLIFIED) { + try { + if (alreadyOutputed) { + std::cout << std::endl; + } + if (!outputOpt.quiet) { + std::cout << "WKT2_2015_SIMPLIFIED string: " << std::endl; + } + auto wkt = wktExportable->exportToWKT( + WKTFormatter::create( + WKTFormatter::Convention::WKT2_2015_SIMPLIFIED) + .get()); + if (outputOpt.c_ify) { + wkt = c_ify_string(wkt); + } + std::cout << wkt << std::endl; + } catch (const std::exception &e) { + std::cerr << "Error when exporting to WKT2_2015_SIMPLIFIED: " + << e.what() << std::endl; + } + alreadyOutputed = true; + } + + if (outputOpt.WKT2_2018) { + try { + if (alreadyOutputed) { + std::cout << std::endl; + } + if (!outputOpt.quiet) { + std::cout << "WKT2_2018 string: " << std::endl; + } + auto wkt = wktExportable->exportToWKT( + WKTFormatter::create(WKTFormatter::Convention::WKT2_2018) + .get()); + if (outputOpt.c_ify) { + wkt = c_ify_string(wkt); + } + std::cout << wkt << std::endl; + } catch (const std::exception &e) { + std::cerr << "Error when exporting to WKT2_2018: " << e.what() + << std::endl; + } + alreadyOutputed = true; + } + + if (outputOpt.WKT2_2018_SIMPLIFIED) { + try { + if (alreadyOutputed) { + std::cout << std::endl; + } + if (!outputOpt.quiet) { + std::cout << "WKT2_2018_SIMPLIFIED string: " << std::endl; + } + auto wkt = wktExportable->exportToWKT( + WKTFormatter::create( + WKTFormatter::Convention::WKT2_2018_SIMPLIFIED) + .get()); + if (outputOpt.c_ify) { + wkt = c_ify_string(wkt); + } + std::cout << wkt << std::endl; + } catch (const std::exception &e) { + std::cerr << "Error when exporting to WKT2_2018_SIMPLIFIED: " + << e.what() << std::endl; + } + alreadyOutputed = true; + } + + if (outputOpt.WKT1_GDAL && !nn_dynamic_pointer_cast<Conversion>(obj)) { + try { + if (alreadyOutputed) { + std::cout << std::endl; + } + if (!outputOpt.quiet) { + std::cout << "WKT1_GDAL: " << 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)); + } + if (!objToExport) { + objToExport = wktExportable; + } + + auto wkt = objToExport->exportToWKT( + WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL) + .get()); + if (outputOpt.c_ify) { + wkt = c_ify_string(wkt); + } + std::cout << wkt << std::endl; + std::cout << std::endl; + } catch (const std::exception &e) { + std::cerr << "Error when exporting to WKT1_GDAL: " << e.what() + << std::endl; + } + alreadyOutputed = true; + } + + if (outputOpt.WKT1_ESRI && !nn_dynamic_pointer_cast<Conversion>(obj)) { + try { + if (alreadyOutputed) { + std::cout << std::endl; + } + if (!outputOpt.quiet) { + std::cout << "WKT1_ESRI: " << std::endl; + } + + auto wkt = wktExportable->exportToWKT( + WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, + dbContext) + .get()); + if (outputOpt.c_ify) { + wkt = c_ify_string(wkt); + } + std::cout << wkt << std::endl; + std::cout << std::endl; + } catch (const std::exception &e) { + std::cerr << "Error when exporting to WKT1_ESRI: " << e.what() + << std::endl; + } + } + } +} + +// --------------------------------------------------------------------------- + +static void outputOperations( + DatabaseContextPtr dbContext, const std::string &sourceCRSStr, + const std::string &targetCRSStr, const ExtentPtr &bboxFilter, + CoordinateOperationContext::SpatialCriterion spatialCriterion, + CoordinateOperationContext::SourceTargetCRSExtentUse crsExtentUse, + CoordinateOperationContext::GridAvailabilityUse gridAvailabilityUse, + bool allowPivots, + const std::vector<std::pair<std::string, std::string>> &pivots, + const std::string &authority, bool usePROJGridAlternatives, + const OutputOptions &outputOpt, bool summary) { + auto sourceObj = + buildObject(dbContext, sourceCRSStr, true, "source CRS", false); + auto sourceCRS = nn_dynamic_pointer_cast<CRS>(sourceObj); + if (!sourceCRS) { + std::cerr << "source CRS string is not a CRS" << std::endl; + std::exit(1); + } + + auto targetObj = + buildObject(dbContext, targetCRSStr, true, "target CRS", false); + auto targetCRS = nn_dynamic_pointer_cast<CRS>(targetObj); + if (!targetCRS) { + std::cerr << "target CRS string is not a CRS" << std::endl; + std::exit(1); + } + + std::vector<CoordinateOperationNNPtr> list; + try { + auto authFactory = + dbContext + ? AuthorityFactory::create(NN_NO_CHECK(dbContext), authority) + .as_nullable() + : nullptr; + auto ctxt = + CoordinateOperationContext::create(authFactory, bboxFilter, 0); + ctxt->setSpatialCriterion(spatialCriterion); + ctxt->setSourceAndTargetCRSExtentUse(crsExtentUse); + ctxt->setGridAvailabilityUse(gridAvailabilityUse); + ctxt->setAllowUseIntermediateCRS(allowPivots); + ctxt->setIntermediateCRS(pivots); + ctxt->setUsePROJAlternativeGridNames(usePROJGridAlternatives); + list = CoordinateOperationFactory::create()->createOperations( + NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), ctxt); + } catch (const std::exception &e) { + std::cerr << "createOperations() failed with: " << e.what() + << std::endl; + std::exit(1); + } + if (outputOpt.quiet && !list.empty()) { + outputObject(dbContext, list[0], outputOpt); + return; + } + if (summary) { + std::cout << "Candidate operations found: " << list.size() << std::endl; + for (const auto &op : list) { + auto ids = op->identifiers(); + if (!ids.empty()) { + std::cout << *(ids[0]->codeSpace()) << ":" << ids[0]->code(); + } else { + std::cout << "unknown id"; + } + + std::cout << ", "; + + auto name = op->nameStr(); + if (!name.empty()) { + std::cout << name; + } else { + std::cout << "unknown name"; + } + + std::cout << ", "; + + auto accuracies = op->coordinateOperationAccuracies(); + if (!accuracies.empty()) { + std::cout << accuracies[0]->value() << " m"; + } else { + if (std::dynamic_pointer_cast<Conversion>(op.as_nullable())) { + std::cout << "0 m"; + } else { + std::cout << "unknown accuracy"; + } + } + + std::cout << ", "; + + auto domains = op->domains(); + if (!domains.empty() && domains[0]->domainOfValidity() && + domains[0]->domainOfValidity()->description().has_value()) { + std::cout << *(domains[0]->domainOfValidity()->description()); + } else { + std::cout << "unknown domain of validity"; + } + + std::cout << std::endl; + } + } else { + bool first = true; + for (size_t i = 0; i < list.size(); ++i) { + const auto &op = list[i]; + if (list.size() > 1) { + if (!first) { + std::cout << std::endl; + } + first = false; + std::cout << "-------------------------------------" + << std::endl; + std::cout << "Operation n" + "\xC2\xB0" + << (i + 1) << ":" << std::endl + << std::endl; + } + outputObject(dbContext, op, outputOpt); + } + } +} + +// --------------------------------------------------------------------------- + +int main(int argc, char **argv) { + + if (argc == 1) { + std::cerr << pj_get_release() << std::endl; + usage(); + } + + std::string user_string; + bool user_string_specified = false; + std::string sourceCRSStr; + std::string targetCRSStr; + bool outputSwithSpecified = false; + OutputOptions outputOpt; + bool kindIsCRS = true; + bool summary = false; + ExtentPtr bboxFilter = nullptr; + CoordinateOperationContext::SpatialCriterion spatialCriterion = + CoordinateOperationContext::SpatialCriterion::STRICT_CONTAINMENT; + CoordinateOperationContext::SourceTargetCRSExtentUse crsExtentUse = + CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST; + bool buildBoundCRSToWGS84 = false; + CoordinateOperationContext::GridAvailabilityUse gridAvailabilityUse = + CoordinateOperationContext::GridAvailabilityUse::USE_FOR_SORTING; + bool allowPivots = true; + std::vector<std::pair<std::string, std::string>> pivots; + bool usePROJGridAlternatives = true; + std::string mainDBPath; + std::vector<std::string> auxDBPath; + bool guessDialect = false; + std::string authority; + bool identify = false; + + for (int i = 1; i < argc; i++) { + std::string arg(argv[i]); + if (arg == "-o" && i + 1 < argc) { + outputSwithSpecified = true; + i++; + auto formats(split(argv[i], ',')); + for (auto format : formats) { + if (ci_equal(format, "all")) { + outputOpt.PROJ5 = true; + outputOpt.PROJ4 = true; + outputOpt.WKT2_2018 = true; + outputOpt.WKT2_2015 = true; + outputOpt.WKT1_GDAL = true; + } else if (ci_equal(format, "default")) { + outputOpt.PROJ5 = true; + outputOpt.PROJ4 = false; + outputOpt.WKT2_2018 = false; + outputOpt.WKT2_2015 = true; + outputOpt.WKT1_GDAL = false; + } else if (ci_equal(format, "PROJ4") || + ci_equal(format, "PROJ.4")) { + outputOpt.PROJ4 = true; + } else if (ci_equal(format, "-PROJ4") || + ci_equal(format, "-PROJ.4")) { + outputOpt.PROJ4 = false; + } else if (ci_equal(format, "PROJ")) { + outputOpt.PROJ5 = true; + } else if (ci_equal(format, "-PROJ")) { + outputOpt.PROJ5 = false; + } else if (ci_equal(format, "WKT_ALL") || + ci_equal(format, "WKT_ALL")) { + outputOpt.WKT2_2018 = true; + outputOpt.WKT2_2015 = true; + outputOpt.WKT1_GDAL = true; + } else if (ci_equal(format, "-WKT_ALL") || + ci_equal(format, "-WKT_ALL")) { + outputOpt.WKT2_2018 = false; + outputOpt.WKT2_2015 = false; + outputOpt.WKT1_GDAL = false; + } else if (ci_equal(format, "WKT2_2018") || + ci_equal(format, "WKT2-2018") || + ci_equal(format, "WKT2:2018")) { + outputOpt.WKT2_2018 = true; + } else if (ci_equal(format, "WKT2_2018_SIMPLIFIED") || + ci_equal(format, "WKT2-2018_SIMPLIFIED") || + ci_equal(format, "WKT2:2018_SIMPLIFIED")) { + outputOpt.WKT2_2018_SIMPLIFIED = true; + } else if (ci_equal(format, "-WKT2_2018") || + ci_equal(format, "-WKT2-2018") || + ci_equal(format, "-WKT2:2018")) { + outputOpt.WKT2_2018 = false; + } else if (ci_equal(format, "WKT2_2015") || + ci_equal(format, "WKT2-2015") || + ci_equal(format, "WKT2:2015")) { + outputOpt.WKT2_2015 = true; + } else if (ci_equal(format, "WKT2_2015_SIMPLIFIED") || + ci_equal(format, "WKT2-2015_SIMPLIFIED") || + ci_equal(format, "WKT2:2015_SIMPLIFIED")) { + outputOpt.WKT2_2015_SIMPLIFIED = true; + } else if (ci_equal(format, "-WKT2_2015") || + ci_equal(format, "-WKT2-2015") || + ci_equal(format, "-WKT2:2015")) { + outputOpt.WKT2_2015 = false; + } else if (ci_equal(format, "WKT1_GDAL") || + ci_equal(format, "WKT1-GDAL") || + ci_equal(format, "WKT1:GDAL")) { + outputOpt.WKT1_GDAL = true; + } else if (ci_equal(format, "-WKT1_GDAL") || + ci_equal(format, "-WKT1-GDAL") || + ci_equal(format, "-WKT1:GDAL")) { + outputOpt.WKT1_GDAL = false; + } else if (ci_equal(format, "WKT1_ESRI") || + ci_equal(format, "WKT1-ESRI") || + ci_equal(format, "WKT1:ESRI")) { + outputOpt.WKT1_ESRI = true; + } else if (ci_equal(format, "-WKT1_ESRI") || + ci_equal(format, "-WKT1-ESRI") || + ci_equal(format, "-WKT1:ESRI")) { + outputOpt.WKT1_ESRI = false; + } else { + std::cerr << "Unrecognized value for option -o: " << format + << std::endl; + usage(); + } + } + } else if (arg == "--bbox" && i + 1 < argc) { + i++; + auto bboxStr(argv[i]); + auto bbox(split(bboxStr, ',')); + if (bbox.size() != 4) { + std::cerr << "Incorrect number of values for option --bbox: " + << bboxStr << std::endl; + usage(); + } + try { + bboxFilter = Extent::createFromBBOX( + c_locale_stod(bbox[0]), c_locale_stod(bbox[1]), + c_locale_stod(bbox[2]), c_locale_stod(bbox[3])) + .as_nullable(); + } catch (const std::exception &e) { + std::cerr << "Invalid value for option --bbox: " << bboxStr + << ", " << e.what() << std::endl; + usage(); + } + } else if (arg == "-k" && i + 1 < argc) { + i++; + std::string kind(argv[i]); + if (ci_equal(kind, "crs") || ci_equal(kind, "srs")) { + kindIsCRS = true; + } else if (ci_equal(kind, "operation")) { + kindIsCRS = false; + } else { + std::cerr << "Unrecognized value for option -k: " << kind + << std::endl; + usage(); + } + } else if ((arg == "-s" || arg == "--ssource-crs") && i + 1 < argc) { + i++; + sourceCRSStr = argv[i]; + } else if ((arg == "-t" || arg == "--target-crs") && i + 1 < argc) { + i++; + targetCRSStr = argv[i]; + } else if (arg == "-q" || arg == "--quiet") { + outputOpt.quiet = true; + } else if (arg == "--c-ify") { + outputOpt.c_ify = true; + } else if (arg == "--summary") { + summary = true; + } else if (ci_equal(arg, "--boundcrs-to-wgs84")) { + buildBoundCRSToWGS84 = true; + + // undocumented: only for debugging purposes + } else if (ci_equal(arg, "--no-proj-grid-alternatives")) { + usePROJGridAlternatives = false; + + } else if (arg == "--spatial-test" && i + 1 < argc) { + i++; + std::string value(argv[i]); + if (ci_equal(value, "contains")) { + spatialCriterion = CoordinateOperationContext:: + SpatialCriterion::STRICT_CONTAINMENT; + } else if (ci_equal(value, "intersects")) { + spatialCriterion = CoordinateOperationContext:: + SpatialCriterion::PARTIAL_INTERSECTION; + } else { + std::cerr << "Unrecognized value for option --patial-test: " + << value << std::endl; + usage(); + } + } else if (arg == "--crs-extent-use" && i + 1 < argc) { + i++; + std::string value(argv[i]); + if (ci_equal(value, "NONE")) { + crsExtentUse = + CoordinateOperationContext::SourceTargetCRSExtentUse::NONE; + } else if (ci_equal(value, "BOTH")) { + crsExtentUse = + CoordinateOperationContext::SourceTargetCRSExtentUse::BOTH; + } else if (ci_equal(value, "INTERSECTION")) { + crsExtentUse = CoordinateOperationContext:: + SourceTargetCRSExtentUse::INTERSECTION; + } else if (ci_equal(value, "SMALLEST")) { + crsExtentUse = CoordinateOperationContext:: + SourceTargetCRSExtentUse::SMALLEST; + } else { + std::cerr << "Unrecognized value for option --crs-extent-use: " + << value << std::endl; + usage(); + } + } else if (arg == "--grid-check" && i + 1 < argc) { + i++; + std::string value(argv[i]); + if (ci_equal(value, "none")) { + gridAvailabilityUse = CoordinateOperationContext:: + GridAvailabilityUse::IGNORE_GRID_AVAILABILITY; + } else if (ci_equal(value, "discard_missing")) { + gridAvailabilityUse = CoordinateOperationContext:: + GridAvailabilityUse::DISCARD_OPERATION_IF_MISSING_GRID; + } else if (ci_equal(value, "sort")) { + gridAvailabilityUse = CoordinateOperationContext:: + GridAvailabilityUse::USE_FOR_SORTING; + } else { + std::cerr << "Unrecognized value for option --grid-check: " + << value << std::endl; + usage(); + } + } else if (arg == "--pivot-crs" && i + 1 < argc) { + i++; + auto value(argv[i]); + if (ci_equal(std::string(value), "none")) { + allowPivots = false; + } else { + auto splitValue(split(value, ',')); + for (const auto &v : splitValue) { + auto auth_code = split(v, ':'); + if (auth_code.size() != 2) { + std::cerr + << "Unrecognized value for option --grid-check: " + << value << std::endl; + usage(); + } + pivots.emplace_back( + std::make_pair(auth_code[0], auth_code[1])); + } + } + } else if (arg == "--main-db-path" && i + 1 < argc) { + i++; + mainDBPath = argv[i]; + } else if (arg == "--aux-db-path" && i + 1 < argc) { + i++; + auxDBPath.push_back(argv[i]); + } else if (arg == "--guess-dialect") { + guessDialect = true; + } else if (arg == "--authority" && i + 1 < argc) { + i++; + authority = argv[i]; + } else if (arg == "--identify") { + identify = true; + } else if (arg == "-?" || arg == "--help") { + usage(); + } else if (arg[0] == '-') { + std::cerr << "Unrecognized option: " << arg << std::endl; + usage(); + } else { + if (!user_string_specified) { + user_string_specified = true; + user_string = arg; + } else { + std::cerr << "Too many parameters: " << arg << std::endl; + usage(); + } + } + } + + DatabaseContextPtr dbContext; + try { + dbContext = + DatabaseContext::create(mainDBPath, auxDBPath).as_nullable(); + } catch (const std::exception &e) { + if (!mainDBPath.empty() || !auxDBPath.empty()) { + std::cerr << "ERROR: Cannot create database connection: " + << e.what() << std::endl; + std::exit(1); + } + std::cerr << "WARNING: Cannot create database connection: " << e.what() + << std::endl; + } + + if (!sourceCRSStr.empty() && targetCRSStr.empty()) { + std::cerr << "Source CRS specified, but missing target CRS" + << std::endl; + usage(); + } else if (sourceCRSStr.empty() && !targetCRSStr.empty()) { + std::cerr << "Target CRS specified, but missing source CRS" + << std::endl; + usage(); + } else if (!sourceCRSStr.empty() && !targetCRSStr.empty()) { + if (user_string_specified) { + std::cerr << "Unused extra value" << std::endl; + usage(); + } + } else if (!user_string_specified) { + std::cerr << "Missing user string" << std::endl; + usage(); + } + + if (!outputSwithSpecified) { + outputOpt.PROJ5 = true; + outputOpt.WKT2_2015 = true; + } + + if (outputOpt.quiet && + (outputOpt.PROJ5 + outputOpt.PROJ4 + outputOpt.WKT2_2018 + + outputOpt.WKT2_2015 + outputOpt.WKT1_GDAL) != 1) { + std::cerr << "-q can only be used with a single output format" + << std::endl; + usage(); + } + + if (!user_string.empty()) { + auto obj(buildObject(dbContext, user_string, kindIsCRS, "input string", + buildBoundCRSToWGS84)); + if (guessDialect) { + auto dialect = WKTParser().guessDialect(user_string); + std::cout << "Guessed WKT dialect: "; + if (dialect == WKTParser::WKTGuessedDialect::WKT2_2018) { + std::cout << "WKT2_2018"; + } else if (dialect == WKTParser::WKTGuessedDialect::WKT2_2015) { + std::cout << "WKT2_2015"; + } else if (dialect == WKTParser::WKTGuessedDialect::WKT1_GDAL) { + std::cout << "WKT1_GDAL"; + } else if (dialect == WKTParser::WKTGuessedDialect::WKT1_ESRI) { + std::cout << "WKT1_ESRI"; + } else { + std::cout << "Not WKT / unknown"; + } + std::cout << std::endl; + } + outputObject(dbContext, obj, outputOpt); + if (identify) { + auto crs = dynamic_cast<CRS *>(obj.get()); + if (crs) { + try { + auto res = crs->identify( + dbContext + ? AuthorityFactory::create(NN_NO_CHECK(dbContext), + authority) + .as_nullable() + : nullptr); + std::cout << std::endl; + std::cout << "Identification match count: " << res.size() + << std::endl; + for (const auto &pair : res) { + const auto &identifiedCRS = pair.first; + const auto &ids = identifiedCRS->identifiers(); + if (!ids.empty()) { + std::cout << *ids[0]->codeSpace() << ":" + << ids[0]->code() << ": " << pair.second + << " %" << std::endl; + } else { + auto boundCRS = + dynamic_cast<BoundCRS *>(identifiedCRS.get()); + if (boundCRS && + !boundCRS->baseCRS()->identifiers().empty()) { + const auto &idsBase = + boundCRS->baseCRS()->identifiers(); + std::cout << "BoundCRS of " + << *idsBase[0]->codeSpace() << ":" + << idsBase[0]->code() << ": " + << pair.second << " %" << std::endl; + } else { + std::cout + << "un-identifier CRS: " << pair.second + << " %" << std::endl; + } + } + } + } catch (const std::exception &e) { + std::cerr << "Identification failed: " << e.what() + << std::endl; + } + } + } + } else { + outputOperations(dbContext, sourceCRSStr, targetCRSStr, bboxFilter, + spatialCriterion, crsExtentUse, gridAvailabilityUse, + allowPivots, pivots, authority, + usePROJGridAlternatives, outputOpt, summary); + } + + return 0; +} + +//! @endcond diff --git a/src/static.cpp b/src/static.cpp new file mode 100644 index 00000000..e30e68b7 --- /dev/null +++ b/src/static.cpp @@ -0,0 +1,638 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/common.hpp" +#include "proj/coordinatesystem.hpp" +#include "proj/crs.hpp" +#include "proj/datum.hpp" +#include "proj/io.hpp" +#include "proj/metadata.hpp" +#include "proj/util.hpp" + +#include "proj/internal/coordinatesystem_internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include <map> +#include <set> +#include <string> + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +// We put all static definitions in the same compilation unit, and in +// increasing order of dependency, to avoid the "static initialization fiasco" +// See https://isocpp.org/wiki/faq/ctors#static-init-order + +using namespace NS_PROJ::crs; +using namespace NS_PROJ::datum; +using namespace NS_PROJ::io; +using namespace NS_PROJ::metadata; +using namespace NS_PROJ::util; + +NS_PROJ_START + +// --------------------------------------------------------------------------- + +/** \brief Key to set the authority citation of a metadata::Identifier. + * + * The value is to be provided as a string or a metadata::Citation. + */ +const std::string Identifier::AUTHORITY_KEY("authority"); + +/** \brief Key to set the code of a metadata::Identifier. + * + * The value is to be provided as a integer or a string. + */ +const std::string Identifier::CODE_KEY("code"); + +/** \brief Key to set the organization responsible for definition and + * maintenance of the code of a metadata::Identifier. + * + * The value is to be provided as a string. + */ +const std::string Identifier::CODESPACE_KEY("codespace"); + +/** \brief Key to set the version identifier for the namespace of a + * metadata::Identifier. + * + * The value is to be provided as a string. + */ +const std::string Identifier::VERSION_KEY("version"); + +/** \brief Key to set the natural language description of the meaning of the + * code value of a metadata::Identifier. + * + * The value is to be provided as a string. + */ +const std::string Identifier::DESCRIPTION_KEY("description"); + +/** \brief Key to set the URI of a metadata::Identifier. + * + * The value is to be provided as a string. + */ +const std::string Identifier::URI_KEY("uri"); + +/** \brief EPSG codespace. + */ +const std::string Identifier::EPSG("EPSG"); + +/** \brief OGC codespace. + */ +const std::string Identifier::OGC("OGC"); + +// --------------------------------------------------------------------------- + +/** \brief Key to set the name of a common::IdentifiedObject + * + * The value is to be provided as a string or metadata::IdentifierNNPtr. + */ +const std::string common::IdentifiedObject::NAME_KEY("name"); + +/** \brief Key to set the identifier(s) of a common::IdentifiedObject + * + * The value is to be provided as a common::IdentifierNNPtr or a + * util::ArrayOfBaseObjectNNPtr + * of common::IdentifierNNPtr. + */ +const std::string common::IdentifiedObject::IDENTIFIERS_KEY("identifiers"); + +/** \brief Key to set the alias(es) of a common::IdentifiedObject + * + * The value is to be provided as string, a util::GenericNameNNPtr or a + * util::ArrayOfBaseObjectNNPtr + * of util::GenericNameNNPtr. + */ +const std::string common::IdentifiedObject::ALIAS_KEY("alias"); + +/** \brief Key to set the remarks of a common::IdentifiedObject + * + * The value is to be provided as a string. + */ +const std::string common::IdentifiedObject::REMARKS_KEY("remarks"); + +/** \brief Key to set the deprecation flag of a common::IdentifiedObject + * + * The value is to be provided as a boolean. + */ +const std::string common::IdentifiedObject::DEPRECATED_KEY("deprecated"); + +// --------------------------------------------------------------------------- + +/** \brief Key to set the scope of a common::ObjectUsage + * + * The value is to be provided as a string. + */ +const std::string common::ObjectUsage::SCOPE_KEY("scope"); + +/** \brief Key to set the domain of validity of a common::ObjectUsage + * + * The value is to be provided as a common::ExtentNNPtr. + */ +const std::string + common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY("domainOfValidity"); + +/** \brief Key to set the object domain(s) of a common::ObjectUsage + * + * The value is to be provided as a common::ObjectDomainNNPtr or a + * util::ArrayOfBaseObjectNNPtr + * of common::ObjectDomainNNPtr. + */ +const std::string common::ObjectUsage::OBJECT_DOMAIN_KEY("objectDomain"); + +// --------------------------------------------------------------------------- + +/** \brief World extent. */ +const ExtentNNPtr + Extent::WORLD(Extent::createFromBBOX(-180, -90, 180, 90, + util::optional<std::string>("World"))); + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::vector<std::string> WKTConstants::constants_; + +const char *WKTConstants::createAndAddToConstantList(const char *text) { + WKTConstants::constants_.push_back(text); + return text; +} + +#define DEFINE_WKT_CONSTANT(x) \ + const std::string WKTConstants::x(createAndAddToConstantList(#x)) + +DEFINE_WKT_CONSTANT(GEOCCS); +DEFINE_WKT_CONSTANT(GEOGCS); +DEFINE_WKT_CONSTANT(DATUM); +DEFINE_WKT_CONSTANT(UNIT); +DEFINE_WKT_CONSTANT(SPHEROID); +DEFINE_WKT_CONSTANT(AXIS); +DEFINE_WKT_CONSTANT(PRIMEM); +DEFINE_WKT_CONSTANT(AUTHORITY); +DEFINE_WKT_CONSTANT(PROJCS); +DEFINE_WKT_CONSTANT(PROJECTION); +DEFINE_WKT_CONSTANT(PARAMETER); +DEFINE_WKT_CONSTANT(VERT_CS); +DEFINE_WKT_CONSTANT(VERT_DATUM); +DEFINE_WKT_CONSTANT(COMPD_CS); +DEFINE_WKT_CONSTANT(TOWGS84); +DEFINE_WKT_CONSTANT(EXTENSION); +DEFINE_WKT_CONSTANT(LOCAL_CS); +DEFINE_WKT_CONSTANT(LOCAL_DATUM); + +DEFINE_WKT_CONSTANT(GEODCRS); +DEFINE_WKT_CONSTANT(LENGTHUNIT); +DEFINE_WKT_CONSTANT(ANGLEUNIT); +DEFINE_WKT_CONSTANT(SCALEUNIT); +DEFINE_WKT_CONSTANT(TIMEUNIT); +DEFINE_WKT_CONSTANT(ELLIPSOID); +DEFINE_WKT_CONSTANT(CS); +DEFINE_WKT_CONSTANT(ID); +DEFINE_WKT_CONSTANT(PROJCRS); +DEFINE_WKT_CONSTANT(BASEGEODCRS); +DEFINE_WKT_CONSTANT(MERIDIAN); +DEFINE_WKT_CONSTANT(ORDER); +DEFINE_WKT_CONSTANT(ANCHOR); +DEFINE_WKT_CONSTANT(CONVERSION); +DEFINE_WKT_CONSTANT(METHOD); +DEFINE_WKT_CONSTANT(REMARK); +DEFINE_WKT_CONSTANT(GEOGCRS); +DEFINE_WKT_CONSTANT(BASEGEOGCRS); +DEFINE_WKT_CONSTANT(SCOPE); +DEFINE_WKT_CONSTANT(AREA); +DEFINE_WKT_CONSTANT(BBOX); +DEFINE_WKT_CONSTANT(CITATION); +DEFINE_WKT_CONSTANT(URI); +DEFINE_WKT_CONSTANT(VERTCRS); +DEFINE_WKT_CONSTANT(VDATUM); +DEFINE_WKT_CONSTANT(COMPOUNDCRS); +DEFINE_WKT_CONSTANT(PARAMETERFILE); +DEFINE_WKT_CONSTANT(COORDINATEOPERATION); +DEFINE_WKT_CONSTANT(SOURCECRS); +DEFINE_WKT_CONSTANT(TARGETCRS); +DEFINE_WKT_CONSTANT(INTERPOLATIONCRS); +DEFINE_WKT_CONSTANT(OPERATIONACCURACY); +DEFINE_WKT_CONSTANT(CONCATENATEDOPERATION); +DEFINE_WKT_CONSTANT(STEP); +DEFINE_WKT_CONSTANT(BOUNDCRS); +DEFINE_WKT_CONSTANT(ABRIDGEDTRANSFORMATION); +DEFINE_WKT_CONSTANT(DERIVINGCONVERSION); +DEFINE_WKT_CONSTANT(TDATUM); +DEFINE_WKT_CONSTANT(CALENDAR); +DEFINE_WKT_CONSTANT(TIMEORIGIN); +DEFINE_WKT_CONSTANT(TIMECRS); +DEFINE_WKT_CONSTANT(VERTICALEXTENT); +DEFINE_WKT_CONSTANT(TIMEEXTENT); +DEFINE_WKT_CONSTANT(USAGE); +DEFINE_WKT_CONSTANT(DYNAMIC); +DEFINE_WKT_CONSTANT(FRAMEEPOCH); +DEFINE_WKT_CONSTANT(MODEL); +DEFINE_WKT_CONSTANT(VELOCITYGRID); +DEFINE_WKT_CONSTANT(ENSEMBLE); +DEFINE_WKT_CONSTANT(MEMBER); +DEFINE_WKT_CONSTANT(ENSEMBLEACCURACY); +DEFINE_WKT_CONSTANT(DERIVEDPROJCRS); +DEFINE_WKT_CONSTANT(BASEPROJCRS); +DEFINE_WKT_CONSTANT(EDATUM); +DEFINE_WKT_CONSTANT(ENGCRS); +DEFINE_WKT_CONSTANT(PDATUM); +DEFINE_WKT_CONSTANT(PARAMETRICCRS); +DEFINE_WKT_CONSTANT(PARAMETRICUNIT); +DEFINE_WKT_CONSTANT(BASEVERTCRS); +DEFINE_WKT_CONSTANT(BASEENGCRS); +DEFINE_WKT_CONSTANT(BASEPARAMCRS); +DEFINE_WKT_CONSTANT(BASETIMECRS); + +DEFINE_WKT_CONSTANT(GEODETICCRS); +DEFINE_WKT_CONSTANT(GEODETICDATUM); +DEFINE_WKT_CONSTANT(PROJECTEDCRS); +DEFINE_WKT_CONSTANT(PRIMEMERIDIAN); +DEFINE_WKT_CONSTANT(GEOGRAPHICCRS); +DEFINE_WKT_CONSTANT(TRF); +DEFINE_WKT_CONSTANT(VERTICALCRS); +DEFINE_WKT_CONSTANT(VERTICALDATUM); +DEFINE_WKT_CONSTANT(VRF); +DEFINE_WKT_CONSTANT(TIMEDATUM); +DEFINE_WKT_CONSTANT(ENGINEERINGDATUM); +DEFINE_WKT_CONSTANT(ENGINEERINGCRS); +DEFINE_WKT_CONSTANT(PARAMETRICDATUM); + +//! @endcond + +// --------------------------------------------------------------------------- + +namespace common { + +/** \brief "Empty"/"None", unit of measure of type NONE. */ +const UnitOfMeasure UnitOfMeasure::NONE("", 1.0, UnitOfMeasure::Type::NONE); + +/** \brief Scale unity, unit of measure of type SCALE. */ +const UnitOfMeasure UnitOfMeasure::SCALE_UNITY("unity", 1.0, + UnitOfMeasure::Type::SCALE, + Identifier::EPSG, "9201"); + +/** \brief Parts-per-million, unit of measure of type SCALE. */ +const UnitOfMeasure UnitOfMeasure::PARTS_PER_MILLION("parts per million", 1e-6, + UnitOfMeasure::Type::SCALE, + Identifier::EPSG, "9202"); + +/** \brief Metre, unit of measure of type LINEAR (SI unit). */ +const UnitOfMeasure UnitOfMeasure::METRE("metre", 1.0, + UnitOfMeasure::Type::LINEAR, + Identifier::EPSG, "9001"); + +/** \brief Degree, unit of measure of type ANGULAR. */ +const UnitOfMeasure UnitOfMeasure::DEGREE("degree", M_PI / 180., + UnitOfMeasure::Type::ANGULAR, + Identifier::EPSG, "9122"); + +/** \brief Arc-second, unit of measure of type ANGULAR. */ +const UnitOfMeasure UnitOfMeasure::ARC_SECOND("arc-second", M_PI / 180. / 3600., + UnitOfMeasure::Type::ANGULAR, + Identifier::EPSG, "9104"); + +/** \brief Grad, unit of measure of type ANGULAR. */ +const UnitOfMeasure UnitOfMeasure::GRAD("grad", M_PI / 200., + UnitOfMeasure::Type::ANGULAR, + Identifier::EPSG, "9105"); + +/** \brief Radian, unit of measure of type ANGULAR (SI unit). */ +const UnitOfMeasure UnitOfMeasure::RADIAN("radian", 1.0, + UnitOfMeasure::Type::ANGULAR, + Identifier::EPSG, "9101"); + +/** \brief Microradian, unit of measure of type ANGULAR. */ +const UnitOfMeasure UnitOfMeasure::MICRORADIAN("microradian", 1e-6, + UnitOfMeasure::Type::ANGULAR, + Identifier::EPSG, "9109"); + +/** \brief Second, unit of measure of type TIME (SI unit). */ +const UnitOfMeasure UnitOfMeasure::SECOND("second", 1.0, + UnitOfMeasure::Type::TIME, + Identifier::EPSG, "1029"); + +/** \brief Year, unit of measure of type TIME */ +const UnitOfMeasure UnitOfMeasure::YEAR("year", 31556925.445, + UnitOfMeasure::Type::TIME, + Identifier::EPSG, "1040"); + +/** \brief Metre per year, unit of measure of type LINEAR. */ +const UnitOfMeasure UnitOfMeasure::METRE_PER_YEAR("metres per year", + 1.0 / 31556925.445, + UnitOfMeasure::Type::LINEAR, + Identifier::EPSG, "1042"); + +/** \brief Arc-second per year, unit of measure of type ANGULAR. */ +const UnitOfMeasure UnitOfMeasure::ARC_SECOND_PER_YEAR( + "arc-seconds per year", M_PI / 180. / 3600. / 31556925.445, + UnitOfMeasure::Type::ANGULAR, Identifier::EPSG, "1043"); + +/** \brief Part-sper-million per year, unit of measure of type SCALE. */ +const UnitOfMeasure UnitOfMeasure::PPM_PER_YEAR("parts per million per year", + 1e-6 / 31556925.445, + UnitOfMeasure::Type::SCALE, + Identifier::EPSG, "1036"); + +} // namespace common + +// --------------------------------------------------------------------------- + +namespace cs { +std::map<std::string, const AxisDirection *> AxisDirection::registry; + +/** Axis positive direction is north. In a geodetic or projected CRS, north is + * defined through the geodetic reference frame. In an engineering CRS, north + * may be defined with respect to an engineering object rather than a + * geographical direction. */ +const AxisDirection AxisDirection::NORTH("north"); + +/** Axis positive direction is approximately north-north-east. */ +const AxisDirection AxisDirection::NORTH_NORTH_EAST("northNorthEast"); + +/** Axis positive direction is approximately north-east. */ +const AxisDirection AxisDirection::NORTH_EAST("northEast"); + +/** Axis positive direction is approximately east-north-east. */ +const AxisDirection AxisDirection::EAST_NORTH_EAST("eastNorthEast"); + +/** Axis positive direction is 90deg clockwise from north. */ +const AxisDirection AxisDirection::EAST("east"); + +/** Axis positive direction is approximately east-south-east. */ +const AxisDirection AxisDirection::EAST_SOUTH_EAST("eastSouthEast"); + +/** Axis positive direction is approximately south-east. */ +const AxisDirection AxisDirection::SOUTH_EAST("southEast"); + +/** Axis positive direction is approximately south-south-east. */ +const AxisDirection AxisDirection::SOUTH_SOUTH_EAST("southSouthEast"); + +/** Axis positive direction is 180deg clockwise from north. */ +const AxisDirection AxisDirection::SOUTH("south"); + +/** Axis positive direction is approximately south-south-west. */ +const AxisDirection AxisDirection::SOUTH_SOUTH_WEST("southSouthWest"); + +/** Axis positive direction is approximately south-west. */ +const AxisDirection AxisDirection::SOUTH_WEST("southWest"); + +/** Axis positive direction is approximately west-south-west. */ +const AxisDirection AxisDirection::WEST_SOUTH_WEST("westSouthWest"); + +/** Axis positive direction is 270deg clockwise from north. */ +const AxisDirection AxisDirection::WEST("west"); + +/** Axis positive direction is approximately west-north-west. */ +const AxisDirection AxisDirection::WEST_NORTH_WEST("westNorthWest"); + +/** Axis positive direction is approximately north-west. */ +const AxisDirection AxisDirection::NORTH_WEST("northWest"); + +/** Axis positive direction is approximately north-north-west. */ +const AxisDirection AxisDirection::NORTH_NORTH_WEST("northNorthWest"); + +/** Axis positive direction is up relative to gravity. */ +const AxisDirection AxisDirection::UP("up"); + +/** Axis positive direction is down relative to gravity. */ +const AxisDirection AxisDirection::DOWN("down"); + +/** Axis positive direction is in the equatorial plane from the centre of the + * modelled Earth towards the intersection of the equator with the prime + * meridian. */ +const AxisDirection AxisDirection::GEOCENTRIC_X("geocentricX"); + +/** Axis positive direction is in the equatorial plane from the centre of the + * modelled Earth towards the intersection of the equator and the meridian 90deg + * eastwards from the prime meridian. */ +const AxisDirection AxisDirection::GEOCENTRIC_Y("geocentricY"); + +/** Axis positive direction is from the centre of the modelled Earth parallel to + * its rotation axis and towards its north pole. */ +const AxisDirection AxisDirection::GEOCENTRIC_Z("geocentricZ"); + +/** Axis positive direction is towards higher pixel column. */ +const AxisDirection AxisDirection::COLUMN_POSITIVE("columnPositive"); + +/** Axis positive direction is towards lower pixel column. */ +const AxisDirection AxisDirection::COLUMN_NEGATIVE("columnNegative"); + +/** Axis positive direction is towards higher pixel row. */ +const AxisDirection AxisDirection::ROW_POSITIVE("rowPositive"); + +/** Axis positive direction is towards lower pixel row. */ +const AxisDirection AxisDirection::ROW_NEGATIVE("rowNegative"); + +/** Axis positive direction is right in display. */ +const AxisDirection AxisDirection::DISPLAY_RIGHT("displayRight"); + +/** Axis positive direction is left in display. */ +const AxisDirection AxisDirection::DISPLAY_LEFT("displayLeft"); + +/** Axis positive direction is towards top of approximately vertical display + * surface. */ +const AxisDirection AxisDirection::DISPLAY_UP("displayUp"); + +/** Axis positive direction is towards bottom of approximately vertical display + * surface. */ +const AxisDirection AxisDirection::DISPLAY_DOWN("displayDown"); + +/** Axis positive direction is forward; for an observer at the centre of the + * object this is will be towards its front, bow or nose. */ +const AxisDirection AxisDirection::FORWARD("forward"); + +/** Axis positive direction is aft; for an observer at the centre of the object + * this will be towards its back, stern or tail. */ +const AxisDirection AxisDirection::AFT("aft"); + +/** Axis positive direction is port; for an observer at the centre of the object + * this will be towards its left. */ +const AxisDirection AxisDirection::PORT("port"); + +/** Axis positive direction is starboard; for an observer at the centre of the + * object this will be towards its right. */ +const AxisDirection AxisDirection::STARBOARD("starboard"); + +/** Axis positive direction is clockwise from a specified direction. */ +const AxisDirection AxisDirection::CLOCKWISE("clockwise"); + +/** Axis positive direction is counter clockwise from a specified direction. */ +const AxisDirection AxisDirection::COUNTER_CLOCKWISE("counterClockwise"); + +/** Axis positive direction is towards the object. */ +const AxisDirection AxisDirection::TOWARDS("towards"); + +/** Axis positive direction is away from the object. */ +const AxisDirection AxisDirection::AWAY_FROM("awayFrom"); + +/** Temporal axis positive direction is towards the future. */ +const AxisDirection AxisDirection::FUTURE("future"); + +/** Temporal axis positive direction is towards the past. */ +const AxisDirection AxisDirection::PAST("past"); + +/** Axis positive direction is unspecified. */ +const AxisDirection AxisDirection::UNSPECIFIED("unspecified"); + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::map<std::string, const AxisDirectionWKT1 *> AxisDirectionWKT1::registry; + +const AxisDirectionWKT1 AxisDirectionWKT1::NORTH("NORTH"); +const AxisDirectionWKT1 AxisDirectionWKT1::EAST("EAST"); +const AxisDirectionWKT1 AxisDirectionWKT1::SOUTH("SOUTH"); +const AxisDirectionWKT1 AxisDirectionWKT1::WEST("WEST"); +const AxisDirectionWKT1 AxisDirectionWKT1::UP("UP"); +const AxisDirectionWKT1 AxisDirectionWKT1::DOWN("DOWN"); +const AxisDirectionWKT1 AxisDirectionWKT1::OTHER("OTHER"); + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const std::string AxisName::Longitude("Longitude"); +const std::string AxisName::Latitude("Latitude"); +const std::string AxisName::Easting("Easting"); +const std::string AxisName::Northing("Northing"); +const std::string AxisName::Westing("Westing"); +const std::string AxisName::Southing("Southing"); +const std::string AxisName::Ellipsoidal_height("Ellipsoidal height"); +const std::string AxisName::Geocentric_X("Geocentric X"); +const std::string AxisName::Geocentric_Y("Geocentric Y"); +const std::string AxisName::Geocentric_Z("Geocentric Z"); +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const std::string AxisAbbreviation::lon("lon"); +const std::string AxisAbbreviation::lat("lat"); +const std::string AxisAbbreviation::E("E"); +const std::string AxisAbbreviation::N("N"); +const std::string AxisAbbreviation::h("h"); +const std::string AxisAbbreviation::X("X"); +const std::string AxisAbbreviation::Y("Y"); +const std::string AxisAbbreviation::Z("Z"); +//! @endcond + +} // namespace cs + +// --------------------------------------------------------------------------- + +/** \brief The realization is by adjustment of a levelling network fixed to one + * or more tide gauges. */ +const RealizationMethod RealizationMethod::LEVELLING("levelling"); +/** \brief The realization is through a geoid height model or a height + * correction model. This is applied to a specified geodetic CRS. */ +const RealizationMethod RealizationMethod::GEOID("geoid"); +/** \brief The realization is through a tidal model or by tidal predictions. */ +const RealizationMethod RealizationMethod::TIDAL("tidal"); + +// --------------------------------------------------------------------------- + +/** \brief The Greenwich PrimeMeridian */ +const PrimeMeridianNNPtr + PrimeMeridian::GREENWICH(PrimeMeridian::createGREENWICH()); +/** \brief The Paris PrimeMeridian */ +const PrimeMeridianNNPtr PrimeMeridian::PARIS(PrimeMeridian::createPARIS()); + +// --------------------------------------------------------------------------- + +/** \brief Earth celestial body */ +const std::string Ellipsoid::EARTH("Earth"); + +/** \brief The EPSG:7008 / "Clarke 1866" Ellipsoid */ +const EllipsoidNNPtr Ellipsoid::CLARKE_1866(Ellipsoid::createCLARKE_1866()); + +/** \brief The EPSG:7030 / "WGS 84" Ellipsoid */ +const EllipsoidNNPtr Ellipsoid::WGS84(Ellipsoid::createWGS84()); + +/** \brief The EPSG:7019 / "GRS 1980" Ellipsoid */ +const EllipsoidNNPtr Ellipsoid::GRS1980(Ellipsoid::createGRS1980()); + +// --------------------------------------------------------------------------- + +/** \brief The EPSG:6267 / "North_American_Datum_1927" GeodeticReferenceFrame */ +const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::EPSG_6267( + GeodeticReferenceFrame::createEPSG_6267()); + +/** \brief The EPSG:6269 / "North_American_Datum_1983" GeodeticReferenceFrame */ +const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::EPSG_6269( + GeodeticReferenceFrame::createEPSG_6269()); + +/** \brief The EPSG:6326 / "WGS_1984" GeodeticReferenceFrame */ +const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::EPSG_6326( + GeodeticReferenceFrame::createEPSG_6326()); + +// --------------------------------------------------------------------------- + +/** \brief The proleptic Gregorian calendar. */ +const std::string + TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN("proleptic Gregorian"); + +// --------------------------------------------------------------------------- + +/** \brief EPSG:4978 / "WGS 84" Geocentric */ +const GeodeticCRSNNPtr GeodeticCRS::EPSG_4978(GeodeticCRS::createEPSG_4978()); + +// --------------------------------------------------------------------------- + +/** \brief EPSG:4267 / "NAD27" 2D GeographicCRS */ +const GeographicCRSNNPtr + GeographicCRS::EPSG_4267(GeographicCRS::createEPSG_4267()); + +/** \brief EPSG:4269 / "NAD83" 2D GeographicCRS */ +const GeographicCRSNNPtr + GeographicCRS::EPSG_4269(GeographicCRS::createEPSG_4269()); + +/** \brief EPSG:4326 / "WGS 84" 2D GeographicCRS */ +const GeographicCRSNNPtr + GeographicCRS::EPSG_4326(GeographicCRS::createEPSG_4326()); + +/** \brief OGC:CRS84 / "CRS 84" 2D GeographicCRS (long, lat)*/ +const GeographicCRSNNPtr + GeographicCRS::OGC_CRS84(GeographicCRS::createOGC_CRS84()); + +/** \brief EPSG:4807 / "NTF (Paris)" 2D GeographicCRS */ +const GeographicCRSNNPtr + GeographicCRS::EPSG_4807(GeographicCRS::createEPSG_4807()); + +/** \brief EPSG:4979 / "WGS 84" 3D GeographicCRS */ +const GeographicCRSNNPtr + GeographicCRS::EPSG_4979(GeographicCRS::createEPSG_4979()); + +// --------------------------------------------------------------------------- + +NS_PROJ_END diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 00000000..b3a5149d --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,667 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2018 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +#include "proj/util.hpp" +#include "proj/io.hpp" + +#include "proj/internal/internal.hpp" + +#include <map> +#include <memory> +#include <string> + +using namespace NS_PROJ::internal; + +#if 0 +namespace dropbox{ namespace oxygen { +template<> nn<NS_PROJ::util::BaseObjectPtr>::~nn() = default; +template<> nn<NS_PROJ::util::BoxedValuePtr>::~nn() = default; +template<> nn<NS_PROJ::util::ArrayOfBaseObjectPtr>::~nn() = default; +template<> nn<NS_PROJ::util::LocalNamePtr>::~nn() = default; +template<> nn<NS_PROJ::util::GenericNamePtr>::~nn() = default; +template<> nn<NS_PROJ::util::NameSpacePtr>::~nn() = default; +}} +#endif + +NS_PROJ_START +namespace util { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct BaseObject::Private { + // This is a manual implementation of std::enable_shared_from_this<> that + // avoids publicly deriving from it. + std::weak_ptr<BaseObject> self_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +BaseObject::BaseObject() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +BaseObject::~BaseObject() = default; + +// --------------------------------------------------------------------------- + +BaseObjectNNPtr::~BaseObjectNNPtr() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** Keep a reference to ourselves as an internal weak pointer. So that + * extractGeographicBaseObject() can later return a shared pointer on itself. + */ +void BaseObject::assignSelf(const BaseObjectNNPtr &self) { + assert(self.get() == this); + d->self_ = self.as_nullable(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +BaseObjectNNPtr BaseObject::shared_from_this() const { + // This assertion checks that in all code paths where we create a + // shared pointer, we took care of assigning it to self_, by calling + // assignSelf(); + return NN_CHECK_ASSERT(d->self_.lock()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct BoxedValue::Private { + BoxedValue::Type type_{BoxedValue::Type::INTEGER}; + std::string stringValue_{}; + int integerValue_{}; + bool booleanValue_{}; + + explicit Private(const std::string &stringValueIn) + : type_(BoxedValue::Type::STRING), stringValue_(stringValueIn) {} + + explicit Private(int integerValueIn) + : type_(BoxedValue::Type::INTEGER), integerValue_(integerValueIn) {} + + explicit Private(bool booleanValueIn) + : type_(BoxedValue::Type::BOOLEAN), booleanValue_(booleanValueIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +BoxedValue::BoxedValue() : d(internal::make_unique<Private>(std::string())) {} + +// --------------------------------------------------------------------------- + +/** \brief Constructs a BoxedValue from a string. + */ +BoxedValue::BoxedValue(const char *stringValueIn) + : d(internal::make_unique<Private>( + std::string(stringValueIn ? stringValueIn : ""))) {} + +// --------------------------------------------------------------------------- + +/** \brief Constructs a BoxedValue from a string. + */ +BoxedValue::BoxedValue(const std::string &stringValueIn) + : d(internal::make_unique<Private>(stringValueIn)) {} + +// --------------------------------------------------------------------------- + +/** \brief Constructs a BoxedValue from an integer. + */ +BoxedValue::BoxedValue(int integerValueIn) + : d(internal::make_unique<Private>(integerValueIn)) {} + +// --------------------------------------------------------------------------- + +/** \brief Constructs a BoxedValue from a boolean. + */ +BoxedValue::BoxedValue(bool booleanValueIn) + : d(internal::make_unique<Private>(booleanValueIn)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +BoxedValue::BoxedValue(const BoxedValue &other) + : d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +BoxedValue::~BoxedValue() = default; + +// --------------------------------------------------------------------------- + +const BoxedValue::Type &BoxedValue::type() const { return d->type_; } + +// --------------------------------------------------------------------------- + +const std::string &BoxedValue::stringValue() const { return d->stringValue_; } + +// --------------------------------------------------------------------------- + +int BoxedValue::integerValue() const { return d->integerValue_; } + +// --------------------------------------------------------------------------- + +bool BoxedValue::booleanValue() const { return d->booleanValue_; } +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ArrayOfBaseObject::Private { + std::vector<BaseObjectNNPtr> values_{}; +}; +//! @endcond +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ArrayOfBaseObject::ArrayOfBaseObject() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +ArrayOfBaseObject::~ArrayOfBaseObject() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Adds an object to the array. + * + * @param obj the object to add. + */ +void ArrayOfBaseObject::add(const BaseObjectNNPtr &obj) { + d->values_.emplace_back(obj); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::vector<BaseObjectNNPtr>::const_iterator ArrayOfBaseObject::begin() const { + return d->values_.begin(); +} + +// --------------------------------------------------------------------------- + +std::vector<BaseObjectNNPtr>::const_iterator ArrayOfBaseObject::end() const { + return d->values_.end(); +} + +// --------------------------------------------------------------------------- + +bool ArrayOfBaseObject::empty() const { return d->values_.empty(); } +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ArrayOfBaseObject. + * + * @return a new ArrayOfBaseObject. + */ +ArrayOfBaseObjectNNPtr ArrayOfBaseObject::create() { + return ArrayOfBaseObject::nn_make_shared<ArrayOfBaseObject>(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct PropertyMap::Private { + std::map<std::string, BaseObjectNNPtr> map_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +PropertyMap::PropertyMap() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PropertyMap::PropertyMap(const PropertyMap &other) + : d(internal::make_unique<Private>(*(other.d))) {} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +PropertyMap::~PropertyMap() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +std::map<std::string, BaseObjectNNPtr>::iterator +PropertyMap::find(const std::string &key) const { + return d->map_.find(key); +} + +// --------------------------------------------------------------------------- + +std::map<std::string, BaseObjectNNPtr>::iterator PropertyMap::end() const { + return d->map_.end(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Set a BaseObjectNNPtr as the value of a key. */ +PropertyMap &PropertyMap::set(const std::string &key, + const BaseObjectNNPtr &val) { + auto iter = d->map_.find(key); + if (iter != d->map_.end()) { + iter->second = val; + } else { + d->map_.insert(std::pair<std::string, BaseObjectNNPtr>(key, val)); + } + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set a BoxedValue as the value of a key. */ +PropertyMap &PropertyMap::set(const std::string &key, const BoxedValue &val) { + auto iter = d->map_.find(key); + if (iter != d->map_.end()) { + iter->second = util::nn_make_shared<BoxedValue>(val); + } else { + d->map_.insert(std::pair<std::string, BaseObjectNNPtr>( + key, util::nn_make_shared<BoxedValue>(val))); + } + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set a string as the value of a key. */ +PropertyMap &PropertyMap::set(const std::string &key, const std::string &val) { + return set(key, BoxedValue(val)); +} + +// --------------------------------------------------------------------------- + +/** \brief Set a string as the value of a key. */ +PropertyMap &PropertyMap::set(const std::string &key, const char *val) { + return set(key, BoxedValue(val)); +} + +// --------------------------------------------------------------------------- + +/** \brief Set a integer as the value of a key. */ +PropertyMap &PropertyMap::set(const std::string &key, int val) { + return set(key, BoxedValue(val)); +} + +// --------------------------------------------------------------------------- + +/** \brief Set a boolean as the value of a key. */ +PropertyMap &PropertyMap::set(const std::string &key, bool val) { + return set(key, BoxedValue(val)); +} + +// --------------------------------------------------------------------------- + +/** \brief Set a vector of strings as the value of a key. */ +PropertyMap &PropertyMap::set(const std::string &key, + const std::vector<std::string> &arrayIn) { + ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create(); + for (const auto &str : arrayIn) { + array->add(util::nn_make_shared<BoxedValue>(str)); + } + return set(key, array); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool PropertyMap::getStringValue( + const std::string &key, + std::string &outVal) const // throw(InvalidValueTypeException) +{ + auto oIter = d->map_.find(key); + if (oIter == d->map_.end()) { + return false; + } + auto genVal = dynamic_cast<const BoxedValue *>(oIter->second.get()); + if (genVal && genVal->type() == BoxedValue::Type::STRING) { + outVal = genVal->stringValue(); + return true; + } + throw InvalidValueTypeException("Invalid value type for " + key); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GenericName::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +GenericName::GenericName() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +GenericName::GenericName(const GenericName &other) + : d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GenericName::~GenericName() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct NameSpace::Private { + GenericNamePtr name{}; + bool isGlobal{}; + std::string separator = std::string(":"); + std::string separatorHead = std::string(":"); +}; +//! @endcond + +// --------------------------------------------------------------------------- + +NameSpace::NameSpace(const GenericNamePtr &nameIn) + : d(internal::make_unique<Private>()) { + d->name = nameIn; +} + +// --------------------------------------------------------------------------- + +NameSpace::NameSpace(const NameSpace &other) + : d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +NameSpace::~NameSpace() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns whether this is a global namespace. */ +bool NameSpace::isGlobal() const { return d->isGlobal; } + +// --------------------------------------------------------------------------- + +NameSpaceNNPtr NameSpace::getGlobalFromThis() const { + NameSpaceNNPtr ns(NameSpace::nn_make_shared<NameSpace>(*this)); + ns->d->isGlobal = true; + ns->d->name = LocalName::make_shared<LocalName>("global"); + return ns; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the name of this namespace. */ +const GenericNamePtr &NameSpace::name() const { return d->name; } + +// --------------------------------------------------------------------------- + +const std::string &NameSpace::separator() const { return d->separator; } + +// --------------------------------------------------------------------------- + +NameSpaceNNPtr NameSpace::createGLOBAL() { + NameSpaceNNPtr ns(NameSpace::nn_make_shared<NameSpace>( + LocalName::make_shared<LocalName>("global"))); + ns->d->isGlobal = true; + return ns; +} + +// --------------------------------------------------------------------------- + +const NameSpaceNNPtr NameSpace::GLOBAL(NameSpace::createGLOBAL()); + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct LocalName::Private { + NameSpacePtr scope{}; + std::string name{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +LocalName::LocalName(const std::string &name) + : d(internal::make_unique<Private>()) { + d->name = name; +} + +// --------------------------------------------------------------------------- + +LocalName::LocalName(const NameSpacePtr &ns, const std::string &name) + : d(internal::make_unique<Private>()) { + d->scope = ns ? ns : static_cast<NameSpacePtr>(NameSpace::GLOBAL); + d->name = name; +} + +// --------------------------------------------------------------------------- + +LocalName::LocalName(const LocalName &other) + : GenericName(other), d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +LocalName::~LocalName() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +const NameSpacePtr LocalName::scope() const { + if (d->scope) + return d->scope; + return NameSpace::GLOBAL; +} + +// --------------------------------------------------------------------------- + +GenericNameNNPtr LocalName::toFullyQualifiedName() const { + if (scope()->isGlobal()) + return LocalName::nn_make_shared<LocalName>(*this); + + return LocalName::nn_make_shared<LocalName>( + d->scope->getGlobalFromThis(), + d->scope->name()->toFullyQualifiedName()->toString() + + d->scope->separator() + d->name); +} + +// --------------------------------------------------------------------------- + +std::string LocalName::toString() const { return d->name; } + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a NameSpace. + * + * @param name name of the namespace. + * @param properties Properties. Allowed keys are "separator" and + * "separator.head". + * @return a new NameFactory. + */ +NameSpaceNNPtr NameFactory::createNameSpace(const GenericNameNNPtr &name, + const PropertyMap &properties) { + NameSpaceNNPtr ns(NameSpace::nn_make_shared<NameSpace>(name)); + properties.getStringValue("separator", ns->d->separator); + properties.getStringValue("separator.head", ns->d->separatorHead); + + return ns; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a LocalName. + * + * @param scope scope. + * @param name string of the local name. + * @return a new LocalName. + */ +LocalNameNNPtr NameFactory::createLocalName(const NameSpacePtr &scope, + const std::string &name) { + return LocalName::nn_make_shared<LocalName>(scope, name); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GenericName. + * + * @param scope scope. + * @param parsedNames the components of the name. + * @return a new GenericName. + */ +GenericNameNNPtr +NameFactory::createGenericName(const NameSpacePtr &scope, + const std::vector<std::string> &parsedNames) { + std::string name; + const std::string separator(scope ? scope->separator() + : NameSpace::GLOBAL->separator()); + bool first = true; + for (const auto &str : parsedNames) { + if (!first) + name += separator; + first = false; + name += str; + } + return LocalName::nn_make_shared<LocalName>(scope, name); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CodeList::~CodeList() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CodeList &CodeList::operator=(const CodeList &other) { + name_ = other.name_; + return *this; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +Exception::Exception(const char *message) : msg_(message) {} + +// --------------------------------------------------------------------------- + +Exception::Exception(const std::string &message) : msg_(message) {} + +// --------------------------------------------------------------------------- + +Exception::Exception(const Exception &) = default; + +// --------------------------------------------------------------------------- + +Exception::~Exception() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** Return the exception text. */ +const char *Exception::what() const noexcept { return msg_.c_str(); } + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +InvalidValueTypeException::InvalidValueTypeException(const char *message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +InvalidValueTypeException::InvalidValueTypeException(const std::string &message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +InvalidValueTypeException::~InvalidValueTypeException() = default; + +// --------------------------------------------------------------------------- + +InvalidValueTypeException::InvalidValueTypeException( + const InvalidValueTypeException &) = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +UnsupportedOperationException::UnsupportedOperationException( + const char *message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +UnsupportedOperationException::UnsupportedOperationException( + const std::string &message) + : Exception(message) {} + +// --------------------------------------------------------------------------- + +UnsupportedOperationException::~UnsupportedOperationException() = default; + +// --------------------------------------------------------------------------- + +UnsupportedOperationException::UnsupportedOperationException( + const UnsupportedOperationException &) = default; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +IComparable::~IComparable() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Returns whether an object is equivalent to another one. + * @param other other object to compare to + * @param criterion comparaison criterion. + * @return true if objects are equivalent. + */ +bool IComparable::isEquivalentTo(const IComparable *other, + Criterion criterion) const { + return _isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +} // namespace util +NS_PROJ_END |
