aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2018-11-14 17:40:42 +0100
committerEven Rouault <even.rouault@spatialys.com>2018-11-14 22:48:29 +0100
commitd928db15d53805d9b728b440079756081961c536 (patch)
treee862a961d26bedb34c58e4f28ef0bdeedb5f3225 /src
parent330e8bf686f9c4524075ca1ff50cbca6c9e091da (diff)
downloadPROJ-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')
-rw-r--r--src/CMakeLists.txt6
-rw-r--r--src/Makefile.am19
-rw-r--r--src/PJ_unitconvert.c19
-rw-r--r--src/bin_cct.cmake4
-rw-r--r--src/bin_cs2cs.cmake8
-rw-r--r--src/bin_geod.cmake7
-rw-r--r--src/bin_geodtest.cmake4
-rw-r--r--src/bin_gie.cmake4
-rw-r--r--src/bin_nad2bin.cmake8
-rw-r--r--src/bin_proj.cmake7
-rw-r--r--src/bin_projinfo.cmake16
-rw-r--r--src/c_api.cpp4038
-rw-r--r--src/common.cpp1125
-rw-r--r--src/coordinateoperation.cpp10915
-rw-r--r--src/coordinatesystem.cpp1107
-rw-r--r--src/crs.cpp4490
-rw-r--r--src/datum.cpp1983
-rw-r--r--src/emess.h6
-rw-r--r--src/factory.cpp4350
-rw-r--r--src/general_doc.dox158
-rw-r--r--src/geodesic.h56
-rw-r--r--src/internal.cpp298
-rw-r--r--src/io.cpp6827
-rw-r--r--src/lib_proj.cmake53
-rw-r--r--src/metadata.cpp1233
-rw-r--r--src/pj_ctx.c4
-rw-r--r--src/pj_ellps.c4
-rw-r--r--src/pj_units.c16
-rw-r--r--src/proj.def156
-rw-r--r--src/proj.h1100
-rw-r--r--src/proj_api.h149
-rw-r--r--src/proj_constants.h580
-rw-r--r--src/proj_internal.h22
-rw-r--r--src/proj_math.h18
-rw-r--r--src/proj_symbol_rename.h273
-rw-r--r--src/projects.h47
-rw-r--r--src/projinfo.cpp914
-rw-r--r--src/static.cpp638
-rw-r--r--src/util.cpp667
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 &parameters = 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 &param = parameters[index];
+ const auto &param_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 &paramList : 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 &param_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> &parameters) {
+ 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> &parameters) {
+ 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 &params = 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 &parameterIn,
+ 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 &parameterIn,
+ 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 &parameterIn,
+ 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 &paramName, double &val,
+ const common::UnitOfMeasure *&unit, int &paramEPSGCode) {
+ 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 &paramName,
+ int epsg_code) const noexcept {
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = 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 &parameter = 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 &paramName,
+ 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> &parameters,
+ 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 &centerLat,
+ const common::Angle &centerLong, 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 &centerLat,
+ const common::Angle &centerLong, 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 &centerLat,
+ const common::Angle &centerLong, 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 &centerLat,
+ const common::Angle &centerLong, 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 &lt;= 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 &centerLat,
+ const common::Angle &centerLong, 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 &lt;= 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 &lt;= 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 &centerLat,
+ const common::Angle &centerLong, 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
+ *&lt;= 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 &centerLat,
+ const common::Angle &centerLong, 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 &centerLong,
+ 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 &centerLong,
+ 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 &centerLong,
+ 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 &centerLong,
+ 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 &centerLong,
+ 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 &centerLong,
+ 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 &lt;= 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 &lt;= 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 &centerLong,
+ 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 &centerLong,
+ 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 &lt;= 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 &centerLong,
+ 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 &centerLong,
+ 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 &centerLong,
+ 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 &centerLat,
+ const common::Angle &centerLong, 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 &gt;=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 &gt;=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 &lt;=
+ *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 &centerLong,
+ 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 &centerLong,
+ 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 &centerLat,
+ const common::Angle &centerLong, 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 &centerLong,
+ 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 &centerLat,
+ const common::Angle &centerLong, 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 &centerLong,
+ 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 &centerLat,
+ const common::Angle &centerLong, 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 &centerLat,
+ const common::Angle &centerLong, 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 &centerLat,
+ const common::Angle &centerLong, 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 &centerLat,
+ const common::Angle &centerLong, 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 &centerLat,
+ const common::Angle &centerLong, 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 &centerLong,
+ 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 &centerLong,
+ 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 &centerLat,
+ const common::Angle &centerLong, 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 &centerLong,
+ 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 &centerLong,
+ 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 &centerLong,
+ 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 &centerLong, 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 &centerLong,
+ 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 &centerLong,
+ 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 &centerLong,
+ 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 &centerLong,
+ 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 &centerLat,
+ const common::Angle &centerLong, 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 &centerLong,
+ 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 &paramValue : 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 &paramName = opParamvalue->parameter()->nameStr();
+ const auto &paramValue = 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 &parameter = 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> &parameters,
+ 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 &paramValue : 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> &map;
+
+ 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 &paramValue : 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> &parameters = 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> &parameters) {
+
+ 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 &param : 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> &parameters = 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> &parameters) {
+ 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 &param_auth_name = row[base_param_idx + i * 6 + 0];
+ if (param_auth_name.empty()) {
+ break;
+ }
+ const auto &param_code = row[base_param_idx + i * 6 + 1];
+ const auto &param_name = row[base_param_idx + i * 6 + 2];
+ const auto &param_value = row[base_param_idx + i * 6 + 3];
+ const auto &param_uom_auth_name = row[base_param_idx + i * 6 + 4];
+ const auto &param_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 &param_auth_name = row[base_param_idx + i * 6 + 0];
+ if (param_auth_name.empty()) {
+ break;
+ }
+ const auto &param_code = row[base_param_idx + i * 6 + 1];
+ const auto &param_name = row[base_param_idx + i * 6 + 2];
+ const auto &param_value = row[base_param_idx + i * 6 + 3];
+ const auto &param_uom_auth_name =
+ row[base_param_idx + i * 6 + 4];
+ const auto &param_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> &params, 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 &parameterValue = 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 &minus; \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 [&minus;90&deg;, 90&deg;].
**********************************************************************/
- 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> &params) {
+ 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 &paramName,
+ const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit);
+
+ void consumeParameters(const WKTNodeNNPtr &node, bool isAbridged,
+ std::vector<OperationParameterNNPtr> &parameters,
+ 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 &paramName, 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> &parameters,
+ 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 &paramValue = 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 &paramName =
+ childNodeChildren[0]->GP()->value();
+ unit = guessUnitForParameter(
+ paramName, defaultLinearUnit, defaultAngularUnit);
+ }
+
+ if (isAbridged) {
+ const auto &paramName = parameters.back()->nameStr();
+ int paramEPSGCode = 0;
+ const auto &paramIds = 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 &paramValue = 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 &paramValue = 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 &param : 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 &paramValue : 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 &paramValue : 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 &paramName) {
+ 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 &paramName, 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 &paramName, 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 &paramName,
+ const char *val) {
+ addParam(paramName, std::string(val));
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::addParam(const std::string &paramName,
+ 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> &params) {
+ 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 &param : 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 &paramValue,
+ 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 &paramValue,
+ 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 &param : 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 &param) {
+ 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 &param : 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 &param : 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
diff --git a/src/proj.h b/src/proj.h
index 282d1487..39d16b30 100644
--- a/src/proj.h
+++ b/src/proj.h
@@ -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