aboutsummaryrefslogtreecommitdiff
path: root/src/iso19111
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2018-12-19 12:25:33 +0100
committerEven Rouault <even.rouault@spatialys.com>2018-12-26 10:08:54 +0100
commite6de172371ea203f6393d745641d66c82b5b13e2 (patch)
tree791fa07f431a2d1db6f6e813ab984db982587278 /src/iso19111
parentce8075076b4e4ffebd32afaba419e1d9ab27cd03 (diff)
downloadPROJ-e6de172371ea203f6393d745641d66c82b5b13e2.tar.gz
PROJ-e6de172371ea203f6393d745641d66c82b5b13e2.zip
cpp conversion: move source files in apps/ iso19111/ conversions/ projections/ transformations/ tests/ subdirectories
Diffstat (limited to 'src/iso19111')
-rw-r--r--src/iso19111/c_api.cpp6540
-rw-r--r--src/iso19111/common.cpp1115
-rw-r--r--src/iso19111/coordinateoperation.cpp11749
-rw-r--r--src/iso19111/coordinatesystem.cpp1279
-rw-r--r--src/iso19111/crs.cpp4971
-rw-r--r--src/iso19111/datum.cpp1996
-rw-r--r--src/iso19111/factory.cpp4973
-rw-r--r--src/iso19111/internal.cpp374
-rw-r--r--src/iso19111/io.cpp7501
-rw-r--r--src/iso19111/metadata.cpp1285
-rw-r--r--src/iso19111/static.cpp644
-rw-r--r--src/iso19111/util.cpp689
12 files changed, 43116 insertions, 0 deletions
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
new file mode 100644
index 00000000..d0b5d720
--- /dev/null
+++ b/src/iso19111/c_api.cpp
@@ -0,0 +1,6540 @@
+/******************************************************************************
+ *
+ * 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/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/internal.hpp"
+
+// PROJ include order is sensitive
+// clang-format off
+#include "proj_internal.h"
+#include "proj.h"
+#include "proj_experimental.h"
+#include "projects.h"
+// clang-format on
+#include "proj_constants.h"
+
+using namespace NS_PROJ::common;
+using namespace NS_PROJ::crs;
+using namespace NS_PROJ::cs;
+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
+ IdentifiedObjectNNPtr obj;
+
+ // cached results
+ mutable std::string lastWKT{};
+ mutable std::string lastPROJString{};
+ mutable bool gridsNeededAsked = false;
+ mutable std::vector<GridDescription> gridsNeeded{};
+
+ explicit PJ_OBJ(const IdentifiedObjectNNPtr &objIn) : obj(objIn) {}
+ static PJ_OBJ *create(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(const IdentifiedObjectNNPtr &objIn) {
+ return new PJ_OBJ(objIn);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Opaque object representing a set of operation results. */
+struct PJ_OBJ_LIST {
+ //! @cond Doxygen_Suppress
+ std::vector<IdentifiedObjectNNPtr> objects;
+
+ explicit PJ_OBJ_LIST(std::vector<IdentifiedObjectNNPtr> &&objectsIn)
+ : 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;
+ std::string lastUOMName_{};
+
+ explicit projCppContext(PJ_CONTEXT *ctx, const char *dbPath = nullptr,
+ const char *const *auxDbPaths = nullptr)
+ : databaseContext(DatabaseContext::create(
+ dbPath ? dbPath : std::string(), toVector(auxDbPaths))) {
+ databaseContext->attachPJContext(ctx);
+ }
+
+ 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 Return a metadata from the database.
+ *
+ * The returned pointer remains valid while ctx is valid, and until
+ * proj_context_get_database_metadata() is called.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param key Metadata key. Must not be NULL
+ * @return value, or nullptr
+ */
+const char *proj_context_get_database_metadata(PJ_CONTEXT *ctx,
+ const char *key) {
+ SANITIZE_CTX(ctx);
+ try {
+ return getDBcontext(ctx)->getMetadata(key);
+ } 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;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @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 "Clone" an object.
+ *
+ * Technically this just increases the reference counter on the object, since
+ * PJ_OBJ objects are immutable.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Object to clone. Must not be NULL.
+ * @return Object that must be unreferenced with proj_obj_destroy(), or NULL in
+ * case of error.
+ */
+PJ_OBJ *proj_obj_clone(PJ_CONTEXT *ctx, const PJ_OBJ *obj) {
+ SANITIZE_CTX(ctx);
+ try {
+ return PJ_OBJ::create(obj->obj);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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_destroy() 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 null-terminated list of options, or NULL. Currently
+ * supported options are:
+ * <ul>
+ * <li>USE_PROJ4_INIT_RULES=YES/NO. Defaults to NO. When set to YES,
+ * init=epsg:XXXX syntax will be allowed and will be interpreted according to
+ * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
+ * order and will expect/output coordinates in radians. ProjectedCRS will have
+ * easting, northing axis order (except the ones with Transverse Mercator South
+ * Orientated projection). In that mode, the epsg:XXXX syntax will be also
+ * interprated the same way.</li>
+ * </ul>
+ * @return Object that must be unreferenced with proj_obj_destroy(), 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 {
+ bool usePROJ4InitRules = false;
+ for (auto iter = options; iter && iter[0]; ++iter) {
+ const char *value;
+ if ((value = getOptionValue(*iter, "USE_PROJ4_INIT_RULES="))) {
+ usePROJ4InitRules = ci_equal(value, "YES");
+ } else {
+ std::string msg("Unknown option :");
+ msg += *iter;
+ proj_log_error(ctx, __FUNCTION__, msg.c_str());
+ return nullptr;
+ }
+ }
+ auto identifiedObject = nn_dynamic_pointer_cast<IdentifiedObject>(
+ createFromUserInput(text, dbContext, usePROJ4InitRules));
+ if (identifiedObject) {
+ return PJ_OBJ::create(NN_NO_CHECK(identifiedObject));
+ }
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+template <class T> static PROJ_STRING_LIST to_string_list(T &&set) {
+ auto ret = new char *[set.size() + 1];
+ size_t i = 0;
+ for (const auto &str : set) {
+ try {
+ ret[i] = new char[str.size() + 1];
+ } catch (const std::exception &) {
+ while (--i > 0) {
+ delete[] ret[i];
+ }
+ delete[] ret;
+ throw;
+ }
+ std::memcpy(ret[i], str.c_str(), str.size() + 1);
+ i++;
+ }
+ ret[i] = nullptr;
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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_destroy() 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 null-terminated list of options, or NULL. Currently
+ * supported options are:
+ * <ul>
+ * <li>STRICT=YES/NO. Defaults to NO. When set to YES, strict validation will
+ * be enabled.</li>
+ * </ul>
+ * @param out_warnings Pointer to a PROJ_STRING_LIST object, or NULL.
+ * If provided, *out_warnings will contain a list of warnings, typically for
+ * non recognized projection method or parameters. It must be freed with
+ * proj_string_list_destroy().
+ * @param out_grammar_errors Pointer to a PROJ_STRING_LIST object, or NULL.
+ * If provided, *out_grammar_errors will contain a list of errors regarding the
+ * WKT grammaer. It must be freed with proj_string_list_destroy().
+ * @return Object that must be unreferenced with proj_obj_destroy(), or NULL in
+ * case of error.
+ */
+PJ_OBJ *proj_obj_create_from_wkt(PJ_CONTEXT *ctx, const char *wkt,
+ const char *const *options,
+ PROJ_STRING_LIST *out_warnings,
+ PROJ_STRING_LIST *out_grammar_errors) {
+ SANITIZE_CTX(ctx);
+ assert(wkt);
+
+ if (out_warnings) {
+ *out_warnings = nullptr;
+ }
+ if (out_grammar_errors) {
+ *out_grammar_errors = nullptr;
+ }
+
+ try {
+ WKTParser parser;
+ auto dbContext = getDBcontextNoException(ctx, __FUNCTION__);
+ if (dbContext) {
+ parser.attachDatabaseContext(NN_NO_CHECK(dbContext));
+ }
+ for (auto iter = options; iter && iter[0]; ++iter) {
+ const char *value;
+ if ((value = getOptionValue(*iter, "STRICT="))) {
+ parser.setStrict(ci_equal(value, "YES"));
+ } else {
+ std::string msg("Unknown option :");
+ msg += *iter;
+ proj_log_error(ctx, __FUNCTION__, msg.c_str());
+ return nullptr;
+ }
+ }
+ auto obj = nn_dynamic_pointer_cast<IdentifiedObject>(
+ parser.createFromWKT(wkt));
+
+ if (out_grammar_errors) {
+ auto warnings = parser.warningList();
+ if (!warnings.empty()) {
+ *out_grammar_errors = to_string_list(warnings);
+ }
+ }
+
+ if (obj && out_warnings) {
+ auto derivedCRS = dynamic_cast<const crs::DerivedCRS *>(obj.get());
+ if (derivedCRS) {
+ auto warnings =
+ derivedCRS->derivingConversionRef()->validateParameters();
+ if (!warnings.empty()) {
+ *out_warnings = to_string_list(warnings);
+ }
+ } else {
+ auto singleOp =
+ dynamic_cast<const operation::SingleOperation *>(obj.get());
+ if (singleOp) {
+ auto warnings = singleOp->validateParameters();
+ if (!warnings.empty()) {
+ *out_warnings = to_string_list(warnings);
+ }
+ }
+ }
+ }
+
+ if (obj) {
+ return PJ_OBJ::create(NN_NO_CHECK(obj));
+ }
+ } catch (const std::exception &e) {
+ if (out_grammar_errors) {
+ std::list<std::string> exc{e.what()};
+ try {
+ *out_grammar_errors = to_string_list(exc);
+ } catch (const std::exception &) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ } else {
+ 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_destroy() 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_destroy(), 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 {
+ PROJStringParser parser;
+ auto dbContext = getDBcontextNoException(ctx, __FUNCTION__);
+ if (dbContext) {
+ parser.attachDatabaseContext(NN_NO_CHECK(dbContext));
+ }
+ auto identifiedObject = nn_dynamic_pointer_cast<IdentifiedObject>(
+ parser.createFromPROJString(proj_string));
+ if (identifiedObject) {
+ return PJ_OBJ::create(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_destroy() 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_destroy(), 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);
+ try {
+ const std::string codeStr(code);
+ 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_PRIME_MERIDIAN:
+ obj = factory->createPrimeMeridian(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(NN_NO_CHECK(obj));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const char *get_unit_category(UnitOfMeasure::Type type) {
+ const char *ret = nullptr;
+ switch (type) {
+ case UnitOfMeasure::Type::UNKNOWN:
+ ret = "unknown";
+ break;
+ case UnitOfMeasure::Type::NONE:
+ ret = "none";
+ break;
+ case UnitOfMeasure::Type::ANGULAR:
+ ret = "angular";
+ break;
+ case UnitOfMeasure::Type::LINEAR:
+ ret = "linear";
+ break;
+ case UnitOfMeasure::Type::SCALE:
+ ret = "scale";
+ break;
+ case UnitOfMeasure::Type::TIME:
+ ret = "time";
+ break;
+ case UnitOfMeasure::Type::PARAMETRIC:
+ ret = "parametric";
+ break;
+ }
+ return ret;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Get information for a unit of measure from a database lookup.
+ *
+ * @param ctx Context, or NULL for default context.
+ * @param auth_name Authority name (must not be NULL)
+ * @param code Unit of measure code (must not be NULL)
+ * @param out_name Pointer to a string value to store the parameter name. or
+ * NULL. This value remains valid until the next call to
+ * proj_uom_get_info_from_database() or the context destruction.
+ * @param out_conv_factor Pointer to a value to store the conversion
+ * factor of the prime meridian longitude unit to radian. or NULL
+ * @param out_category Pointer to a string value to store the parameter name. or
+ * NULL. This value might be "unknown", "none", "linear", "angular", "scale",
+ * "time" or "parametric";
+ * @return TRUE in case of success
+ */
+int proj_uom_get_info_from_database(PJ_CONTEXT *ctx, const char *auth_name,
+ const char *code, const char **out_name,
+ double *out_conv_factor,
+ const char **out_category) {
+ assert(auth_name);
+ assert(code);
+ SANITIZE_CTX(ctx);
+ try {
+ auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name);
+ auto obj = factory->createUnitOfMeasure(code);
+ if (out_name) {
+ ctx->cpp_context->lastUOMName_ = obj->name();
+ *out_name = ctx->cpp_context->lastUOMName_.c_str();
+ }
+ if (out_conv_factor) {
+ *out_conv_factor = obj->conversionToSI();
+ }
+ if (out_category) {
+ *out_category = get_unit_category(obj->type());
+ }
+ return true;
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return GeodeticCRS that use the specified datum.
+ *
+ * @param ctx Context, or NULL for default context.
+ * @param crs_auth_name CRS authority name, or NULL.
+ * @param datum_auth_name Datum authority name (must not be NULL)
+ * @param datum_code Datum code (must not be NULL)
+ * @param crs_type "geographic 2D", "geographic 3D", "geocentric" or NULL
+ * @return a result set that must be unreferenced with
+ * proj_obj_list_destroy(), or NULL in case of error.
+ */
+PJ_OBJ_LIST *proj_obj_query_geodetic_crs_from_datum(PJ_CONTEXT *ctx,
+ const char *crs_auth_name,
+ const char *datum_auth_name,
+ const char *datum_code,
+ const char *crs_type) {
+ assert(datum_auth_name);
+ assert(datum_code);
+ SANITIZE_CTX(ctx);
+ try {
+ auto factory = AuthorityFactory::create(
+ getDBcontext(ctx), crs_auth_name ? crs_auth_name : "");
+ auto res = factory->createGeodeticCRSFromDatum(
+ datum_auth_name, datum_code, crs_type ? crs_type : "");
+ std::vector<IdentifiedObjectNNPtr> objects;
+ for (const auto &obj : res) {
+ objects.push_back(obj);
+ }
+ return new PJ_OBJ_LIST(std::move(objects));
+ } 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_destroy(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_PRIME_MERIDIAN:
+ cppType = AuthorityFactory::ObjectType::PRIME_MERIDIAN;
+ 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_ENGINEERING_CRS:
+ valid = false;
+ 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_destroy(), 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(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(const PJ_OBJ *obj) {
+ assert(obj);
+ auto ptr = obj->obj.get();
+ if (dynamic_cast<Ellipsoid *>(ptr)) {
+ return PJ_OBJ_TYPE_ELLIPSOID;
+ }
+
+ if (dynamic_cast<PrimeMeridian *>(ptr)) {
+ return PJ_OBJ_TYPE_PRIME_MERIDIAN;
+ }
+
+ 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<EngineeringCRS *>(ptr)) {
+ return PJ_OBJ_TYPE_ENGINEERING_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(const PJ_OBJ *obj) {
+ assert(obj);
+ return obj->obj->isDeprecated();
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a list of non-deprecated objects related to the passed one
+ *
+ * @param ctx Context, or NULL for default context.
+ * @param obj Object (of type CRS for now) for which non-deprecated objects
+ * must be searched. Must not be NULL
+ * @return a result set that must be unreferenced with
+ * proj_obj_list_destroy(), or NULL in case of error.
+ */
+PJ_OBJ_LIST *proj_obj_get_non_deprecated(PJ_CONTEXT *ctx, const PJ_OBJ *obj) {
+ assert(obj);
+ SANITIZE_CTX(ctx);
+ auto crs = dynamic_cast<const CRS *>(obj->obj.get());
+ if (!crs) {
+ return nullptr;
+ }
+ try {
+ std::vector<IdentifiedObjectNNPtr> objects;
+ auto res = crs->getNonDeprecated(getDBcontext(ctx));
+ for (const auto &resObj : res) {
+ objects.push_back(resObj);
+ }
+ return new PJ_OBJ_LIST(std::move(objects));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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(const PJ_OBJ *obj, const 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(const 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(const 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(const 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(const 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();
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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 ctx PROJ context, or NULL for default context
+ * @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=AUTO/YES/NO. In AUTO mode, axis will be output for WKT2
+ * variants, for WKT1_GDAL for ProjectedCRS with easting/northing ordering
+ * (otherwise stripped), but not for WKT1_ESRI. Setting to YES will output
+ * them unconditionaly, and to NO will omit them unconditionaly.</li>
+ * </ul>
+ * @return a string, or NULL in case of error.
+ */
+const char *proj_obj_as_wkt(PJ_CONTEXT *ctx, const PJ_OBJ *obj,
+ PJ_WKT_TYPE type, const char *const *options) {
+ SANITIZE_CTX(ctx);
+ 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 dbContext = getDBcontextNoException(ctx, __FUNCTION__);
+ auto formatter = WKTFormatter::create(convention, dbContext);
+ 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="))) {
+ if (!ci_equal(value, "AUTO")) {
+ formatter->setOutputAxis(
+ ci_equal(value, "YES")
+ ? WKTFormatter::OutputAxisRule::YES
+ : WKTFormatter::OutputAxisRule::NO);
+ }
+ } else {
+ std::string msg("Unknown option :");
+ msg += *iter;
+ proj_log_error(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(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 ctx PROJ context, or NULL for default context
+ * @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 (or USE_ETMERC=NO to disable implicit
+ * use of etmerc by utm conversions)
+ * @return a string, or NULL in case of error.
+ */
+const char *proj_obj_as_proj_string(PJ_CONTEXT *ctx, const PJ_OBJ *obj,
+ PJ_PROJ_STRING_TYPE type,
+ const char *const *options) {
+ SANITIZE_CTX(ctx);
+ assert(obj);
+ auto exportable =
+ dynamic_cast<const IPROJStringExportable *>(obj->obj.get());
+ if (!exportable) {
+ proj_log_error(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(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);
+ } else if (ci_equal(options[0], "USE_ETMERC=NO")) {
+ formatter->setUseETMercForTMerc(false);
+ }
+ }
+ obj->lastPROJString = exportable->exportToPROJString(formatter.get());
+ return obj->lastPROJString.c_str();
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the area of use of an object.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Object (must not be NULL)
+ * @param out_west_lon_degree 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 out_south_lat_degree 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 out_east_lon_degree 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 out_north_lat_degree 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 out_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_CONTEXT *ctx, const PJ_OBJ *obj,
+ double *out_west_lon_degree,
+ double *out_south_lat_degree,
+ double *out_east_lon_degree,
+ double *out_north_lat_degree,
+ const char **out_area_name) {
+ (void)ctx;
+ if (out_area_name) {
+ *out_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() && out_area_name) {
+ *out_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 (out_west_lon_degree) {
+ *out_west_lon_degree = bbox->westBoundLongitude();
+ }
+ if (out_south_lat_degree) {
+ *out_south_lat_degree = bbox->southBoundLatitude();
+ }
+ if (out_east_lon_degree) {
+ *out_east_lon_degree = bbox->eastBoundLongitude();
+ }
+ if (out_north_lat_degree) {
+ *out_north_lat_degree = bbox->northBoundLatitude();
+ }
+ return true;
+ }
+ }
+ if (out_west_lon_degree) {
+ *out_west_lon_degree = -1000;
+ }
+ if (out_south_lat_degree) {
+ *out_south_lat_degree = -1000;
+ }
+ if (out_east_lon_degree) {
+ *out_east_lon_degree = -1000;
+ }
+ if (out_north_lat_degree) {
+ *out_north_lat_degree = -1000;
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+static const GeodeticCRS *extractGeodeticCRS(PJ_CONTEXT *ctx, const PJ_OBJ *crs,
+ const char *fname) {
+ assert(crs);
+ auto l_crs = dynamic_cast<const CRS *>(crs->obj.get());
+ if (!l_crs) {
+ proj_log_error(ctx, fname, "Object is not a CRS");
+ return nullptr;
+ }
+ auto geodCRS = l_crs->extractGeodeticCRSRaw();
+ if (!geodCRS) {
+ proj_log_error(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_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs Objet of type CRS (must not be NULL)
+ * @return Object that must be unreferenced with proj_obj_destroy(), or NULL
+ * in case of error.
+ */
+PJ_OBJ *proj_obj_crs_get_geodetic_crs(PJ_CONTEXT *ctx, const PJ_OBJ *crs) {
+ SANITIZE_CTX(ctx);
+ auto geodCRS = extractGeodeticCRS(ctx, crs, __FUNCTION__);
+ if (!geodCRS) {
+ return nullptr;
+ }
+ return PJ_OBJ::create(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_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @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_destroy(), or NULL
+ * in case of error.
+ */
+PJ_OBJ *proj_obj_crs_get_sub_crs(PJ_CONTEXT *ctx, const PJ_OBJ *crs,
+ int index) {
+ SANITIZE_CTX(ctx);
+ assert(crs);
+ auto l_crs = dynamic_cast<CompoundCRS *>(crs->obj.get());
+ if (!l_crs) {
+ proj_log_error(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(components[index]);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns a BoundCRS
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param base_crs Base CRS (must not be NULL)
+ * @param hub_crs Hub CRS (must not be NULL)
+ * @param transformation Transformation (must not be NULL)
+ * @return Object that must be unreferenced with proj_obj_destroy(), or NULL
+ * in case of error.
+ */
+PJ_OBJ *proj_obj_crs_create_bound_crs(PJ_CONTEXT *ctx, const PJ_OBJ *base_crs,
+ const PJ_OBJ *hub_crs,
+ const PJ_OBJ *transformation) {
+ SANITIZE_CTX(ctx);
+ assert(base_crs);
+ assert(hub_crs);
+ assert(transformation);
+ auto l_base_crs = util::nn_dynamic_pointer_cast<CRS>(base_crs->obj);
+ if (!l_base_crs) {
+ proj_log_error(ctx, __FUNCTION__, "base_crs is not a CRS");
+ return nullptr;
+ }
+ auto l_hub_crs = util::nn_dynamic_pointer_cast<CRS>(hub_crs->obj);
+ if (!l_hub_crs) {
+ proj_log_error(ctx, __FUNCTION__, "hub_crs is not a CRS");
+ return nullptr;
+ }
+ auto l_transformation =
+ util::nn_dynamic_pointer_cast<Transformation>(transformation->obj);
+ if (!l_transformation) {
+ proj_log_error(ctx, __FUNCTION__, "transformation is not a CRS");
+ return nullptr;
+ }
+ try {
+ return PJ_OBJ::create(BoundCRS::create(NN_NO_CHECK(l_base_crs),
+ NN_NO_CHECK(l_hub_crs),
+ NN_NO_CHECK(l_transformation)));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns potentially
+ * a BoundCRS, with a transformation to EPSG:4326, wrapping this CRS
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() 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 ctx PROJ context, or NULL for default context
+ * @param crs Objet of type CRS (must not be NULL)
+ * @param options null-terminated list of options, or NULL. Currently
+ * supported options are:
+ * <ul>
+ * <li>ALLOW_INTERMEDIATE_CRS=YES/NO. Defaults to NO. When set to YES,
+ * intermediate CRS may be considered when computing the possible
+ * tranformations. Slower.</li>
+ * </ul>
+ * @return Object that must be unreferenced with proj_obj_destroy(), or NULL
+ * in case of error.
+ */
+PJ_OBJ *proj_obj_crs_create_bound_crs_to_WGS84(PJ_CONTEXT *ctx,
+ const PJ_OBJ *crs,
+ const char *const *options) {
+ SANITIZE_CTX(ctx);
+ assert(crs);
+ auto l_crs = dynamic_cast<const CRS *>(crs->obj.get());
+ if (!l_crs) {
+ proj_log_error(ctx, __FUNCTION__, "Object is not a CRS");
+ return nullptr;
+ }
+ auto dbContext = getDBcontextNoException(ctx, __FUNCTION__);
+ try {
+ bool allowIntermediateCRS = false;
+ for (auto iter = options; iter && iter[0]; ++iter) {
+ const char *value;
+ if ((value = getOptionValue(*iter, "ALLOW_INTERMEDIATE_CRS="))) {
+ allowIntermediateCRS = ci_equal(value, "YES");
+ } else {
+ std::string msg("Unknown option :");
+ msg += *iter;
+ proj_log_error(ctx, __FUNCTION__, msg.c_str());
+ return nullptr;
+ }
+ }
+ return PJ_OBJ::create(l_crs->createBoundCRSToWGS84IfPossible(
+ dbContext, allowIntermediateCRS));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Get the ellipsoid from a CRS or a GeodeticReferenceFrame.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Objet of type CRS or GeodeticReferenceFrame (must not be NULL)
+ * @return Object that must be unreferenced with proj_obj_destroy(), or NULL
+ * in case of error.
+ */
+PJ_OBJ *proj_obj_get_ellipsoid(PJ_CONTEXT *ctx, const PJ_OBJ *obj) {
+ SANITIZE_CTX(ctx);
+ auto ptr = obj->obj.get();
+ if (dynamic_cast<const CRS *>(ptr)) {
+ auto geodCRS = extractGeodeticCRS(ctx, obj, __FUNCTION__);
+ if (geodCRS) {
+ return PJ_OBJ::create(geodCRS->ellipsoid());
+ }
+ } else {
+ auto datum = dynamic_cast<const GeodeticReferenceFrame *>(ptr);
+ if (datum) {
+ return PJ_OBJ::create(datum->ellipsoid());
+ }
+ }
+ proj_log_error(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_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs Objet of type CRS (must not be NULL)
+ * @return Object that must be unreferenced with proj_obj_destroy(), or NULL
+ * in case of error.
+ */
+PJ_OBJ *proj_obj_crs_get_horizontal_datum(PJ_CONTEXT *ctx, const PJ_OBJ *crs) {
+ SANITIZE_CTX(ctx);
+ auto geodCRS = extractGeodeticCRS(ctx, crs, __FUNCTION__);
+ if (!geodCRS) {
+ return nullptr;
+ }
+ const auto &datum = geodCRS->datum();
+ if (datum) {
+ return PJ_OBJ::create(NN_NO_CHECK(datum));
+ }
+
+ const auto &datumEnsemble = geodCRS->datumEnsemble();
+ if (datumEnsemble) {
+ return PJ_OBJ::create(NN_NO_CHECK(datumEnsemble));
+ }
+ proj_log_error(ctx, __FUNCTION__, "CRS has no datum");
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return ellipsoid parameters.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param ellipsoid Object of type Ellipsoid (must not be NULL)
+ * @param out_semi_major_metre Pointer to a value to store the semi-major axis
+ * in
+ * metre. or NULL
+ * @param out_semi_minor_metre Pointer to a value to store the semi-minor axis
+ * in
+ * metre. or NULL
+ * @param out_is_semi_minor_computed 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 out_inv_flattening Pointer to a value to store the inverse
+ * flattening. or NULL
+ * @return TRUE in case of success.
+ */
+int proj_obj_ellipsoid_get_parameters(PJ_CONTEXT *ctx, const PJ_OBJ *ellipsoid,
+ double *out_semi_major_metre,
+ double *out_semi_minor_metre,
+ int *out_is_semi_minor_computed,
+ double *out_inv_flattening) {
+ SANITIZE_CTX(ctx);
+ assert(ellipsoid);
+ auto l_ellipsoid = dynamic_cast<const Ellipsoid *>(ellipsoid->obj.get());
+ if (!l_ellipsoid) {
+ proj_log_error(ctx, __FUNCTION__, "Object is not a Ellipsoid");
+ return FALSE;
+ }
+
+ if (out_semi_major_metre) {
+ *out_semi_major_metre = l_ellipsoid->semiMajorAxis().getSIValue();
+ }
+ if (out_semi_minor_metre) {
+ *out_semi_minor_metre =
+ l_ellipsoid->computeSemiMinorAxis().getSIValue();
+ }
+ if (out_is_semi_minor_computed) {
+ *out_is_semi_minor_computed =
+ !(l_ellipsoid->semiMinorAxis().has_value());
+ }
+ if (out_inv_flattening) {
+ *out_inv_flattening = 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_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Objet of type CRS or GeodeticReferenceFrame (must not be NULL)
+ * @return Object that must be unreferenced with proj_obj_destroy(), or NULL
+ * in case of error.
+ */
+
+PJ_OBJ *proj_obj_get_prime_meridian(PJ_CONTEXT *ctx, const PJ_OBJ *obj) {
+ SANITIZE_CTX(ctx);
+ auto ptr = obj->obj.get();
+ if (dynamic_cast<CRS *>(ptr)) {
+ auto geodCRS = extractGeodeticCRS(ctx, obj, __FUNCTION__);
+ if (geodCRS) {
+ return PJ_OBJ::create(geodCRS->primeMeridian());
+ }
+ } else {
+ auto datum = dynamic_cast<const GeodeticReferenceFrame *>(ptr);
+ if (datum) {
+ return PJ_OBJ::create(datum->primeMeridian());
+ }
+ }
+ proj_log_error(ctx, __FUNCTION__,
+ "Object is not a CRS or GeodeticReferenceFrame");
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return prime meridian parameters.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param prime_meridian Object of type PrimeMeridian (must not be NULL)
+ * @param out_longitude Pointer to a value to store the longitude of the prime
+ * meridian, in its native unit. or NULL
+ * @param out_unit_conv_factor Pointer to a value to store the conversion
+ * factor of the prime meridian longitude unit to radian. or NULL
+ * @param out_unit_name 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_CONTEXT *ctx,
+ const PJ_OBJ *prime_meridian,
+ double *out_longitude,
+ double *out_unit_conv_factor,
+ const char **out_unit_name) {
+ SANITIZE_CTX(ctx);
+ assert(prime_meridian);
+ auto l_pm = dynamic_cast<const PrimeMeridian *>(prime_meridian->obj.get());
+ if (!l_pm) {
+ proj_log_error(ctx, __FUNCTION__, "Object is not a PrimeMeridian");
+ return false;
+ }
+ const auto &longitude = l_pm->longitude();
+ if (out_longitude) {
+ *out_longitude = longitude.value();
+ }
+ const auto &unit = longitude.unit();
+ if (out_unit_conv_factor) {
+ *out_unit_conv_factor = unit.conversionToSI();
+ }
+ if (out_unit_name) {
+ *out_unit_name = unit.name().c_str();
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the base CRS of a BoundCRS or a DerivedCRS/ProjectedCRS, or
+ * the source CRS of a CoordinateOperation.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Objet of type BoundCRS or CoordinateOperation (must not be NULL)
+ * @return Object that must be unreferenced with proj_obj_destroy(), or NULL
+ * in case of error, or missing source CRS.
+ */
+PJ_OBJ *proj_obj_get_source_crs(PJ_CONTEXT *ctx, const PJ_OBJ *obj) {
+ SANITIZE_CTX(ctx);
+ assert(obj);
+ auto ptr = obj->obj.get();
+ auto boundCRS = dynamic_cast<const BoundCRS *>(ptr);
+ if (boundCRS) {
+ return PJ_OBJ::create(boundCRS->baseCRS());
+ }
+ auto derivedCRS = dynamic_cast<const DerivedCRS *>(ptr);
+ if (derivedCRS) {
+ return PJ_OBJ::create(derivedCRS->baseCRS());
+ }
+ auto co = dynamic_cast<const CoordinateOperation *>(ptr);
+ if (co) {
+ auto sourceCRS = co->sourceCRS();
+ if (sourceCRS) {
+ return PJ_OBJ::create(NN_NO_CHECK(sourceCRS));
+ }
+ return nullptr;
+ }
+ proj_log_error(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_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Objet of type BoundCRS or CoordinateOperation (must not be NULL)
+ * @return Object that must be unreferenced with proj_obj_destroy(), or NULL
+ * in case of error, or missing target CRS.
+ */
+PJ_OBJ *proj_obj_get_target_crs(PJ_CONTEXT *ctx, const PJ_OBJ *obj) {
+ SANITIZE_CTX(ctx);
+ assert(obj);
+ auto ptr = obj->obj.get();
+ auto boundCRS = dynamic_cast<const BoundCRS *>(ptr);
+ if (boundCRS) {
+ return PJ_OBJ::create(boundCRS->hubCRS());
+ }
+ auto co = dynamic_cast<const CoordinateOperation *>(ptr);
+ if (co) {
+ auto targetCRS = co->targetCRS();
+ if (targetCRS) {
+ return PJ_OBJ::create(NN_NO_CHECK(targetCRS));
+ }
+ return nullptr;
+ }
+ proj_log_error(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. The list is sorted by decreasing
+ * confidence.
+ *
+ * 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 ctx PROJ context, or NULL for default context
+ * @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 out_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_int_list_destroy().
+ * @return a list of matching reference CRS, or nullptr in case of error.
+ */
+PJ_OBJ_LIST *proj_obj_identify(PJ_CONTEXT *ctx, const PJ_OBJ *obj,
+ const char *auth_name,
+ const char *const *options,
+ int **out_confidence) {
+ SANITIZE_CTX(ctx);
+ assert(obj);
+ (void)options;
+ if (out_confidence) {
+ *out_confidence = nullptr;
+ }
+ auto ptr = obj->obj.get();
+ auto crs = dynamic_cast<const CRS *>(ptr);
+ if (!crs) {
+ proj_log_error(ctx, __FUNCTION__, "Object is not a CRS");
+ } else {
+ int *confidenceTemp = nullptr;
+ try {
+ auto factory = AuthorityFactory::create(getDBcontext(ctx),
+ auth_name ? auth_name : "");
+ auto res = crs->identify(factory);
+ std::vector<IdentifiedObjectNNPtr> objects;
+ confidenceTemp = out_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>(std::move(objects));
+ if (out_confidence) {
+ *out_confidence = confidenceTemp;
+ confidenceTemp = nullptr;
+ }
+ return ret.release();
+ } catch (const std::exception &e) {
+ delete[] confidenceTemp;
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Free an array of integer. */
+void proj_int_list_destroy(int *list) { delete[] list; }
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the list of authorities used in the database.
+ *
+ * The returned list is NULL terminated and must be freed with
+ * proj_string_list_destroy().
+ *
+ * @param ctx PROJ context, or NULL for default context
+ *
+ * @return a NULL terminated list of NUL-terminated strings that must be
+ * freed with proj_string_list_destroy(), or NULL in case of error.
+ */
+PROJ_STRING_LIST proj_get_authorities_from_database(PJ_CONTEXT *ctx) {
+ SANITIZE_CTX(ctx);
+ try {
+ return 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_string_list_destroy().
+ *
+ * @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_string_list_destroy(), 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 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_string_list_destroy(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_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs Objet of type DerivedCRS or BoundCRSs (must not be NULL)
+ * @return Object of type SingleOperation that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ *proj_obj_crs_get_coordoperation(PJ_CONTEXT *ctx, const PJ_OBJ *crs) {
+ SANITIZE_CTX(ctx);
+ 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(ctx, __FUNCTION__,
+ "Object is not a DerivedCRS or BoundCRS");
+ return nullptr;
+ }
+ }
+
+ return PJ_OBJ::create(NN_NO_CHECK(co));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return informatin on the operation method of the SingleOperation.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param coordoperation Objet of type SingleOperation (typically a Conversion
+ * or Transformation) (must not be NULL)
+ * @param out_method_name Pointer to a string value to store the method
+ * (projection) name. or NULL
+ * @param out_method_auth_name Pointer to a string value to store the method
+ * authority name. or NULL
+ * @param out_method_code Pointer to a string value to store the method
+ * code. or NULL
+ * @return TRUE in case of success.
+ */
+int proj_coordoperation_get_method_info(PJ_CONTEXT *ctx,
+ const PJ_OBJ *coordoperation,
+ const char **out_method_name,
+ const char **out_method_auth_name,
+ const char **out_method_code) {
+ SANITIZE_CTX(ctx);
+ assert(coordoperation);
+
+ auto singleOp =
+ dynamic_cast<const SingleOperation *>(coordoperation->obj.get());
+ if (!singleOp) {
+ proj_log_error(ctx, __FUNCTION__,
+ "Object is not a DerivedCRS or BoundCRS");
+ return false;
+ }
+
+ const auto &method = singleOp->method();
+ const auto &method_ids = method->identifiers();
+ if (out_method_name) {
+ *out_method_name = method->name()->description()->c_str();
+ }
+ if (out_method_auth_name) {
+ if (!method_ids.empty()) {
+ *out_method_auth_name = method_ids[0]->codeSpace()->c_str();
+ } else {
+ *out_method_auth_name = nullptr;
+ }
+ }
+ if (out_method_code) {
+ if (!method_ids.empty()) {
+ *out_method_code = method_ids[0]->code().c_str();
+ } else {
+ *out_method_code = nullptr;
+ }
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static PropertyMap createPropertyMapName(const char *c_name) {
+ std::string name(c_name ? c_name : "unnamed");
+ PropertyMap properties;
+ if (ends_with(name, " (deprecated)")) {
+ name.resize(name.size() - strlen(" (deprecated)"));
+ properties.set(common::IdentifiedObject::DEPRECATED_KEY, true);
+ }
+ return properties.set(common::IdentifiedObject::NAME_KEY, name);
+}
+
+// ---------------------------------------------------------------------------
+
+static UnitOfMeasure createLinearUnit(const char *name, double convFactor,
+ const char *unit_auth_name = nullptr,
+ const char *unit_code = nullptr) {
+ return name == nullptr
+ ? UnitOfMeasure::METRE
+ : UnitOfMeasure(name, convFactor, UnitOfMeasure::Type::LINEAR,
+ unit_auth_name ? unit_auth_name : "",
+ unit_code ? unit_code : "");
+}
+
+// ---------------------------------------------------------------------------
+
+static UnitOfMeasure createAngularUnit(const char *name, double convFactor,
+ const char *unit_auth_name = nullptr,
+ const char *unit_code = nullptr) {
+ return name ? (ci_equal(name, "degree")
+ ? UnitOfMeasure::DEGREE
+ : ci_equal(name, "grad")
+ ? UnitOfMeasure::GRAD
+ : UnitOfMeasure(name, convFactor,
+ UnitOfMeasure::Type::ANGULAR,
+ unit_auth_name ? unit_auth_name
+ : "",
+ unit_code ? unit_code : ""))
+ : UnitOfMeasure::DEGREE;
+}
+
+// ---------------------------------------------------------------------------
+
+static GeodeticReferenceFrameNNPtr createGeodeticReferenceFrame(
+ PJ_CONTEXT *ctx, const char *datum_name, const char *ellps_name,
+ double semi_major_metre, double inv_flattening,
+ const char *prime_meridian_name, double prime_meridian_offset,
+ const char *angular_units, double angular_units_conv) {
+ const UnitOfMeasure angUnit(
+ createAngularUnit(angular_units, angular_units_conv));
+ auto dbContext = getDBcontextNoException(ctx, __FUNCTION__);
+ auto body = Ellipsoid::guessBodyName(dbContext, semi_major_metre);
+ auto ellpsName = createPropertyMapName(ellps_name);
+ auto ellps = inv_flattening != 0.0
+ ? Ellipsoid::createFlattenedSphere(
+ ellpsName, Length(semi_major_metre),
+ Scale(inv_flattening), body)
+ : Ellipsoid::createSphere(ellpsName,
+ Length(semi_major_metre), body);
+ auto pm = PrimeMeridian::create(
+ PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ prime_meridian_name
+ ? prime_meridian_name
+ : prime_meridian_offset == 0.0
+ ? (ellps->celestialBody() == Ellipsoid::EARTH
+ ? PrimeMeridian::GREENWICH->nameStr().c_str()
+ : PrimeMeridian::REFERENCE_MERIDIAN->nameStr()
+ .c_str())
+ : "unnamed"),
+ Angle(prime_meridian_offset, angUnit));
+
+ std::string datumName(datum_name ? datum_name : "unnamed");
+ if (datumName == "WGS_1984") {
+ datumName = GeodeticReferenceFrame::EPSG_6326->nameStr();
+ } else if (datumName.find('_') != std::string::npos) {
+ // Likely coming from WKT1
+ if (dbContext) {
+ auto authFactory =
+ AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string());
+ auto res = authFactory->createObjectsFromName(
+ datumName,
+ {AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, true,
+ 1);
+ if (!res.empty()) {
+ const auto &refDatum = res.front();
+ if (metadata::Identifier::isEquivalentName(
+ datumName.c_str(), refDatum->nameStr().c_str())) {
+ datumName = refDatum->nameStr();
+ }
+ } else {
+ std::string outTableName;
+ std::string authNameFromAlias;
+ std::string codeFromAlias;
+ auto officialName = authFactory->getOfficialNameFromAlias(
+ datumName, "geodetic_datum", std::string(), true,
+ outTableName, authNameFromAlias, codeFromAlias);
+ if (!officialName.empty()) {
+ datumName = officialName;
+ }
+ }
+ }
+ }
+
+ return GeodeticReferenceFrame::create(
+ createPropertyMapName(datumName.c_str()), ellps,
+ util::optional<std::string>(), pm);
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Create a GeographicCRS.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs_name Name of the GeographicCRS. Or NULL
+ * @param datum_name Name of the GeodeticReferenceFrame. Or NULL
+ * @param ellps_name Name of the Ellipsoid. Or NULL
+ * @param semi_major_metre Ellipsoid semi-major axis, in metres.
+ * @param inv_flattening Ellipsoid inverse flattening. Or 0 for a sphere.
+ * @param prime_meridian_name Name of the PrimeMeridian. Or NULL
+ * @param prime_meridian_offset Offset of the prime meridian, expressed in the
+ * specified angular units.
+ * @param pm_angular_units Name of the angular units. Or NULL for Degree
+ * @param pm_angular_units_conv Conversion factor from the angular unit to
+ * radian.
+ * Or
+ * 0 for Degree if pm_angular_units == NULL. Otherwise should be not NULL
+ * @param ellipsoidal_cs Coordinate system. Must not be NULL.
+ *
+ * @return Object of type GeographicCRS that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ *proj_obj_create_geographic_crs(
+ PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name,
+ const char *ellps_name, double semi_major_metre, double inv_flattening,
+ const char *prime_meridian_name, double prime_meridian_offset,
+ const char *pm_angular_units, double pm_angular_units_conv,
+ PJ_OBJ *ellipsoidal_cs) {
+
+ SANITIZE_CTX(ctx);
+ auto cs = util::nn_dynamic_pointer_cast<EllipsoidalCS>(ellipsoidal_cs->obj);
+ if (!cs) {
+ return nullptr;
+ }
+ try {
+ auto datum = createGeodeticReferenceFrame(
+ ctx, datum_name, ellps_name, semi_major_metre, inv_flattening,
+ prime_meridian_name, prime_meridian_offset, pm_angular_units,
+ pm_angular_units_conv);
+ auto geogCRS = GeographicCRS::create(createPropertyMapName(crs_name),
+ datum, NN_NO_CHECK(cs));
+ return PJ_OBJ::create(geogCRS);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Create a GeographicCRS.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs_name Name of the GeographicCRS. Or NULL
+ * @param datum Datum. Must not be NULL.
+ * @param ellipsoidal_cs Coordinate system. Must not be NULL.
+ *
+ * @return Object of type GeographicCRS that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ *proj_obj_create_geographic_crs_from_datum(PJ_CONTEXT *ctx,
+ const char *crs_name,
+ PJ_OBJ *datum,
+ PJ_OBJ *ellipsoidal_cs) {
+
+ SANITIZE_CTX(ctx);
+ auto l_datum =
+ util::nn_dynamic_pointer_cast<GeodeticReferenceFrame>(datum->obj);
+ if (!l_datum) {
+ proj_log_error(ctx, __FUNCTION__,
+ "datum is not a GeodeticReferenceFrame");
+ return nullptr;
+ }
+ auto cs = util::nn_dynamic_pointer_cast<EllipsoidalCS>(ellipsoidal_cs->obj);
+ if (!cs) {
+ return nullptr;
+ }
+ try {
+ auto geogCRS =
+ GeographicCRS::create(createPropertyMapName(crs_name),
+ NN_NO_CHECK(l_datum), NN_NO_CHECK(cs));
+ return PJ_OBJ::create(geogCRS);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Create a GeodeticCRS of geocentric type.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs_name Name of the GeographicCRS. Or NULL
+ * @param datum_name Name of the GeodeticReferenceFrame. Or NULL
+ * @param ellps_name Name of the Ellipsoid. Or NULL
+ * @param semi_major_metre Ellipsoid semi-major axis, in metres.
+ * @param inv_flattening Ellipsoid inverse flattening. Or 0 for a sphere.
+ * @param prime_meridian_name Name of the PrimeMeridian. Or NULL
+ * @param prime_meridian_offset Offset of the prime meridian, expressed in the
+ * specified angular units.
+ * @param angular_units Name of the angular units. Or NULL for Degree
+ * @param angular_units_conv Conversion factor from the angular unit to radian.
+ * Or
+ * 0 for Degree if angular_units == NULL. Otherwise should be not NULL
+ * @param linear_units Name of the linear units. Or NULL for Metre
+ * @param linear_units_conv Conversion factor from the linear unit to metre. Or
+ * 0 for Metre if linear_units == NULL. Otherwise should be not NULL
+ *
+ * @return Object of type GeodeticCRS that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ *proj_obj_create_geocentric_crs(
+ PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name,
+ const char *ellps_name, double semi_major_metre, double inv_flattening,
+ const char *prime_meridian_name, double prime_meridian_offset,
+ const char *angular_units, double angular_units_conv,
+ const char *linear_units, double linear_units_conv) {
+
+ SANITIZE_CTX(ctx);
+ try {
+ const UnitOfMeasure linearUnit(
+ createLinearUnit(linear_units, linear_units_conv));
+ auto datum = createGeodeticReferenceFrame(
+ ctx, datum_name, ellps_name, semi_major_metre, inv_flattening,
+ prime_meridian_name, prime_meridian_offset, angular_units,
+ angular_units_conv);
+
+ auto geodCRS =
+ GeodeticCRS::create(createPropertyMapName(crs_name), datum,
+ cs::CartesianCS::createGeocentric(linearUnit));
+ return PJ_OBJ::create(geodCRS);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Create a GeodeticCRS of geocentric type.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs_name Name of the GeographicCRS. Or NULL
+ * @param datum Datum. Must not be NULL.
+ * @param linear_units Name of the linear units. Or NULL for Metre
+ * @param linear_units_conv Conversion factor from the linear unit to metre. Or
+ * 0 for Metre if linear_units == NULL. Otherwise should be not NULL
+ *
+ * @return Object of type GeodeticCRS that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ *proj_obj_create_geocentric_crs_from_datum(PJ_CONTEXT *ctx,
+ const char *crs_name,
+ const PJ_OBJ *datum,
+ const char *linear_units,
+ double linear_units_conv) {
+ SANITIZE_CTX(ctx);
+ try {
+ const UnitOfMeasure linearUnit(
+ createLinearUnit(linear_units, linear_units_conv));
+ auto l_datum =
+ util::nn_dynamic_pointer_cast<GeodeticReferenceFrame>(datum->obj);
+ if (!l_datum) {
+ proj_log_error(ctx, __FUNCTION__,
+ "datum is not a GeodeticReferenceFrame");
+ return nullptr;
+ }
+ auto geodCRS = GeodeticCRS::create(
+ createPropertyMapName(crs_name), NN_NO_CHECK(l_datum),
+ cs::CartesianCS::createGeocentric(linearUnit));
+ return PJ_OBJ::create(geodCRS);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Create a VerticalCRS
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs_name Name of the GeographicCRS. Or NULL
+ * @param datum_name Name of the VerticalReferenceFrame. Or NULL
+ * @param linear_units Name of the linear units. Or NULL for Metre
+ * @param linear_units_conv Conversion factor from the linear unit to metre. Or
+ * 0 for Metre if linear_units == NULL. Otherwise should be not NULL
+ *
+ * @return Object of type VerticalCRS that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ *proj_obj_create_vertical_crs(PJ_CONTEXT *ctx, const char *crs_name,
+ const char *datum_name,
+ const char *linear_units,
+ double linear_units_conv) {
+
+ SANITIZE_CTX(ctx);
+ try {
+ const UnitOfMeasure linearUnit(
+ createLinearUnit(linear_units, linear_units_conv));
+ auto datum =
+ VerticalReferenceFrame::create(createPropertyMapName(datum_name));
+ auto vertCRS = VerticalCRS::create(
+ createPropertyMapName(crs_name), datum,
+ cs::VerticalCS::createGravityRelatedHeight(linearUnit));
+ return PJ_OBJ::create(vertCRS);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Create a CompoundCRS
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs_name Name of the GeographicCRS. Or NULL
+ * @param horiz_crs Horizontal CRS. must not be NULL.
+ * @param vert_crs Vertical CRS. must not be NULL.
+ *
+ * @return Object of type CompoundCRS that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ *proj_obj_create_compound_crs(PJ_CONTEXT *ctx, const char *crs_name,
+ PJ_OBJ *horiz_crs, PJ_OBJ *vert_crs) {
+
+ assert(horiz_crs);
+ assert(vert_crs);
+ SANITIZE_CTX(ctx);
+ auto l_horiz_crs = util::nn_dynamic_pointer_cast<CRS>(horiz_crs->obj);
+ if (!l_horiz_crs) {
+ return nullptr;
+ }
+ auto l_vert_crs = util::nn_dynamic_pointer_cast<CRS>(vert_crs->obj);
+ if (!l_vert_crs) {
+ return nullptr;
+ }
+ try {
+ auto compoundCRS = CompoundCRS::create(
+ createPropertyMapName(crs_name),
+ {NN_NO_CHECK(l_horiz_crs), NN_NO_CHECK(l_vert_crs)});
+ return PJ_OBJ::create(compoundCRS);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a copy of the object with its name changed
+ *
+ * Currently, only implemented on CRS objects.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Object of type CRS. Must not be NULL
+ * @param name New name. Must not be NULL
+ *
+ * @return Object that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ PROJ_DLL *proj_obj_alter_name(PJ_CONTEXT *ctx, const PJ_OBJ *obj,
+ const char *name) {
+ SANITIZE_CTX(ctx);
+ auto crs = dynamic_cast<const CRS *>(obj->obj.get());
+ if (!crs) {
+ return nullptr;
+ }
+ try {
+ return PJ_OBJ::create(crs->alterName(name));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a copy of the object with its identifier changed/set
+ *
+ * Currently, only implemented on CRS objects.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Object of type CRS. Must not be NULL
+ * @param auth_name Authority name. Must not be NULL
+ * @param code Code. Must not be NULL
+ *
+ * @return Object that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ PROJ_DLL *proj_obj_alter_id(PJ_CONTEXT *ctx, const PJ_OBJ *obj,
+ const char *auth_name, const char *code) {
+ SANITIZE_CTX(ctx);
+ auto crs = dynamic_cast<const CRS *>(obj->obj.get());
+ if (!crs) {
+ return nullptr;
+ }
+ try {
+ return PJ_OBJ::create(crs->alterId(auth_name, code));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a copy of the CRS with its geodetic CRS changed
+ *
+ * Currently, when obj is a GeodeticCRS, it returns a clone of new_geod_crs
+ * When obj is a ProjectedCRS, it replaces its base CRS with new_geod_crs.
+ * When obj is a CompoundCRS, it replaces the GeodeticCRS part of the horizontal
+ * CRS with new_geod_crs.
+ * In other cases, it returns a clone of obj.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Object of type CRS. Must not be NULL
+ * @param new_geod_crs Object of type GeodeticCRS. Must not be NULL
+ *
+ * @return Object that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ *proj_obj_crs_alter_geodetic_crs(PJ_CONTEXT *ctx, const PJ_OBJ *obj,
+ const PJ_OBJ *new_geod_crs) {
+ SANITIZE_CTX(ctx);
+ auto l_new_geod_crs =
+ util::nn_dynamic_pointer_cast<GeodeticCRS>(new_geod_crs->obj);
+ if (!l_new_geod_crs) {
+ proj_log_error(ctx, __FUNCTION__, "new_geod_crs is not a GeodeticCRS");
+ return nullptr;
+ }
+
+ auto crs = dynamic_cast<const CRS *>(obj->obj.get());
+ if (!crs) {
+ proj_log_error(ctx, __FUNCTION__, "obj is not a CRS");
+ return nullptr;
+ }
+
+ try {
+ return PJ_OBJ::create(
+ crs->alterGeodeticCRS(NN_NO_CHECK(l_new_geod_crs)));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a copy of the CRS with its angular units changed
+ *
+ * The CRS must be or contain a GeographicCRS.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Object of type CRS. Must not be NULL
+ * @param angular_units Name of the angular units. Or NULL for Degree
+ * @param angular_units_conv Conversion factor from the angular unit to radian.
+ * Or 0 for Degree if angular_units == NULL. Otherwise should be not NULL
+ * @param unit_auth_name Unit authority name. Or NULL.
+ * @param unit_code Unit code. Or NULL.
+ *
+ * @return Object that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ *proj_obj_crs_alter_cs_angular_unit(PJ_CONTEXT *ctx, const PJ_OBJ *obj,
+ const char *angular_units,
+ double angular_units_conv,
+ const char *unit_auth_name,
+ const char *unit_code) {
+
+ SANITIZE_CTX(ctx);
+ auto geodCRS = proj_obj_crs_get_geodetic_crs(ctx, obj);
+ if (!geodCRS) {
+ return nullptr;
+ }
+ auto geogCRS = dynamic_cast<const GeographicCRS *>(geodCRS->obj.get());
+ if (!geogCRS) {
+ proj_obj_destroy(geodCRS);
+ return nullptr;
+ }
+
+ PJ_OBJ *geogCRSAltered = nullptr;
+ try {
+ const UnitOfMeasure angUnit(createAngularUnit(
+ angular_units, angular_units_conv, unit_auth_name, unit_code));
+ geogCRSAltered = PJ_OBJ::create(GeographicCRS::create(
+ createPropertyMapName(proj_obj_get_name(geodCRS)), geogCRS->datum(),
+ geogCRS->datumEnsemble(),
+ geogCRS->coordinateSystem()->alterAngularUnit(angUnit)));
+ proj_obj_destroy(geodCRS);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ proj_obj_destroy(geodCRS);
+ return nullptr;
+ }
+
+ auto ret = proj_obj_crs_alter_geodetic_crs(ctx, obj, geogCRSAltered);
+ proj_obj_destroy(geogCRSAltered);
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a copy of the CRS with the linear units of its coordinate
+ * system changed
+ *
+ * The CRS must be or contain a ProjectedCRS, VerticalCRS or a GeocentricCRS.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Object of type CRS. Must not be NULL
+ * @param linear_units Name of the linear units. Or NULL for Metre
+ * @param linear_units_conv Conversion factor from the linear unit to metre. Or
+ * 0 for Metre if linear_units == NULL. Otherwise should be not NULL
+ * @param unit_auth_name Unit authority name. Or NULL.
+ * @param unit_code Unit code. Or NULL.
+ *
+ * @return Object that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ *proj_obj_crs_alter_cs_linear_unit(PJ_CONTEXT *ctx, const PJ_OBJ *obj,
+ const char *linear_units,
+ double linear_units_conv,
+ const char *unit_auth_name,
+ const char *unit_code) {
+ SANITIZE_CTX(ctx);
+ auto crs = dynamic_cast<const CRS *>(obj->obj.get());
+ if (!crs) {
+ return nullptr;
+ }
+
+ try {
+ const UnitOfMeasure linearUnit(createLinearUnit(
+ linear_units, linear_units_conv, unit_auth_name, unit_code));
+ return PJ_OBJ::create(crs->alterCSLinearUnit(linearUnit));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a copy of the CRS with the lineaer units of the parameters
+ * of its conversion modified.
+ *
+ * The CRS must be or contain a ProjectedCRS, VerticalCRS or a GeocentricCRS.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Object of type ProjectedCRS. Must not be NULL
+ * @param linear_units Name of the linear units. Or NULL for Metre
+ * @param linear_units_conv Conversion factor from the linear unit to metre. Or
+ * 0 for Metre if linear_units == NULL. Otherwise should be not NULL
+ * @param unit_auth_name Unit authority name. Or NULL.
+ * @param unit_code Unit code. Or NULL.
+ * @param convert_to_new_unit TRUE if exisiting values should be converted from
+ * their current unit to the new unit. If FALSE, their value will be left
+ * unchanged and the unit overriden (so the resulting CRS will not be
+ * equivalent to the original one for reprojection purposes).
+ *
+ * @return Object that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ *proj_obj_crs_alter_parameters_linear_unit(
+ PJ_CONTEXT *ctx, const PJ_OBJ *obj, const char *linear_units,
+ double linear_units_conv, const char *unit_auth_name, const char *unit_code,
+ int convert_to_new_unit) {
+ SANITIZE_CTX(ctx);
+ auto crs = dynamic_cast<const ProjectedCRS *>(obj->obj.get());
+ if (!crs) {
+ return nullptr;
+ }
+
+ try {
+ const UnitOfMeasure linearUnit(createLinearUnit(
+ linear_units, linear_units_conv, unit_auth_name, unit_code));
+ return PJ_OBJ::create(crs->alterParametersLinearUnit(
+ linearUnit, convert_to_new_unit == TRUE));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a EngineeringCRS with just a name
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs_name CRS name. Or NULL.
+ *
+ * @return Object that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ PROJ_DLL *proj_obj_create_engineering_crs(PJ_CONTEXT *ctx,
+ const char *crs_name) {
+ SANITIZE_CTX(ctx);
+ try {
+ return PJ_OBJ::create(EngineeringCRS::create(
+ createPropertyMapName(crs_name),
+ EngineeringDatum::create(PropertyMap()),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static void setSingleOperationElements(
+ const char *name, const char *auth_name, const char *code,
+ const char *method_name, const char *method_auth_name,
+ const char *method_code, int param_count,
+ const PJ_PARAM_DESCRIPTION *params, PropertyMap &propSingleOp,
+ PropertyMap &propMethod, std::vector<OperationParameterNNPtr> &parameters,
+ std::vector<ParameterValueNNPtr> &values) {
+ propSingleOp.set(common::IdentifiedObject::NAME_KEY,
+ name ? name : "unnamed");
+ if (auth_name && code) {
+ propSingleOp.set(metadata::Identifier::CODESPACE_KEY, auth_name)
+ .set(metadata::Identifier::CODE_KEY, code);
+ }
+
+ propMethod.set(common::IdentifiedObject::NAME_KEY,
+ method_name ? method_name : "unnamed");
+ if (method_auth_name && method_code) {
+ propMethod.set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
+ .set(metadata::Identifier::CODE_KEY, method_code);
+ }
+
+ for (int i = 0; i < param_count; i++) {
+ PropertyMap propParam;
+ propParam.set(common::IdentifiedObject::NAME_KEY,
+ params[i].name ? params[i].name : "unnamed");
+ if (params[i].auth_name && params[i].code) {
+ propParam
+ .set(metadata::Identifier::CODESPACE_KEY, params[i].auth_name)
+ .set(metadata::Identifier::CODE_KEY, params[i].code);
+ }
+ parameters.emplace_back(OperationParameter::create(propParam));
+ auto unit_type = UnitOfMeasure::Type::UNKNOWN;
+ switch (params[i].unit_type) {
+ case PJ_UT_ANGULAR:
+ unit_type = UnitOfMeasure::Type::ANGULAR;
+ break;
+ case PJ_UT_LINEAR:
+ unit_type = UnitOfMeasure::Type::LINEAR;
+ break;
+ case PJ_UT_SCALE:
+ unit_type = UnitOfMeasure::Type::SCALE;
+ break;
+ case PJ_UT_TIME:
+ unit_type = UnitOfMeasure::Type::TIME;
+ break;
+ case PJ_UT_PARAMETRIC:
+ unit_type = UnitOfMeasure::Type::PARAMETRIC;
+ break;
+ }
+
+ Measure measure(
+ params[i].value,
+ params[i].unit_type == PJ_UT_ANGULAR
+ ? createAngularUnit(params[i].unit_name,
+ params[i].unit_conv_factor)
+ : params[i].unit_type == PJ_UT_LINEAR
+ ? createLinearUnit(params[i].unit_name,
+ params[i].unit_conv_factor)
+ : UnitOfMeasure(params[i].unit_name ? params[i].unit_name
+ : "unnamed",
+ params[i].unit_conv_factor, unit_type));
+ values.emplace_back(ParameterValue::create(measure));
+ }
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a Conversion
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param name Conversion name. Or NULL.
+ * @param auth_name Conversion authority name. Or NULL.
+ * @param code Conversion code. Or NULL.
+ * @param method_name Method name. Or NULL.
+ * @param method_auth_name Method authority name. Or NULL.
+ * @param method_code Method code. Or NULL.
+ * @param param_count Number of parameters (size of params argument)
+ * @param params Parameter descriptions (array of size param_count)
+ *
+ * @return Object that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+
+PJ_OBJ *proj_obj_create_conversion(PJ_CONTEXT *ctx, const char *name,
+ const char *auth_name, const char *code,
+ const char *method_name,
+ const char *method_auth_name,
+ const char *method_code, int param_count,
+ const PJ_PARAM_DESCRIPTION *params) {
+ SANITIZE_CTX(ctx);
+ try {
+ PropertyMap propSingleOp;
+ PropertyMap propMethod;
+ std::vector<OperationParameterNNPtr> parameters;
+ std::vector<ParameterValueNNPtr> values;
+
+ setSingleOperationElements(
+ name, auth_name, code, method_name, method_auth_name, method_code,
+ param_count, params, propSingleOp, propMethod, parameters, values);
+
+ return PJ_OBJ::create(
+ Conversion::create(propSingleOp, propMethod, parameters, values));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a Transformation
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param name Transformation name. Or NULL.
+ * @param auth_name Transformation authority name. Or NULL.
+ * @param code Transformation code. Or NULL.
+ * @param source_crs Object of type CRS representing the source CRS.
+ * Must not be NULL.
+ * @param target_crs Object of type CRS representing the target CRS.
+ * Must not be NULL.
+ * @param interpolation_crs Object of type CRS representing the interpolation
+ * CRS. Or NULL.
+ * @param method_name Method name. Or NULL.
+ * @param method_auth_name Method authority name. Or NULL.
+ * @param method_code Method code. Or NULL.
+ * @param param_count Number of parameters (size of params argument)
+ * @param params Parameter descriptions (array of size param_count)
+ * @param accuracy Accuracy of the transformation in meters. A negative
+ * values means unknown.
+ *
+ * @return Object that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+
+PJ_OBJ *proj_obj_create_transformation(
+ PJ_CONTEXT *ctx, const char *name, const char *auth_name, const char *code,
+ PJ_OBJ *source_crs, PJ_OBJ *target_crs, PJ_OBJ *interpolation_crs,
+ const char *method_name, const char *method_auth_name,
+ const char *method_code, int param_count,
+ const PJ_PARAM_DESCRIPTION *params, double accuracy) {
+ SANITIZE_CTX(ctx);
+ assert(source_crs);
+ assert(target_crs);
+
+ auto l_sourceCRS = util::nn_dynamic_pointer_cast<CRS>(source_crs->obj);
+ if (!l_sourceCRS) {
+ proj_log_error(ctx, __FUNCTION__, "source_crs is not a CRS");
+ return nullptr;
+ }
+
+ auto l_targetCRS = util::nn_dynamic_pointer_cast<CRS>(target_crs->obj);
+ if (!l_targetCRS) {
+ proj_log_error(ctx, __FUNCTION__, "target_crs is not a CRS");
+ return nullptr;
+ }
+
+ CRSPtr l_interpolationCRS;
+ if (interpolation_crs) {
+ l_interpolationCRS =
+ util::nn_dynamic_pointer_cast<CRS>(interpolation_crs->obj);
+ if (!l_interpolationCRS) {
+ proj_log_error(ctx, __FUNCTION__, "interpolation_crs is not a CRS");
+ return nullptr;
+ }
+ }
+
+ try {
+ PropertyMap propSingleOp;
+ PropertyMap propMethod;
+ std::vector<OperationParameterNNPtr> parameters;
+ std::vector<ParameterValueNNPtr> values;
+
+ setSingleOperationElements(
+ name, auth_name, code, method_name, method_auth_name, method_code,
+ param_count, params, propSingleOp, propMethod, parameters, values);
+
+ std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
+ if (accuracy >= 0.0) {
+ accuracies.emplace_back(
+ PositionalAccuracy::create(toString(accuracy)));
+ }
+
+ return PJ_OBJ::create(Transformation::create(
+ propSingleOp, NN_NO_CHECK(l_sourceCRS), NN_NO_CHECK(l_targetCRS),
+ l_interpolationCRS, propMethod, parameters, values, accuracies));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/**
+ * \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 ctx PROJ context, or NULL for default context
+ * @param conversion Object of type Conversion. Must not be NULL.
+ * @param new_method_epsg_code EPSG code of the target method. Or 0 (in which
+ * case new_method_name must be specified).
+ * @param new_method_name EPSG or PROJ target method name. Or nullptr (in which
+ * case new_method_epsg_code must be specified).
+ * @return new conversion that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+PJ_OBJ *proj_obj_convert_conversion_to_other_method(
+ PJ_CONTEXT *ctx, const PJ_OBJ *conversion, int new_method_epsg_code,
+ const char *new_method_name) {
+ SANITIZE_CTX(ctx);
+ auto conv = dynamic_cast<const Conversion *>(conversion->obj.get());
+ if (!conv) {
+ proj_log_error(ctx, __FUNCTION__, "not a Conversion");
+ return nullptr;
+ }
+ if (new_method_epsg_code == 0) {
+ if (!new_method_name) {
+ return nullptr;
+ }
+ if (metadata::Identifier::isEquivalentName(
+ new_method_name, EPSG_NAME_METHOD_MERCATOR_VARIANT_A)) {
+ new_method_epsg_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_A;
+ } else if (metadata::Identifier::isEquivalentName(
+ new_method_name, EPSG_NAME_METHOD_MERCATOR_VARIANT_B)) {
+ new_method_epsg_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_B;
+ } else if (metadata::Identifier::isEquivalentName(
+ new_method_name,
+ EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)) {
+ new_method_epsg_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP;
+ } else if (metadata::Identifier::isEquivalentName(
+ new_method_name,
+ EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP)) {
+ new_method_epsg_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP;
+ }
+ }
+ try {
+ auto new_conv = conv->convertToOtherMethod(new_method_epsg_code);
+ if (!new_conv)
+ return nullptr;
+ return PJ_OBJ::create(NN_NO_CHECK(new_conv));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static CoordinateSystemAxisNNPtr createAxis(const PJ_AXIS_DESCRIPTION &axis) {
+ const auto dir =
+ axis.direction ? AxisDirection::valueOf(axis.direction) : nullptr;
+ if (dir == nullptr)
+ throw Exception("invalid value for axis direction");
+ auto unit_type = UnitOfMeasure::Type::UNKNOWN;
+ switch (axis.unit_type) {
+ case PJ_UT_ANGULAR:
+ unit_type = UnitOfMeasure::Type::ANGULAR;
+ break;
+ case PJ_UT_LINEAR:
+ unit_type = UnitOfMeasure::Type::LINEAR;
+ break;
+ case PJ_UT_SCALE:
+ unit_type = UnitOfMeasure::Type::SCALE;
+ break;
+ case PJ_UT_TIME:
+ unit_type = UnitOfMeasure::Type::TIME;
+ break;
+ case PJ_UT_PARAMETRIC:
+ unit_type = UnitOfMeasure::Type::PARAMETRIC;
+ break;
+ }
+ auto unit =
+ axis.unit_type == PJ_UT_ANGULAR
+ ? createAngularUnit(axis.unit_name, axis.unit_conv_factor)
+ : axis.unit_type == PJ_UT_LINEAR
+ ? createLinearUnit(axis.unit_name, axis.unit_conv_factor)
+ : UnitOfMeasure(axis.unit_name ? axis.unit_name : "unnamed",
+ axis.unit_conv_factor, unit_type);
+
+ return CoordinateSystemAxis::create(
+ createPropertyMapName(axis.name),
+ axis.abbreviation ? axis.abbreviation : std::string(), *dir, unit);
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a CoordinateSystem.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param type Coordinate system type.
+ * @param axis_count Number of axis
+ * @param axis Axis description (array of size axis_count)
+ *
+ * @return Object that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+
+PJ_OBJ *proj_obj_create_cs(PJ_CONTEXT *ctx, PJ_COORDINATE_SYSTEM_TYPE type,
+ int axis_count, const PJ_AXIS_DESCRIPTION *axis) {
+ try {
+ switch (type) {
+ case PJ_CS_TYPE_UNKNOWN:
+ return nullptr;
+
+ case PJ_CS_TYPE_CARTESIAN: {
+ if (axis_count == 2) {
+ return PJ_OBJ::create(CartesianCS::create(
+ PropertyMap(), createAxis(axis[0]), createAxis(axis[1])));
+ } else if (axis_count == 3) {
+ return PJ_OBJ::create(CartesianCS::create(
+ PropertyMap(), createAxis(axis[0]), createAxis(axis[1]),
+ createAxis(axis[2])));
+ }
+ break;
+ }
+
+ case PJ_CS_TYPE_ELLIPSOIDAL: {
+ if (axis_count == 2) {
+ return PJ_OBJ::create(EllipsoidalCS::create(
+ PropertyMap(), createAxis(axis[0]), createAxis(axis[1])));
+ } else if (axis_count == 3) {
+ return PJ_OBJ::create(EllipsoidalCS::create(
+ PropertyMap(), createAxis(axis[0]), createAxis(axis[1]),
+ createAxis(axis[2])));
+ }
+ break;
+ }
+
+ case PJ_CS_TYPE_VERTICAL: {
+ if (axis_count == 1) {
+ return PJ_OBJ::create(
+ VerticalCS::create(PropertyMap(), createAxis(axis[0])));
+ }
+ break;
+ }
+
+ case PJ_CS_TYPE_SPHERICAL: {
+ if (axis_count == 3) {
+ return PJ_OBJ::create(EllipsoidalCS::create(
+ PropertyMap(), createAxis(axis[0]), createAxis(axis[1]),
+ createAxis(axis[2])));
+ }
+ break;
+ }
+
+ case PJ_CS_TYPE_PARAMETRIC: {
+ if (axis_count == 1) {
+ return PJ_OBJ::create(
+ ParametricCS::create(PropertyMap(), createAxis(axis[0])));
+ }
+ break;
+ }
+
+ case PJ_CS_TYPE_ORDINAL: {
+ std::vector<CoordinateSystemAxisNNPtr> axisVector;
+ for (int i = 0; i < axis_count; i++) {
+ axisVector.emplace_back(createAxis(axis[i]));
+ }
+
+ return PJ_OBJ::create(OrdinalCS::create(PropertyMap(), axisVector));
+ }
+
+ case PJ_CS_TYPE_DATETIMETEMPORAL: {
+ if (axis_count == 1) {
+ return PJ_OBJ::create(DateTimeTemporalCS::create(
+ PropertyMap(), createAxis(axis[0])));
+ }
+ break;
+ }
+
+ case PJ_CS_TYPE_TEMPORALCOUNT: {
+ if (axis_count == 1) {
+ return PJ_OBJ::create(TemporalCountCS::create(
+ PropertyMap(), createAxis(axis[0])));
+ }
+ break;
+ }
+
+ case PJ_CS_TYPE_TEMPORALMEASURE: {
+ if (axis_count == 1) {
+ return PJ_OBJ::create(TemporalMeasureCS::create(
+ PropertyMap(), createAxis(axis[0])));
+ }
+ break;
+ }
+ }
+
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+ proj_log_error(ctx, __FUNCTION__, "Wrong value for axis_count");
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a CartesiansCS 2D
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param type Coordinate system type.
+ * @param unit_name Unit name.
+ * @param unit_conv_factor Unit conversion factor to SI.
+ *
+ * @return Object that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+
+PJ_OBJ *proj_obj_create_cartesian_2D_cs(PJ_CONTEXT *ctx,
+ PJ_CARTESIAN_CS_2D_TYPE type,
+ const char *unit_name,
+ double unit_conv_factor) {
+ try {
+ switch (type) {
+ case PJ_CART2D_EASTING_NORTHING:
+ return PJ_OBJ::create(CartesianCS::createEastingNorthing(
+ createLinearUnit(unit_name, unit_conv_factor)));
+
+ case PJ_CART2D_NORTHING_EASTING:
+ return PJ_OBJ::create(CartesianCS::createNorthingEasting(
+ createLinearUnit(unit_name, unit_conv_factor)));
+
+ case PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH:
+ return PJ_OBJ::create(
+ CartesianCS::createNorthPoleEastingSouthNorthingSouth(
+ createLinearUnit(unit_name, unit_conv_factor)));
+
+ case PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH:
+ return PJ_OBJ::create(
+ CartesianCS::createSouthPoleEastingNorthNorthingNorth(
+ createLinearUnit(unit_name, unit_conv_factor)));
+
+ case PJ_CART2D_WESTING_SOUTHING:
+ return PJ_OBJ::create(CartesianCS::createWestingSouthing(
+ createLinearUnit(unit_name, unit_conv_factor)));
+ }
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a Ellipsoidal 2D
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param type Coordinate system type.
+ * @param unit_name Unit name.
+ * @param unit_conv_factor Unit conversion factor to SI.
+ *
+ * @return Object that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+
+PJ_OBJ *proj_obj_create_ellipsoidal_2D_cs(PJ_CONTEXT *ctx,
+ PJ_ELLIPSOIDAL_CS_2D_TYPE type,
+ const char *unit_name,
+ double unit_conv_factor) {
+ try {
+ switch (type) {
+ case PJ_ELLPS2D_LONGITUDE_LATITUDE:
+ return PJ_OBJ::create(EllipsoidalCS::createLongitudeLatitude(
+ createAngularUnit(unit_name, unit_conv_factor)));
+
+ case PJ_ELLPS2D_LATITUDE_LONGITUDE:
+ return PJ_OBJ::create(EllipsoidalCS::createLatitudeLongitude(
+ createAngularUnit(unit_name, unit_conv_factor)));
+ }
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ProjectedCRS
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs_name CRS name. Or NULL
+ * @param geodetic_crs Base GeodeticCRS. Must not be NULL.
+ * @param conversion Conversion. Must not be NULL.
+ * @param coordinate_system Cartesian coordinate system. Must not be NULL.
+ *
+ * @return Object that must be unreferenced with
+ * proj_obj_destroy(), or NULL in case of error.
+ */
+
+PJ_OBJ *proj_obj_create_projected_crs(PJ_CONTEXT *ctx, const char *crs_name,
+ const PJ_OBJ *geodetic_crs,
+ const PJ_OBJ *conversion,
+ const PJ_OBJ *coordinate_system) {
+ SANITIZE_CTX(ctx);
+ auto geodCRS =
+ util::nn_dynamic_pointer_cast<GeodeticCRS>(geodetic_crs->obj);
+ if (!geodCRS) {
+ return nullptr;
+ }
+ auto conv = util::nn_dynamic_pointer_cast<Conversion>(conversion->obj);
+ if (!conv) {
+ return nullptr;
+ }
+ auto cs =
+ util::nn_dynamic_pointer_cast<CartesianCS>(coordinate_system->obj);
+ if (!cs) {
+ return nullptr;
+ }
+ try {
+ return PJ_OBJ::create(ProjectedCRS::create(
+ createPropertyMapName(crs_name), NN_NO_CHECK(geodCRS),
+ NN_NO_CHECK(conv), NN_NO_CHECK(cs)));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static PJ_OBJ *proj_obj_create_conversion(const ConversionNNPtr &conv) {
+ return PJ_OBJ::create(conv);
+}
+
+//! @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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_utm(PJ_CONTEXT *ctx, int zone, int north) {
+ SANITIZE_CTX(ctx);
+ try {
+ auto conv = Conversion::createUTM(PropertyMap(), zone, north != 0);
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_transverse_mercator(
+ PJ_CONTEXT *ctx, double center_lat, double center_long, double scale,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createTransverseMercator(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Scale(scale),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_gauss_schreiber_transverse_mercator(
+ PJ_CONTEXT *ctx, double center_lat, double center_long, double scale,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createGaussSchreiberTransverseMercator(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Scale(scale),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_transverse_mercator_south_oriented(
+ PJ_CONTEXT *ctx, double center_lat, double center_long, double scale,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createTransverseMercatorSouthOriented(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Scale(scale),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_two_point_equidistant(
+ PJ_CONTEXT *ctx, double latitude_first_point, double longitude_first_point,
+ double latitude_second_point, double longitude_secon_point,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createTwoPointEquidistant(
+ PropertyMap(), Angle(latitude_first_point, angUnit),
+ Angle(longitude_first_point, angUnit),
+ Angle(latitude_second_point, angUnit),
+ Angle(longitude_secon_point, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_tunisia_mapping_grid(
+ PJ_CONTEXT *ctx, double center_lat, double center_long,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createTunisiaMappingGrid(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_albers_equal_area(
+ PJ_CONTEXT *ctx, double latitude_false_origin,
+ double longitude_false_origin, double latitude_first_parallel,
+ double latitude_second_parallel, double easting_false_origin,
+ double northing_false_origin, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createAlbersEqualArea(
+ PropertyMap(), Angle(latitude_false_origin, angUnit),
+ Angle(longitude_false_origin, angUnit),
+ Angle(latitude_first_parallel, angUnit),
+ Angle(latitude_second_parallel, angUnit),
+ Length(easting_false_origin, linearUnit),
+ Length(northing_false_origin, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_lambert_conic_conformal_1sp(
+ PJ_CONTEXT *ctx, double center_lat, double center_long, double scale,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createLambertConicConformal_1SP(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Scale(scale),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_lambert_conic_conformal_2sp(
+ PJ_CONTEXT *ctx, double latitude_false_origin,
+ double longitude_false_origin, double latitude_first_parallel,
+ double latitude_second_parallel, double easting_false_origin,
+ double northing_false_origin, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createLambertConicConformal_2SP(
+ PropertyMap(), Angle(latitude_false_origin, angUnit),
+ Angle(longitude_false_origin, angUnit),
+ Angle(latitude_first_parallel, angUnit),
+ Angle(latitude_second_parallel, angUnit),
+ Length(easting_false_origin, linearUnit),
+ Length(northing_false_origin, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_lambert_conic_conformal_2sp_michigan(
+ PJ_CONTEXT *ctx, double latitude_false_origin,
+ double longitude_false_origin, double latitude_first_parallel,
+ double latitude_second_parallel, double easting_false_origin,
+ double northing_false_origin, double ellipsoid_scaling_factor,
+ const char *ang_unit_name, double ang_unit_conv_factor,
+ const char *linear_unit_name, double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createLambertConicConformal_2SP_Michigan(
+ PropertyMap(), Angle(latitude_false_origin, angUnit),
+ Angle(longitude_false_origin, angUnit),
+ Angle(latitude_first_parallel, angUnit),
+ Angle(latitude_second_parallel, angUnit),
+ Length(easting_false_origin, linearUnit),
+ Length(northing_false_origin, linearUnit),
+ Scale(ellipsoid_scaling_factor));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_lambert_conic_conformal_2sp_belgium(
+ PJ_CONTEXT *ctx, double latitude_false_origin,
+ double longitude_false_origin, double latitude_first_parallel,
+ double latitude_second_parallel, double easting_false_origin,
+ double northing_false_origin, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createLambertConicConformal_2SP_Belgium(
+ PropertyMap(), Angle(latitude_false_origin, angUnit),
+ Angle(longitude_false_origin, angUnit),
+ Angle(latitude_first_parallel, angUnit),
+ Angle(latitude_second_parallel, angUnit),
+ Length(easting_false_origin, linearUnit),
+ Length(northing_false_origin, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_azimuthal_equidistant(
+ PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createAzimuthalEquidistant(
+ PropertyMap(), Angle(latitude_nat_origin, angUnit),
+ Angle(longitude_nat_origin, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_guam_projection(
+ PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createGuamProjection(
+ PropertyMap(), Angle(latitude_nat_origin, angUnit),
+ Angle(longitude_nat_origin, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ProjectedCRS with a conversion based on the Bonne
+ * projection method.
+ *
+ * See osgeo::proj::operation::Conversion::createBonne().
+ *
+ * Linear parameters are expressed in (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_bonne(
+ PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createBonne(
+ PropertyMap(), Angle(latitude_nat_origin, angUnit),
+ Angle(longitude_nat_origin, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_lambert_cylindrical_equal_area_spherical(
+ PJ_CONTEXT *ctx, double latitude_first_parallel,
+ double longitude_nat_origin, double false_easting, double false_northing,
+ const char *ang_unit_name, double ang_unit_conv_factor,
+ const char *linear_unit_name, double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createLambertCylindricalEqualAreaSpherical(
+ PropertyMap(), Angle(latitude_first_parallel, angUnit),
+ Angle(longitude_nat_origin, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_lambert_cylindrical_equal_area(
+ PJ_CONTEXT *ctx, double latitude_first_parallel,
+ double longitude_nat_origin, double false_easting, double false_northing,
+ const char *ang_unit_name, double ang_unit_conv_factor,
+ const char *linear_unit_name, double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createLambertCylindricalEqualArea(
+ PropertyMap(), Angle(latitude_first_parallel, angUnit),
+ Angle(longitude_nat_origin, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_cassini_soldner(
+ PJ_CONTEXT *ctx, double center_lat, double center_long,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createCassiniSoldner(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_equidistant_conic(
+ PJ_CONTEXT *ctx, double center_lat, double center_long,
+ double latitude_first_parallel, double latitude_second_parallel,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createEquidistantConic(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit),
+ Angle(latitude_first_parallel, angUnit),
+ Angle(latitude_second_parallel, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_eckert_i(PJ_CONTEXT *ctx, double center_long,
+ double false_easting,
+ double false_northing,
+ const char *ang_unit_name,
+ double ang_unit_conv_factor,
+ const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createEckertI(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_eckert_ii(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createEckertII(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_eckert_iii(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createEckertIII(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_eckert_iv(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createEckertIV(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_eckert_v(PJ_CONTEXT *ctx, double center_long,
+ double false_easting,
+ double false_northing,
+ const char *ang_unit_name,
+ double ang_unit_conv_factor,
+ const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createEckertV(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_eckert_vi(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createEckertVI(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_equidistant_cylindrical(
+ PJ_CONTEXT *ctx, double latitude_first_parallel,
+ double longitude_nat_origin, double false_easting, double false_northing,
+ const char *ang_unit_name, double ang_unit_conv_factor,
+ const char *linear_unit_name, double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createEquidistantCylindrical(
+ PropertyMap(), Angle(latitude_first_parallel, angUnit),
+ Angle(longitude_nat_origin, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_equidistant_cylindrical_spherical(
+ PJ_CONTEXT *ctx, double latitude_first_parallel,
+ double longitude_nat_origin, double false_easting, double false_northing,
+ const char *ang_unit_name, double ang_unit_conv_factor,
+ const char *linear_unit_name, double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createEquidistantCylindricalSpherical(
+ PropertyMap(), Angle(latitude_first_parallel, angUnit),
+ Angle(longitude_nat_origin, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_gall(PJ_CONTEXT *ctx, double center_long,
+ double false_easting,
+ double false_northing,
+ const char *ang_unit_name,
+ double ang_unit_conv_factor,
+ const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv =
+ Conversion::createGall(PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_goode_homolosine(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createGoodeHomolosine(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_interrupted_goode_homolosine(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createInterruptedGoodeHomolosine(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_geostationary_satellite_sweep_x(
+ PJ_CONTEXT *ctx, double center_long, double height, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createGeostationarySatelliteSweepX(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(height, linearUnit), Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_geostationary_satellite_sweep_y(
+ PJ_CONTEXT *ctx, double center_long, double height, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createGeostationarySatelliteSweepY(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(height, linearUnit), Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ProjectedCRS with a conversion based on the Gnomonic
+ * projection method.
+ *
+ * See osgeo::proj::operation::Conversion::createGnomonic().
+ *
+ * Linear parameters are expressed in (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_gnomonic(
+ PJ_CONTEXT *ctx, double center_lat, double center_long,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createGnomonic(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_hotine_oblique_mercator_variant_a(
+ PJ_CONTEXT *ctx, double latitude_projection_centre,
+ double longitude_projection_centre, double azimuth_initial_line,
+ double angle_from_rectified_to_skrew_grid, double scale,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createHotineObliqueMercatorVariantA(
+ PropertyMap(), Angle(latitude_projection_centre, angUnit),
+ Angle(longitude_projection_centre, angUnit),
+ Angle(azimuth_initial_line, angUnit),
+ Angle(angle_from_rectified_to_skrew_grid, angUnit), Scale(scale),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_hotine_oblique_mercator_variant_b(
+ PJ_CONTEXT *ctx, double latitude_projection_centre,
+ double longitude_projection_centre, double azimuth_initial_line,
+ double angle_from_rectified_to_skrew_grid, double scale,
+ double easting_projection_centre, double northing_projection_centre,
+ const char *ang_unit_name, double ang_unit_conv_factor,
+ const char *linear_unit_name, double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createHotineObliqueMercatorVariantB(
+ PropertyMap(), Angle(latitude_projection_centre, angUnit),
+ Angle(longitude_projection_centre, angUnit),
+ Angle(azimuth_initial_line, angUnit),
+ Angle(angle_from_rectified_to_skrew_grid, angUnit), Scale(scale),
+ Length(easting_projection_centre, linearUnit),
+ Length(northing_projection_centre, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *
+proj_obj_create_conversion_hotine_oblique_mercator_two_point_natural_origin(
+ PJ_CONTEXT *ctx, double latitude_projection_centre, double latitude_point1,
+ double longitude_point1, double latitude_point2, double longitude_point2,
+ double scale, double easting_projection_centre,
+ double northing_projection_centre, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv =
+ Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin(
+ PropertyMap(), Angle(latitude_projection_centre, angUnit),
+ Angle(latitude_point1, angUnit),
+ Angle(longitude_point1, angUnit),
+ Angle(latitude_point2, angUnit),
+ Angle(longitude_point2, angUnit), Scale(scale),
+ Length(easting_projection_centre, linearUnit),
+ Length(northing_projection_centre, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ProjectedCRS with a conversion based on the Laborde
+ * Oblique Mercator projection method.
+ *
+ * See
+ * osgeo::proj::operation::Conversion::createLabordeObliqueMercator().
+ *
+ * Linear parameters are expressed in (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_laborde_oblique_mercator(
+ PJ_CONTEXT *ctx, double latitude_projection_centre,
+ double longitude_projection_centre, double azimuth_initial_line,
+ double scale, double false_easting, double false_northing,
+ const char *ang_unit_name, double ang_unit_conv_factor,
+ const char *linear_unit_name, double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createLabordeObliqueMercator(
+ PropertyMap(), Angle(latitude_projection_centre, angUnit),
+ Angle(longitude_projection_centre, angUnit),
+ Angle(azimuth_initial_line, angUnit), Scale(scale),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_international_map_world_polyconic(
+ PJ_CONTEXT *ctx, double center_long, double latitude_first_parallel,
+ double latitude_second_parallel, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createInternationalMapWorldPolyconic(
+ PropertyMap(), Angle(center_long, angUnit),
+ Angle(latitude_first_parallel, angUnit),
+ Angle(latitude_second_parallel, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_krovak_north_oriented(
+ PJ_CONTEXT *ctx, double latitude_projection_centre,
+ double longitude_of_origin, double colatitude_cone_axis,
+ double latitude_pseudo_standard_parallel,
+ double scale_factor_pseudo_standard_parallel, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createKrovakNorthOriented(
+ PropertyMap(), Angle(latitude_projection_centre, angUnit),
+ Angle(longitude_of_origin, angUnit),
+ Angle(colatitude_cone_axis, angUnit),
+ Angle(latitude_pseudo_standard_parallel, angUnit),
+ Scale(scale_factor_pseudo_standard_parallel),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ProjectedCRS with a conversion based on the Krovak
+ * projection method.
+ *
+ * See osgeo::proj::operation::Conversion::createKrovak().
+ *
+ * Linear parameters are expressed in (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_krovak(
+ PJ_CONTEXT *ctx, double latitude_projection_centre,
+ double longitude_of_origin, double colatitude_cone_axis,
+ double latitude_pseudo_standard_parallel,
+ double scale_factor_pseudo_standard_parallel, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createKrovak(
+ PropertyMap(), Angle(latitude_projection_centre, angUnit),
+ Angle(longitude_of_origin, angUnit),
+ Angle(colatitude_cone_axis, angUnit),
+ Angle(latitude_pseudo_standard_parallel, angUnit),
+ Scale(scale_factor_pseudo_standard_parallel),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_lambert_azimuthal_equal_area(
+ PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createLambertAzimuthalEqualArea(
+ PropertyMap(), Angle(latitude_nat_origin, angUnit),
+ Angle(longitude_nat_origin, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_miller_cylindrical(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createMillerCylindrical(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ProjectedCRS with a conversion based on the Mercator
+ * projection method.
+ *
+ * See osgeo::proj::operation::Conversion::createMercatorVariantA().
+ *
+ * Linear parameters are expressed in (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_mercator_variant_a(
+ PJ_CONTEXT *ctx, double center_lat, double center_long, double scale,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createMercatorVariantA(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Scale(scale),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ProjectedCRS with a conversion based on the Mercator
+ * projection method.
+ *
+ * See osgeo::proj::operation::Conversion::createMercatorVariantB().
+ *
+ * Linear parameters are expressed in (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_mercator_variant_b(
+ PJ_CONTEXT *ctx, double latitude_first_parallel, double center_long,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createMercatorVariantB(
+ PropertyMap(), Angle(latitude_first_parallel, angUnit),
+ Angle(center_long, angUnit), Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_popular_visualisation_pseudo_mercator(
+ PJ_CONTEXT *ctx, double center_lat, double center_long,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createPopularVisualisationPseudoMercator(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ProjectedCRS with a conversion based on the Mollweide
+ * projection method.
+ *
+ * See osgeo::proj::operation::Conversion::createMollweide().
+ *
+ * Linear parameters are expressed in (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_mollweide(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createMollweide(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_new_zealand_mapping_grid(
+ PJ_CONTEXT *ctx, double center_lat, double center_long,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createNewZealandMappingGrid(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_oblique_stereographic(
+ PJ_CONTEXT *ctx, double center_lat, double center_long, double scale,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createObliqueStereographic(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Scale(scale),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ProjectedCRS with a conversion based on the
+ * Orthographic projection method.
+ *
+ * See osgeo::proj::operation::Conversion::createOrthographic().
+ *
+ * Linear parameters are expressed in (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_orthographic(
+ PJ_CONTEXT *ctx, double center_lat, double center_long,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createOrthographic(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_american_polyconic(
+ PJ_CONTEXT *ctx, double center_lat, double center_long,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createAmericanPolyconic(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_polar_stereographic_variant_a(
+ PJ_CONTEXT *ctx, double center_lat, double center_long, double scale,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createPolarStereographicVariantA(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Scale(scale),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_polar_stereographic_variant_b(
+ PJ_CONTEXT *ctx, double latitude_standard_parallel,
+ double longitude_of_origin, double false_easting, double false_northing,
+ const char *ang_unit_name, double ang_unit_conv_factor,
+ const char *linear_unit_name, double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createPolarStereographicVariantB(
+ PropertyMap(), Angle(latitude_standard_parallel, angUnit),
+ Angle(longitude_of_origin, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ProjectedCRS with a conversion based on the Robinson
+ * projection method.
+ *
+ * See osgeo::proj::operation::Conversion::createRobinson().
+ *
+ * Linear parameters are expressed in (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_robinson(PJ_CONTEXT *ctx, double center_long,
+ double false_easting,
+ double false_northing,
+ const char *ang_unit_name,
+ double ang_unit_conv_factor,
+ const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createRobinson(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ProjectedCRS with a conversion based on the Sinusoidal
+ * projection method.
+ *
+ * See osgeo::proj::operation::Conversion::createSinusoidal().
+ *
+ * Linear parameters are expressed in (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_sinusoidal(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createSinusoidal(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ProjectedCRS with a conversion based on the
+ * Stereographic projection method.
+ *
+ * See osgeo::proj::operation::Conversion::createStereographic().
+ *
+ * Linear parameters are expressed in (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_stereographic(
+ PJ_CONTEXT *ctx, double center_lat, double center_long, double scale,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createStereographic(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Scale(scale),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_van_der_grinten(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createVanDerGrinten(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_wagner_i(PJ_CONTEXT *ctx, double center_long,
+ double false_easting,
+ double false_northing,
+ const char *ang_unit_name,
+ double ang_unit_conv_factor,
+ const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createWagnerI(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_wagner_ii(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createWagnerII(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_wagner_iii(
+ PJ_CONTEXT *ctx, double latitude_true_scale, double center_long,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createWagnerIII(
+ PropertyMap(), Angle(latitude_true_scale, angUnit),
+ Angle(center_long, angUnit), Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_wagner_iv(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createWagnerIV(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_wagner_v(PJ_CONTEXT *ctx, double center_long,
+ double false_easting,
+ double false_northing,
+ const char *ang_unit_name,
+ double ang_unit_conv_factor,
+ const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createWagnerV(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_wagner_vi(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createWagnerVI(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_wagner_vii(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createWagnerVII(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_quadrilateralized_spherical_cube(
+ PJ_CONTEXT *ctx, double center_lat, double center_long,
+ double false_easting, double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createQuadrilateralizedSphericalCube(
+ PropertyMap(), Angle(center_lat, angUnit),
+ Angle(center_long, angUnit), Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_spherical_cross_track_height(
+ PJ_CONTEXT *ctx, double peg_point_lat, double peg_point_long,
+ double peg_point_heading, double peg_point_height,
+ const char *ang_unit_name, double ang_unit_conv_factor,
+ const char *linear_unit_name, double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createSphericalCrossTrackHeight(
+ PropertyMap(), Angle(peg_point_lat, angUnit),
+ Angle(peg_point_long, angUnit), Angle(peg_point_heading, angUnit),
+ Length(peg_point_height, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+/** \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 (linear_unit_name,
+ * linear_unit_conv_factor).
+ * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).
+ */
+PJ_OBJ *proj_obj_create_conversion_equal_earth(
+ PJ_CONTEXT *ctx, double center_long, double false_easting,
+ double false_northing, const char *ang_unit_name,
+ double ang_unit_conv_factor, const char *linear_unit_name,
+ double linear_unit_conv_factor) {
+ SANITIZE_CTX(ctx);
+ try {
+ UnitOfMeasure linearUnit(
+ createLinearUnit(linear_unit_name, linear_unit_conv_factor));
+ UnitOfMeasure angUnit(
+ createAngularUnit(ang_unit_name, ang_unit_conv_factor));
+ auto conv = Conversion::createEqualEarth(
+ PropertyMap(), Angle(center_long, angUnit),
+ Length(false_easting, linearUnit),
+ Length(false_northing, linearUnit));
+ return proj_obj_create_conversion(conv);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+/* 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 ctx PROJ context, or NULL for default context
+ * @param coordoperation Objet of type CoordinateOperation or derived classes
+ * (must not be NULL)
+ * @return TRUE or FALSE.
+ */
+
+int proj_coordoperation_is_instanciable(PJ_CONTEXT *ctx,
+ const PJ_OBJ *coordoperation) {
+ assert(coordoperation);
+ auto op =
+ dynamic_cast<const CoordinateOperation *>(coordoperation->obj.get());
+ if (!op) {
+ proj_log_error(ctx, __FUNCTION__,
+ "Object is not a CoordinateOperation");
+ return 0;
+ }
+ auto dbContext = getDBcontextNoException(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 ctx PROJ context, or NULL for default context
+ * @param coordoperation Objet of type SingleOperation or derived classes
+ * (must not be NULL)
+ */
+
+int proj_coordoperation_get_param_count(PJ_CONTEXT *ctx,
+ const PJ_OBJ *coordoperation) {
+ SANITIZE_CTX(ctx);
+ assert(coordoperation);
+ auto op = dynamic_cast<const SingleOperation *>(coordoperation->obj.get());
+ if (!op) {
+ proj_log_error(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 ctx PROJ context, or NULL for default context
+ * @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_CONTEXT *ctx,
+ const PJ_OBJ *coordoperation,
+ const char *name) {
+ SANITIZE_CTX(ctx);
+ assert(coordoperation);
+ assert(name);
+ auto op = dynamic_cast<const SingleOperation *>(coordoperation->obj.get());
+ if (!op) {
+ proj_log_error(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 ctx PROJ context, or NULL for default context
+ * @param coordoperation Objet of type SingleOperation or derived classes
+ * (must not be NULL)
+ * @param index Parameter index.
+ * @param out_name Pointer to a string value to store the parameter name. or
+ * NULL
+ * @param out_auth_name Pointer to a string value to store the parameter
+ * authority name. or NULL
+ * @param out_code Pointer to a string value to store the parameter
+ * code. or NULL
+ * @param out_value Pointer to a double value to store the parameter
+ * value (if numeric). or NULL
+ * @param out_value_string Pointer to a string value to store the parameter
+ * value (if of type string). or NULL
+ * @param out_unit_conv_factor Pointer to a double value to store the parameter
+ * unit conversion factor. or NULL
+ * @param out_unit_name Pointer to a string value to store the parameter
+ * unit name. or NULL
+ * @param out_unit_auth_name Pointer to a string value to store the
+ * unit authority name. or NULL
+ * @param out_unit_code Pointer to a string value to store the
+ * unit code. or NULL
+ * @param out_unit_category Pointer to a string value to store the parameter
+ * name. or
+ * NULL. This value might be "unknown", "none", "linear", "angular", "scale",
+ * "time" or "parametric";
+ * @return TRUE in case of success.
+ */
+
+int proj_coordoperation_get_param(
+ PJ_CONTEXT *ctx, const PJ_OBJ *coordoperation, int index,
+ const char **out_name, const char **out_auth_name, const char **out_code,
+ double *out_value, const char **out_value_string,
+ double *out_unit_conv_factor, const char **out_unit_name,
+ const char **out_unit_auth_name, const char **out_unit_code,
+ const char **out_unit_category) {
+ SANITIZE_CTX(ctx);
+ assert(coordoperation);
+ auto op = dynamic_cast<const SingleOperation *>(coordoperation->obj.get());
+ if (!op) {
+ proj_log_error(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(ctx, __FUNCTION__, "Invalid index");
+ return false;
+ }
+
+ const auto &param = parameters[index];
+ const auto &param_ids = param->identifiers();
+ if (out_name) {
+ *out_name = param->name()->description()->c_str();
+ }
+ if (out_auth_name) {
+ if (!param_ids.empty()) {
+ *out_auth_name = param_ids[0]->codeSpace()->c_str();
+ } else {
+ *out_auth_name = nullptr;
+ }
+ }
+ if (out_code) {
+ if (!param_ids.empty()) {
+ *out_code = param_ids[0]->code().c_str();
+ } else {
+ *out_code = 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 (out_value) {
+ *out_value = 0;
+ if (paramValue) {
+ if (paramValue->type() == ParameterValue::Type::MEASURE) {
+ *out_value = paramValue->value().value();
+ }
+ }
+ }
+ if (out_value_string) {
+ *out_value_string = nullptr;
+ if (paramValue) {
+ if (paramValue->type() == ParameterValue::Type::FILENAME) {
+ *out_value_string = paramValue->valueFile().c_str();
+ } else if (paramValue->type() == ParameterValue::Type::STRING) {
+ *out_value_string = paramValue->stringValue().c_str();
+ }
+ }
+ }
+ if (out_unit_conv_factor) {
+ *out_unit_conv_factor = 0;
+ }
+ if (out_unit_name) {
+ *out_unit_name = nullptr;
+ }
+ if (out_unit_auth_name) {
+ *out_unit_auth_name = nullptr;
+ }
+ if (out_unit_code) {
+ *out_unit_code = nullptr;
+ }
+ if (out_unit_category) {
+ *out_unit_category = nullptr;
+ }
+ if (paramValue) {
+ if (paramValue->type() == ParameterValue::Type::MEASURE) {
+ const auto &unit = paramValue->value().unit();
+ if (out_unit_conv_factor) {
+ *out_unit_conv_factor = unit.conversionToSI();
+ }
+ if (out_unit_name) {
+ *out_unit_name = unit.name().c_str();
+ }
+ if (out_unit_auth_name) {
+ *out_unit_auth_name = unit.codeSpace().c_str();
+ }
+ if (out_unit_code) {
+ *out_unit_code = unit.code().c_str();
+ }
+ if (out_unit_category) {
+ *out_unit_category = get_unit_category(unit.type());
+ }
+ }
+ }
+
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameters of a Helmert transformation as WKT1 TOWGS84
+ * values.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param coordoperation Objet of type Transformation, that can be represented
+ * as a WKT1 TOWGS84 node (must not be NULL)
+ * @param out_values Pointer to an array of value_count double values.
+ * @param value_count Size of out_values array.
+ * @param emit_error_if_incompatible Boolean to inicate if an error must be
+ * logged if coordoperation is not compatible with a WKT1 TOWGS84
+ * representation.
+ * @return TRUE in case of success, or FALSE if coordoperation is not
+ * compatible with a WKT1 TOWGS84 representation.
+ */
+
+int proj_coordoperation_get_towgs84_values(PJ_CONTEXT *ctx,
+ const PJ_OBJ *coordoperation,
+ double *out_values, int value_count,
+ int emit_error_if_incompatible) {
+ SANITIZE_CTX(ctx);
+ assert(coordoperation);
+ auto transf =
+ dynamic_cast<const Transformation *>(coordoperation->obj.get());
+ if (!transf) {
+ if (emit_error_if_incompatible) {
+ proj_log_error(ctx, __FUNCTION__, "Object is not a Transformation");
+ }
+ return FALSE;
+ }
+ try {
+ auto values = transf->getTOWGS84Parameters();
+ for (int i = 0;
+ i < value_count && static_cast<size_t>(i) < values.size(); i++) {
+ out_values[i] = values[i];
+ }
+ return TRUE;
+ } catch (const std::exception &e) {
+ if (emit_error_if_incompatible) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return FALSE;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the number of grids used by a CoordinateOperation
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param coordoperation Objet of type CoordinateOperation or derived classes
+ * (must not be NULL)
+ */
+
+int proj_coordoperation_get_grid_used_count(PJ_CONTEXT *ctx,
+ const PJ_OBJ *coordoperation) {
+ SANITIZE_CTX(ctx);
+ assert(coordoperation);
+ auto co =
+ dynamic_cast<const CoordinateOperation *>(coordoperation->obj.get());
+ if (!co) {
+ proj_log_error(ctx, __FUNCTION__,
+ "Object is not a CoordinateOperation");
+ return 0;
+ }
+ auto dbContext = getDBcontextNoException(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(ctx, __FUNCTION__, e.what());
+ return 0;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a parameter of a SingleOperation
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param coordoperation Objet of type SingleOperation or derived classes
+ * (must not be NULL)
+ * @param index Parameter index.
+ * @param out_short_name Pointer to a string value to store the grid short name.
+ * or NULL
+ * @param out_full_name Pointer to a string value to store the grid full
+ * filename. or NULL
+ * @param out_package_name Pointer to a string value to store the package name
+ * where
+ * the grid might be found. or NULL
+ * @param out_url Pointer to a string value to store the grid URL or the
+ * package URL where the grid might be found. or NULL
+ * @param out_direct_download Pointer to a int (boolean) value to store whether
+ * *out_url can be downloaded directly. or NULL
+ * @param out_open_license Pointer to a int (boolean) value to store whether
+ * the grid is released with an open license. or NULL
+ * @param out_available 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_CONTEXT *ctx, const PJ_OBJ *coordoperation, int index,
+ const char **out_short_name, const char **out_full_name,
+ const char **out_package_name, const char **out_url,
+ int *out_direct_download, int *out_open_license, int *out_available) {
+ SANITIZE_CTX(ctx);
+ const int count =
+ proj_coordoperation_get_grid_used_count(ctx, coordoperation);
+ if (index < 0 || index >= count) {
+ proj_log_error(ctx, __FUNCTION__, "Invalid index");
+ return false;
+ }
+
+ const auto &gridDesc = coordoperation->gridsNeeded[index];
+ if (out_short_name) {
+ *out_short_name = gridDesc.shortName.c_str();
+ }
+
+ if (out_full_name) {
+ *out_full_name = gridDesc.fullName.c_str();
+ }
+
+ if (out_package_name) {
+ *out_package_name = gridDesc.packageName.c_str();
+ }
+
+ if (out_url) {
+ *out_url = gridDesc.url.c_str();
+ }
+
+ if (out_direct_download) {
+ *out_direct_download = gridDesc.directDownload;
+ }
+
+ if (out_open_license) {
+ *out_open_license = gridDesc.openLicense;
+ }
+
+ if (out_available) {
+ *out_available = gridDesc.available;
+ }
+
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Opaque object representing an operation factory context. */
+struct PJ_OPERATION_FACTORY_CONTEXT {
+ //! @cond Doxygen_Suppress
+ CoordinateOperationContextNNPtr operationContext;
+
+ explicit PJ_OPERATION_FACTORY_CONTEXT(
+ CoordinateOperationContextNNPtr &&operationContextIn)
+ : 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_destroy() after use.
+ *
+ * If authority is NULL or the empty string, then coordinate
+ * operations from any authority will be searched, with the restrictions set
+ * in the authority_to_authority_preference database table.
+ * If authority is set to "any", then coordinate
+ * operations from any authority will be searched
+ * If authority is a non-empty string different of "any",
+ * then coordinate operatiosn will be searched only in that authority namespace.
+ *
+ * @param ctx Context, or NULL for default context.
+ * @param authority Name of authority to which to restrict the search of
+ * candidate operations.
+ * @return Object that must be unreferenced with
+ * proj_operation_factory_context_destroy(), 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(
+ std::move(operationContext));
+ } else {
+ auto operationContext =
+ CoordinateOperationContext::create(nullptr, nullptr, 0.0);
+ return new PJ_OPERATION_FACTORY_CONTEXT(
+ 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 ctx Object, or NULL.
+ */
+void proj_operation_factory_context_destroy(PJ_OPERATION_FACTORY_CONTEXT *ctx) {
+ delete ctx;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set the desired accuracy of the resulting coordinate transformations.
+ * @param ctx PROJ context, or NULL for default context
+ * @param factory_ctx 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_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx,
+ double accuracy) {
+ SANITIZE_CTX(ctx);
+ assert(factory_ctx);
+ try {
+ factory_ctx->operationContext->setDesiredAccuracy(accuracy);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set the desired area of interest for the resulting coordinate
+ * transformations.
+ *
+ * For an area of interest crossing the anti-meridian, west_lon_degree will be
+ * greater than east_lon_degree.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param factory_ctx Operation factory context. must not be NULL
+ * @param west_lon_degree West longitude (in degrees).
+ * @param south_lat_degree South latitude (in degrees).
+ * @param east_lon_degree East longitude (in degrees).
+ * @param north_lat_degree North latitude (in degrees).
+ */
+void proj_operation_factory_context_set_area_of_interest(
+ PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx,
+ double west_lon_degree, double south_lat_degree, double east_lon_degree,
+ double north_lat_degree) {
+ SANITIZE_CTX(ctx);
+ assert(factory_ctx);
+ try {
+ factory_ctx->operationContext->setAreaOfInterest(
+ Extent::createFromBBOX(west_lon_degree, south_lat_degree,
+ east_lon_degree, north_lat_degree));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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 ctx PROJ context, or NULL for default context
+ * @param factory_ctx 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_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx,
+ PROJ_CRS_EXTENT_USE use) {
+ SANITIZE_CTX(ctx);
+ assert(factory_ctx);
+ try {
+ switch (use) {
+ case PJ_CRS_EXTENT_NONE:
+ factory_ctx->operationContext->setSourceAndTargetCRSExtentUse(
+ CoordinateOperationContext::SourceTargetCRSExtentUse::NONE);
+ break;
+
+ case PJ_CRS_EXTENT_BOTH:
+ factory_ctx->operationContext->setSourceAndTargetCRSExtentUse(
+ CoordinateOperationContext::SourceTargetCRSExtentUse::BOTH);
+ break;
+
+ case PJ_CRS_EXTENT_INTERSECTION:
+ factory_ctx->operationContext->setSourceAndTargetCRSExtentUse(
+ CoordinateOperationContext::SourceTargetCRSExtentUse::
+ INTERSECTION);
+ break;
+
+ case PJ_CRS_EXTENT_SMALLEST:
+ factory_ctx->operationContext->setSourceAndTargetCRSExtentUse(
+ CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST);
+ break;
+ }
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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 ctx PROJ context, or NULL for default context
+ * @param factory_ctx Operation factory context. must not be NULL
+ * @param criterion patial criterion to use
+ */
+void PROJ_DLL proj_operation_factory_context_set_spatial_criterion(
+ PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx,
+ PROJ_SPATIAL_CRITERION criterion) {
+ SANITIZE_CTX(ctx);
+ assert(factory_ctx);
+ try {
+ switch (criterion) {
+ case PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT:
+ factory_ctx->operationContext->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::
+ STRICT_CONTAINMENT);
+ break;
+
+ case PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION:
+ factory_ctx->operationContext->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::
+ PARTIAL_INTERSECTION);
+ break;
+ }
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set how grid availability is used.
+ *
+ * The default is USE_FOR_SORTING.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param factory_ctx 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_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx,
+ PROJ_GRID_AVAILABILITY_USE use) {
+ SANITIZE_CTX(ctx);
+ assert(factory_ctx);
+ try {
+ switch (use) {
+ case PROJ_GRID_AVAILABILITY_USED_FOR_SORTING:
+ factory_ctx->operationContext->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ USE_FOR_SORTING);
+ break;
+
+ case PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID:
+ factory_ctx->operationContext->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID);
+ break;
+
+ case PROJ_GRID_AVAILABILITY_IGNORED:
+ factory_ctx->operationContext->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ break;
+ }
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether PROJ alternative grid names should be substituted to
+ * the official authority names.
+ *
+ * The default is true.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param factory_ctx 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_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx,
+ int usePROJNames) {
+ SANITIZE_CTX(ctx);
+ assert(factory_ctx);
+ try {
+ factory_ctx->operationContext->setUsePROJAlternativeGridNames(
+ usePROJNames != 0);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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 ctx PROJ context, or NULL for default context
+ * @param factory_ctx 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_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, int allow) {
+ SANITIZE_CTX(ctx);
+ assert(factory_ctx);
+ try {
+ factory_ctx->operationContext->setAllowUseIntermediateCRS(allow != 0);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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 ctx PROJ context, or NULL for default context
+ * @param factory_ctx 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_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx,
+ const char *const *list_of_auth_name_codes) {
+ SANITIZE_CTX(ctx);
+ assert(factory_ctx);
+ try {
+ 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])));
+ }
+ factory_ctx->operationContext->setIntermediateCRS(pivots);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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 ctx PROJ context, or NULL for default context
+ * @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_destroy(), or NULL in case of error.
+ */
+PJ_OBJ_LIST *proj_obj_create_operations(
+ PJ_CONTEXT *ctx, const PJ_OBJ *source_crs, const PJ_OBJ *target_crs,
+ const PJ_OPERATION_FACTORY_CONTEXT *operationContext) {
+ SANITIZE_CTX(ctx);
+ assert(source_crs);
+ assert(target_crs);
+ assert(operationContext);
+
+ auto sourceCRS = nn_dynamic_pointer_cast<CRS>(source_crs->obj);
+ if (!sourceCRS) {
+ proj_log_error(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(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(std::move(objects));
+ } catch (const std::exception &e) {
+ proj_log_error(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(const 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_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @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_destroy(),
+ * or nullptr in case of error.
+ */
+
+PJ_OBJ *proj_obj_list_get(PJ_CONTEXT *ctx, const PJ_OBJ_LIST *result,
+ int index) {
+ SANITIZE_CTX(ctx);
+ assert(result);
+ if (index < 0 || index >= proj_obj_list_get_count(result)) {
+ proj_log_error(ctx, __FUNCTION__, "Invalid index");
+ return nullptr;
+ }
+ return PJ_OBJ::create(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_destroy(PJ_OBJ_LIST *result) { delete result; }
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the accuracy (in metre) of a coordinate operation.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param coordoperation Coordinate operation. Must not be NULL.
+ * @return the accuracy, or a negative value if unknown or in case of error.
+ */
+double proj_coordoperation_get_accuracy(PJ_CONTEXT *ctx,
+ const PJ_OBJ *coordoperation) {
+ SANITIZE_CTX(ctx);
+ assert(coordoperation);
+ auto co =
+ dynamic_cast<const CoordinateOperation *>(coordoperation->obj.get());
+ if (!co) {
+ proj_log_error(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;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the datum of a SingleCRS.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs Objet of type SingleCRS (must not be NULL)
+ * @return Object that must be unreferenced with proj_obj_destroy(), or NULL
+ * in case of error (or if there is no datum)
+ */
+PJ_OBJ *proj_obj_crs_get_datum(PJ_CONTEXT *ctx, const PJ_OBJ *crs) {
+ SANITIZE_CTX(ctx);
+ assert(crs);
+ auto l_crs = dynamic_cast<const SingleCRS *>(crs->obj.get());
+ if (!l_crs) {
+ proj_log_error(ctx, __FUNCTION__, "Object is not a SingleCRS");
+ return nullptr;
+ }
+ const auto &datum = l_crs->datum();
+ if (!datum) {
+ return nullptr;
+ }
+ return PJ_OBJ::create(NN_NO_CHECK(datum));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the coordinate system of a SingleCRS.
+ *
+ * The returned object must be unreferenced with proj_obj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs Objet of type SingleCRS (must not be NULL)
+ * @return Object that must be unreferenced with proj_obj_destroy(), or NULL
+ * in case of error.
+ */
+PJ_OBJ *proj_obj_crs_get_coordinate_system(PJ_CONTEXT *ctx, const PJ_OBJ *crs) {
+ SANITIZE_CTX(ctx);
+ assert(crs);
+ auto l_crs = dynamic_cast<const SingleCRS *>(crs->obj.get());
+ if (!l_crs) {
+ proj_log_error(ctx, __FUNCTION__, "Object is not a SingleCRS");
+ return nullptr;
+ }
+ return PJ_OBJ::create(l_crs->coordinateSystem());
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the type of the coordinate system.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param cs Objet of type CoordinateSystem (must not be NULL)
+ * @return type, or PJ_CS_TYPE_UNKNOWN in case of error.
+ */
+PJ_COORDINATE_SYSTEM_TYPE proj_obj_cs_get_type(PJ_CONTEXT *ctx,
+ const PJ_OBJ *cs) {
+ SANITIZE_CTX(ctx);
+ assert(cs);
+ auto l_cs = dynamic_cast<const CoordinateSystem *>(cs->obj.get());
+ if (!l_cs) {
+ proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateSystem");
+ return PJ_CS_TYPE_UNKNOWN;
+ }
+ if (dynamic_cast<const CartesianCS *>(l_cs)) {
+ return PJ_CS_TYPE_CARTESIAN;
+ }
+ if (dynamic_cast<const EllipsoidalCS *>(l_cs)) {
+ return PJ_CS_TYPE_ELLIPSOIDAL;
+ }
+ if (dynamic_cast<const VerticalCS *>(l_cs)) {
+ return PJ_CS_TYPE_VERTICAL;
+ }
+ if (dynamic_cast<const SphericalCS *>(l_cs)) {
+ return PJ_CS_TYPE_SPHERICAL;
+ }
+ if (dynamic_cast<const OrdinalCS *>(l_cs)) {
+ return PJ_CS_TYPE_ORDINAL;
+ }
+ if (dynamic_cast<const ParametricCS *>(l_cs)) {
+ return PJ_CS_TYPE_PARAMETRIC;
+ }
+ if (dynamic_cast<const DateTimeTemporalCS *>(l_cs)) {
+ return PJ_CS_TYPE_DATETIMETEMPORAL;
+ }
+ if (dynamic_cast<const TemporalCountCS *>(l_cs)) {
+ return PJ_CS_TYPE_TEMPORALCOUNT;
+ }
+ if (dynamic_cast<const TemporalMeasureCS *>(l_cs)) {
+ return PJ_CS_TYPE_TEMPORALMEASURE;
+ }
+ return PJ_CS_TYPE_UNKNOWN;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the number of axis of the coordinate system.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param cs Objet of type CoordinateSystem (must not be NULL)
+ * @return number of axis, or -1 in case of error.
+ */
+int proj_obj_cs_get_axis_count(PJ_CONTEXT *ctx, const PJ_OBJ *cs) {
+ SANITIZE_CTX(ctx);
+ assert(cs);
+ auto l_cs = dynamic_cast<const CoordinateSystem *>(cs->obj.get());
+ if (!l_cs) {
+ proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateSystem");
+ return -1;
+ }
+ return static_cast<int>(l_cs->axisList().size());
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns information on an axis
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param cs Objet of type CoordinateSystem (must not be NULL)
+ * @param index Index of the coordinate system (between 0 and
+ * proj_obj_cs_get_axis_count() - 1)
+ * @param out_name Pointer to a string value to store the axis name. or NULL
+ * @param out_abbrev Pointer to a string value to store the axis abbreviation.
+ * or NULL
+ * @param out_direction Pointer to a string value to store the axis direction.
+ * or NULL
+ * @param out_unit_conv_factor Pointer to a double value to store the axis
+ * unit conversion factor. or NULL
+ * @param out_unit_name Pointer to a string value to store the axis
+ * unit name. or NULL
+ * @param out_unit_auth_name Pointer to a string value to store the axis
+ * unit authority name. or NULL
+ * @param out_unit_code Pointer to a string value to store the axis
+ * unit code. or NULL
+ * @return TRUE in case of success
+ */
+int proj_obj_cs_get_axis_info(PJ_CONTEXT *ctx, const PJ_OBJ *cs, int index,
+ const char **out_name, const char **out_abbrev,
+ const char **out_direction,
+ double *out_unit_conv_factor,
+ const char **out_unit_name,
+ const char **out_unit_auth_name,
+ const char **out_unit_code) {
+ SANITIZE_CTX(ctx);
+ assert(cs);
+ auto l_cs = dynamic_cast<const CoordinateSystem *>(cs->obj.get());
+ if (!l_cs) {
+ proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateSystem");
+ return false;
+ }
+ const auto &axisList = l_cs->axisList();
+ if (index < 0 || static_cast<size_t>(index) >= axisList.size()) {
+ proj_log_error(ctx, __FUNCTION__, "Invalid index");
+ return false;
+ }
+ const auto &axis = axisList[index];
+ if (out_name) {
+ *out_name = axis->nameStr().c_str();
+ }
+ if (out_abbrev) {
+ *out_abbrev = axis->abbreviation().c_str();
+ }
+ if (out_direction) {
+ *out_direction = axis->direction().toString().c_str();
+ }
+ if (out_unit_conv_factor) {
+ *out_unit_conv_factor = axis->unit().conversionToSI();
+ }
+ if (out_unit_name) {
+ *out_unit_name = axis->unit().name().c_str();
+ }
+ if (out_unit_auth_name) {
+ *out_unit_auth_name = axis->unit().codeSpace().c_str();
+ }
+ if (out_unit_code) {
+ *out_unit_code = axis->unit().code().c_str();
+ }
+ return true;
+}
diff --git a/src/iso19111/common.cpp b/src/iso19111/common.cpp
new file mode 100644
index 00000000..bd690924
--- /dev/null
+++ b/src/iso19111/common.cpp
@@ -0,0 +1,1115 @@
+/******************************************************************************
+ *
+ * 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.
+ * @param maxRelativeError Maximum relative error allowed.
+ * @return true if objects are equivalent.
+ */
+bool Measure::_isEquivalentTo(const Measure &other,
+ util::IComparable::Criterion criterion,
+ double maxRelativeError) const {
+ if (criterion == util::IComparable::Criterion::STRICT) {
+ return operator==(other);
+ }
+ return std::fabs(getSIValue() - other.getSIValue()) <=
+ maxRelativeError * 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)
+{
+ const auto pVal = properties.get(NAME_KEY);
+ if (!pVal) {
+ return;
+ }
+ if (const auto genVal = dynamic_cast<const BoxedValue *>(pVal->get())) {
+ if (genVal->type() == BoxedValue::Type::STRING) {
+ name = Identifier::createFromDescription(genVal->stringValue());
+ } else {
+ throw InvalidValueTypeException("Invalid value type for " +
+ NAME_KEY);
+ }
+ } else {
+ if (auto identifier =
+ util::nn_dynamic_pointer_cast<Identifier>(*pVal)) {
+ name = NN_NO_CHECK(identifier);
+ } else {
+ throw InvalidValueTypeException("Invalid value type for " +
+ NAME_KEY);
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void IdentifiedObject::Private::setIdentifiers(
+ const PropertyMap &properties) // throw(InvalidValueTypeException)
+{
+ auto pVal = properties.get(IDENTIFIERS_KEY);
+ if (!pVal) {
+
+ pVal = properties.get(Identifier::CODE_KEY);
+ if (pVal) {
+ identifiers.push_back(
+ Identifier::create(std::string(), properties));
+ }
+ return;
+ }
+ if (auto identifier = util::nn_dynamic_pointer_cast<Identifier>(*pVal)) {
+ identifiers.clear();
+ identifiers.push_back(NN_NO_CHECK(identifier));
+ } else {
+ if (auto array = dynamic_cast<const ArrayOfBaseObject *>(pVal->get())) {
+ 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)
+{
+ const auto pVal = properties.get(ALIAS_KEY);
+ if (!pVal) {
+ return;
+ }
+ if (auto l_name = util::nn_dynamic_pointer_cast<GenericName>(*pVal)) {
+ aliases.clear();
+ aliases.push_back(NN_NO_CHECK(l_name));
+ } else {
+ if (const auto array =
+ dynamic_cast<const ArrayOfBaseObject *>(pVal->get())) {
+ 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 =
+ dynamic_cast<const BoxedValue *>(val.get())) {
+ 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);
+
+ {
+ const auto pVal = properties.get(DEPRECATED_KEY);
+ if (pVal) {
+ if (const auto genVal =
+ dynamic_cast<const BoxedValue *>(pVal->get())) {
+ 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) {
+ const auto bbox = dynamic_cast<const GeographicBoundingBox *>(
+ d->domainOfValidity_->geographicElements()[0].get());
+ 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;
+ properties.getStringValue(SCOPE_KEY, scope);
+
+ ExtentPtr domainOfValidity;
+ {
+ const auto pVal = properties.get(DOMAIN_OF_VALIDITY_KEY);
+ if (pVal) {
+ domainOfValidity = util::nn_dynamic_pointer_cast<Extent>(*pVal);
+ 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));
+ }
+
+ {
+ const auto pVal = properties.get(OBJECT_DOMAIN_KEY);
+ if (pVal) {
+ if (auto objectDomain =
+ util::nn_dynamic_pointer_cast<ObjectDomain>(*pVal)) {
+ d->domains_.emplace_back(NN_NO_CHECK(objectDomain));
+ } else if (const auto array =
+ dynamic_cast<const ArrayOfBaseObject *>(
+ pVal->get())) {
+ 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/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
new file mode 100644
index 00000000..442a9b78
--- /dev/null
+++ b/src/iso19111/coordinateoperation.cpp
@@ -0,0 +1,11749 @@
+/******************************************************************************
+ *
+ * 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[][7] = {
+ {"latitude_of_point_1", "Latitude_Of_1st_Point", nullptr},
+ {"longitude_of_point_1", "Longitude_Of_1st_Point", nullptr},
+ {"latitude_of_point_2", "Latitude_Of_2nd_Point", nullptr},
+ {"longitude_of_point_2", "Longitude_Of_2nd_Point", nullptr},
+
+ {"satellite_height", "height", nullptr},
+
+ {EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN,
+ EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, nullptr},
+
+ {EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN,
+ EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, nullptr},
+
+ {EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR,
+ EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
+ EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, nullptr},
+
+ {WKT1_LATITUDE_OF_ORIGIN, WKT1_LATITUDE_OF_CENTER,
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN,
+ EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, "Central_Parallel",
+ nullptr},
+
+ {WKT1_CENTRAL_MERIDIAN, WKT1_LONGITUDE_OF_CENTER,
+ EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN,
+ EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, nullptr},
+
+ {"pseudo_standard_parallel_1", WKT1_STANDARD_PARALLEL_1, nullptr},
+ };
+
+ for (const auto &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;
+}
+
+bool areEquivalentParameters(const std::string &a, const std::string &b) {
+
+ static const std::set<std::string> setEquivalentParameters =
+ buildSetEquivalentParameters();
+
+ auto a_can = metadata::Identifier::canonicalizeName(a);
+ auto b_can = metadata::Identifier::canonicalizeName(b);
+ return setEquivalentParameters.find(createEntryEqParam(a_can, b_can)) !=
+ setEquivalentParameters.end();
+}
+
+// ---------------------------------------------------------------------------
+
+PROJ_NO_INLINE const MethodMapping *getMapping(int epsg_code) noexcept {
+ for (const auto &mapping : projectionMethodMappings) {
+ if (mapping.epsg_code == epsg_code) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+const MethodMapping *getMapping(const OperationMethod *method) noexcept {
+ const std::string &name(method->nameStr());
+ const int epsg_code = method->getEPSGCode();
+ for (const auto &mapping : projectionMethodMappings) {
+ if ((epsg_code != 0 && mapping.epsg_code == epsg_code) ||
+ metadata::Identifier::isEquivalentName(mapping.wkt2_name,
+ name.c_str())) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+const MethodMapping *getMappingFromWKT1(const std::string &wkt1_name) noexcept {
+ // Unusual for a WKT1 projection name, but 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 : projectionMethodMappings) {
+ if (mapping.wkt1_name && metadata::Identifier::isEquivalentName(
+ mapping.wkt1_name, wkt1_name.c_str())) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+const MethodMapping *getMapping(const char *wkt2_name) noexcept {
+ for (const auto &mapping : projectionMethodMappings) {
+ if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
+ wkt2_name)) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<const MethodMapping *>
+getMappingsFromPROJName(const std::string &projName) {
+ std::vector<const MethodMapping *> res;
+ for (const auto &mapping : projectionMethodMappings) {
+ if (mapping.proj_name_main && projName == mapping.proj_name_main) {
+ res.push_back(&mapping);
+ }
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+static const ParamMapping *getMapping(const MethodMapping *mapping,
+ const OperationParameterNNPtr &param) {
+ if (mapping->params == nullptr) {
+ return nullptr;
+ }
+
+ // First try with id
+ const int epsg_code = param->getEPSGCode();
+ if (epsg_code) {
+ for (int i = 0; mapping->params[i] != nullptr; ++i) {
+ const auto *paramMapping = mapping->params[i];
+ if (paramMapping->epsg_code == epsg_code) {
+ return paramMapping;
+ }
+ }
+ }
+
+ // then equivalent name
+ const std::string &name = param->nameStr();
+ for (int i = 0; mapping->params[i] != nullptr; ++i) {
+ const auto *paramMapping = mapping->params[i];
+ if (metadata::Identifier::isEquivalentName(paramMapping->wkt2_name,
+ name.c_str())) {
+ return paramMapping;
+ }
+ }
+
+ // and finally different name, but equivalent parameter
+ for (int i = 0; mapping->params[i] != nullptr; ++i) {
+ const auto *paramMapping = mapping->params[i];
+ if (areEquivalentParameters(paramMapping->wkt2_name, name)) {
+ return paramMapping;
+ }
+ }
+
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping,
+ const std::string &wkt1_name) {
+ for (int i = 0; mapping->params[i] != nullptr; ++i) {
+ const auto *paramMapping = mapping->params[i];
+ if (paramMapping->wkt1_name &&
+ (metadata::Identifier::isEquivalentName(paramMapping->wkt1_name,
+ wkt1_name.c_str()) ||
+ areEquivalentParameters(paramMapping->wkt1_name, wkt1_name))) {
+ return paramMapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<const ESRIMethodMapping *>
+getMappingsFromESRI(const std::string &esri_name) {
+ std::vector<const ESRIMethodMapping *> res;
+ for (const auto &mapping : esriMappings) {
+ if (ci_equal(esri_name, mapping.esri_name)) {
+ res.push_back(&mapping);
+ }
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+static const ESRIMethodMapping *getESRIMapping(const std::string &wkt2_name,
+ int epsg_code) {
+ for (const auto &mapping : esriMappings) {
+ if ((epsg_code != 0 && mapping.epsg_code == epsg_code) ||
+ ci_equal(wkt2_name, mapping.wkt2_name)) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+static double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops);
+
+// Returns the accuracy of an operation, or -1 if unknown
+static double getAccuracy(const CoordinateOperationNNPtr &op) {
+
+ if (dynamic_cast<const Conversion *>(op.get())) {
+ // A conversion is perfectly accurate.
+ return 0.0;
+ }
+
+ double accuracy = -1.0;
+ const auto &accuracies = op->coordinateOperationAccuracies();
+ if (!accuracies.empty()) {
+ try {
+ accuracy = c_locale_stod(accuracies[0]->value());
+ } catch (const std::exception &) {
+ }
+ } else {
+ auto concatenated =
+ dynamic_cast<const ConcatenatedOperation *>(op.get());
+ if (concatenated) {
+ accuracy = getAccuracy(concatenated->operations());
+ }
+ }
+ return accuracy;
+}
+
+// ---------------------------------------------------------------------------
+
+// Returns the accuracy of a set of 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_{};
+ std::string projMethodOverride_{};
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+OperationMethod::OperationMethod() : d(internal::make_unique<Private>()) {}
+
+// ---------------------------------------------------------------------------
+
+OperationMethod::OperationMethod(const OperationMethod &other)
+ : IdentifiedObject(other), d(internal::make_unique<Private>(*other.d)) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+OperationMethod::~OperationMethod() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the formula(s) or procedure used by this coordinate operation
+ * method.
+ *
+ * This may be a reference to a publication (in which case use
+ * formulaCitation()).
+ *
+ * Note that the operation method may not be analytic, in which case this
+ * attribute references or contains the procedure, not an analytic formula.
+ *
+ * @return the formula, or empty.
+ */
+const util::optional<std::string> &OperationMethod::formula() PROJ_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;
+ properties.getStringValue("proj_method", method->d->projMethodOverride_);
+ return method;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a operation method from a vector of OperationParameter.
+ *
+ * @param properties See \ref general_properties. At minimum the name should be
+ * defined.
+ * @param parameters Vector of OperationParameterNNPtr.
+ * @return a new OperationMethod.
+ */
+OperationMethodNNPtr OperationMethod::create(
+ const util::PropertyMap &properties,
+ const std::vector<OperationParameterNNPtr> &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) {
+ auto l_name = nameStr();
+ if (ends_with(l_name, " (3D)")) {
+ l_name.resize(l_name.size() - strlen(" (3D)"));
+ }
+ for (const auto &tuple : methodNameCodes) {
+ if (metadata::Identifier::isEquivalentName(l_name.c_str(),
+ tuple.name)) {
+ return tuple.epsg_code;
+ }
+ }
+ }
+ return epsg_code;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void OperationMethod::_exportToWKT(io::WKTFormatter *formatter) const {
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ formatter->startNode(isWKT2 ? io::WKTConstants::METHOD
+ : io::WKTConstants::PROJECTION,
+ !identifiers().empty());
+ std::string l_name(nameStr());
+ if (!isWKT2) {
+ const MethodMapping *mapping = getMapping(this);
+ if (mapping == nullptr) {
+ l_name = replaceAll(l_name, " ", "_");
+ } else {
+ if (l_name ==
+ PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) {
+ l_name = "Geostationary_Satellite";
+ } else {
+ if (mapping->wkt1_name == nullptr) {
+ throw io::FormattingException(
+ std::string("Unsupported conversion method: ") +
+ mapping->wkt2_name);
+ }
+ l_name = mapping->wkt1_name;
+ }
+ }
+ }
+ formatter->addQuotedString(l_name);
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+ formatter->endNode();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+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, d->parameter) : nullptr;
+ if (paramMapping && paramMapping->wkt1_name == nullptr) {
+ return;
+ }
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ if (isWKT2 && parameterValue()->type() == ParameterValue::Type::FILENAME) {
+ formatter->startNode(io::WKTConstants::PARAMETERFILE,
+ !parameter()->identifiers().empty());
+ } else {
+ formatter->startNode(io::WKTConstants::PARAMETER,
+ !parameter()->identifiers().empty());
+ }
+ if (paramMapping) {
+ formatter->addQuotedString(paramMapping->wkt1_name);
+ } else {
+ formatter->addQuotedString(parameter()->nameStr());
+ }
+ parameterValue()->_exportToWKT(formatter);
+ if (formatter->outputId()) {
+ parameter()->formatID(formatter);
+ }
+ formatter->endNode();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+/** 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;
+ }
+ if (!d->parameter->_isEquivalentTo(otherOPV->d->parameter.get(),
+ criterion)) {
+ return false;
+ }
+ if (criterion == util::IComparable::Criterion::STRICT) {
+ return d->parameterValue->_isEquivalentTo(
+ otherOPV->d->parameterValue.get(), criterion);
+ }
+ if (d->parameterValue->_isEquivalentTo(otherOPV->d->parameterValue.get(),
+ criterion)) {
+ return true;
+ }
+ if (d->parameter->getEPSGCode() ==
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE ||
+ d->parameter->getEPSGCode() ==
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) {
+ if (parameterValue()->type() == ParameterValue::Type::MEASURE &&
+ otherOPV->parameterValue()->type() ==
+ ParameterValue::Type::MEASURE) {
+ const double a = std::fmod(parameterValue()->value().convertToUnit(
+ common::UnitOfMeasure::DEGREE) +
+ 360.0,
+ 360.0);
+ const double b =
+ std::fmod(otherOPV->parameterValue()->value().convertToUnit(
+ common::UnitOfMeasure::DEGREE) +
+ 360.0,
+ 360.0);
+ return std::fabs(a - b) <= 1e-10 * std::fabs(a);
+ }
+ }
+ return false;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct GeneralOperationParameter::Private {};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+GeneralOperationParameter::GeneralOperationParameter() : d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+GeneralOperationParameter::GeneralOperationParameter(
+ const GeneralOperationParameter &other)
+ : IdentifiedObject(other), d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+GeneralOperationParameter::~GeneralOperationParameter() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct OperationParameter::Private {};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+OperationParameter::OperationParameter() : d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+OperationParameter::OperationParameter(const OperationParameter &other)
+ : GeneralOperationParameter(other), d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+OperationParameter::~OperationParameter() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a OperationParameter.
+ *
+ * @param properties See \ref general_properties. At minimum the name should be
+ * defined.
+ * @return a new OperationParameter.
+ */
+OperationParameterNNPtr
+OperationParameter::create(const util::PropertyMap &properties) {
+ OperationParameterNNPtr op(
+ OperationParameter::nn_make_shared<OperationParameter>());
+ op->assignSelf(op);
+ op->setProperties(properties);
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool OperationParameter::_isEquivalentTo(
+ const util::IComparable *other,
+ util::IComparable::Criterion criterion) const {
+ auto otherOP = dynamic_cast<const OperationParameter *>(other);
+ if (otherOP == nullptr) {
+ return false;
+ }
+ if (criterion == util::IComparable::Criterion::STRICT) {
+ return IdentifiedObject::_isEquivalentTo(other, criterion);
+ }
+ if (IdentifiedObject::_isEquivalentTo(other, criterion)) {
+ return true;
+ }
+ auto l_epsgCode = getEPSGCode();
+ return l_epsgCode != 0 && l_epsgCode == otherOP->getEPSGCode();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+void OperationParameter::_exportToWKT(io::WKTFormatter *) const {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the name of a parameter designed by its EPSG code
+ * @return name, or nullptr if not found
+ */
+const char *OperationParameter::getNameForEPSGCode(int epsg_code) noexcept {
+ for (const auto &tuple : paramNameCodes) {
+ if (tuple.epsg_code == epsg_code) {
+ return tuple.name;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the EPSG code, either directly, or through the name
+ * @return code, or 0 if not found
+ */
+int OperationParameter::getEPSGCode() PROJ_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;
+ }
+ }
+ if (metadata::Identifier::isEquivalentName(l_name.c_str(),
+ "Latitude of origin")) {
+ return EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN;
+ }
+ if (metadata::Identifier::isEquivalentName(l_name.c_str(),
+ "Scale factor")) {
+ return EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN;
+ }
+ }
+ return epsg_code;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct SingleOperation::Private {
+ std::vector<GeneralParameterValueNNPtr> parameterValues_{};
+ OperationMethodNNPtr method_;
+
+ explicit Private(const OperationMethodNNPtr &methodIn)
+ : method_(methodIn) {}
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+SingleOperation::SingleOperation(const OperationMethodNNPtr &methodIn)
+ : d(internal::make_unique<Private>(methodIn)) {}
+
+// ---------------------------------------------------------------------------
+
+SingleOperation::SingleOperation(const SingleOperation &other)
+ :
+#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT)
+ CoordinateOperation(other),
+#endif
+ d(internal::make_unique<Private>(*other.d)) {
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+SingleOperation::~SingleOperation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameter values.
+ *
+ * @return the parameter values.
+ */
+const std::vector<GeneralParameterValueNNPtr> &
+SingleOperation::parameterValues() PROJ_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 {
+ if (epsg_code) {
+ 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();
+ }
+ }
+ }
+ }
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = opParamvalue->parameter();
+ if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), parameter->nameStr().c_str())) {
+ return opParamvalue->parameterValue();
+ }
+ }
+ }
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = opParamvalue->parameter();
+ if (areEquivalentParameters(paramName, parameter->nameStr())) {
+ return opParamvalue->parameterValue();
+ }
+ }
+ }
+ return nullParameterValue;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameter value corresponding to a EPSG code
+ *
+ * @param epsg_code the parameter EPSG code
+ * @return the value, or nullptr if not found.
+ */
+const ParameterValuePtr &SingleOperation::parameterValue(int epsg_code) const
+ noexcept {
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &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 {
+ return _isEquivalentTo(other, criterion, false);
+}
+
+bool SingleOperation::_isEquivalentTo(const util::IComparable *other,
+ util::IComparable::Criterion criterion,
+ bool inOtherDirection) 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();
+ const int otherMethodEPSGCode = otherSO->d->method_->getEPSGCode();
+
+ bool equivalentMethods =
+ (criterion == util::IComparable::Criterion::EQUIVALENT &&
+ methodEPSGCode != 0 && methodEPSGCode == otherMethodEPSGCode) ||
+ d->method_->_isEquivalentTo(otherSO->d->method_.get(), criterion);
+ if (!equivalentMethods &&
+ criterion == util::IComparable::Criterion::EQUIVALENT) {
+ if ((methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) ||
+ (otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA &&
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) ||
+ (methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) ||
+ (otherMethodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL &&
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) {
+ auto geodCRS =
+ dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get());
+ auto otherGeodCRS = dynamic_cast<const crs::GeodeticCRS *>(
+ otherSO->sourceCRS().get());
+ if (geodCRS && otherGeodCRS && geodCRS->ellipsoid()->isSphere() &&
+ otherGeodCRS->ellipsoid()->isSphere()) {
+ equivalentMethods = true;
+ }
+ }
+ }
+
+ if (!equivalentMethods) {
+ if (criterion == util::IComparable::Criterion::EQUIVALENT) {
+
+ const auto isTOWGS84Transf = [](int code) {
+ return code ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
+ code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
+ code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
+ code ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
+ code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ code ==
+ EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ code ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D ||
+ code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D ||
+ code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D;
+ };
+
+ // Translation vs (PV or CF)
+ // or different PV vs CF convention
+ if (isTOWGS84Transf(methodEPSGCode) &&
+ isTOWGS84Transf(otherMethodEPSGCode)) {
+ auto transf = static_cast<const Transformation *>(this);
+ auto otherTransf = static_cast<const Transformation *>(otherSO);
+ auto params = transf->getTOWGS84Parameters();
+ auto otherParams = otherTransf->getTOWGS84Parameters();
+ assert(params.size() == 7);
+ assert(otherParams.size() == 7);
+ for (size_t i = 0; i < 7; i++) {
+ if (std::fabs(params[i] - otherParams[i]) >
+ 1e-10 * std::fabs(params[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // _1SP methods can sometimes be equivalent to _2SP ones
+ // Check it by using convertToOtherMethod()
+ if (methodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
+ // Convert from 2SP to 1SP as the other direction has more
+ // degree of liberties.
+ return otherSO->_isEquivalentTo(this, criterion);
+ } 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();
+ const auto otherValuesSize = otherValues.size();
+ if (criterion == util::IComparable::Criterion::STRICT) {
+ if (valuesSize != otherValuesSize) {
+ return false;
+ }
+ for (size_t i = 0; i < valuesSize; i++) {
+ if (!values[i]->_isEquivalentTo(otherValues[i].get(), criterion)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ std::vector<bool> candidateIndices(otherValuesSize, true);
+ bool equivalent = true;
+ bool foundMissingArgs = valuesSize != otherValuesSize;
+
+ for (size_t i = 0; equivalent && i < valuesSize; i++) {
+ auto opParamvalue =
+ dynamic_cast<const OperationParameterValue *>(values[i].get());
+ if (!opParamvalue)
+ return false;
+
+ equivalent = false;
+ bool sameNameDifferentValue = false;
+ for (size_t j = 0; j < otherValuesSize; j++) {
+ if (candidateIndices[j] &&
+ values[i]->_isEquivalentTo(otherValues[j].get(), criterion)) {
+ candidateIndices[j] = false;
+ equivalent = true;
+ break;
+ } else if (candidateIndices[j]) {
+ auto otherOpParamvalue =
+ dynamic_cast<const OperationParameterValue *>(
+ otherValues[j].get());
+ if (!otherOpParamvalue)
+ return false;
+ sameNameDifferentValue =
+ opParamvalue->parameter()->_isEquivalentTo(
+ otherOpParamvalue->parameter().get(), criterion);
+ if (sameNameDifferentValue) {
+ candidateIndices[j] = false;
+ break;
+ }
+ }
+ }
+
+ if (!equivalent &&
+ methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
+ // For LCC_2SP, the standard parallels can be switched and
+ // this will result in the same result.
+ const int paramEPSGCode = opParamvalue->parameter()->getEPSGCode();
+ if (paramEPSGCode ==
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL ||
+ paramEPSGCode ==
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) {
+ auto value_1st = parameterValue(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL);
+ auto value_2nd = parameterValue(
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL);
+ if (value_1st && value_2nd) {
+ equivalent =
+ value_1st->_isEquivalentTo(
+ otherSO
+ ->parameterValue(
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL)
+ .get(),
+ criterion) &&
+ value_2nd->_isEquivalentTo(
+ otherSO
+ ->parameterValue(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL)
+ .get(),
+ criterion);
+ }
+ }
+ }
+
+ if (equivalent) {
+ continue;
+ }
+
+ if (sameNameDifferentValue) {
+ break;
+ }
+
+ // If there are parameters in this method not found in the other one,
+ // check that they are set to a default neutral value, that is 1
+ // for scale, and 0 otherwise.
+ foundMissingArgs = true;
+ const auto &value = opParamvalue->parameterValue();
+ if (value->type() != ParameterValue::Type::MEASURE) {
+ break;
+ }
+ if (value->value().unit().type() ==
+ common::UnitOfMeasure::Type::SCALE) {
+ equivalent = value->value().getSIValue() == 1.0;
+ } else {
+ equivalent = value->value().getSIValue() == 0.0;
+ }
+ }
+
+ // In the case the arguments don't perfectly match, try the reverse
+ // check.
+ if (equivalent && foundMissingArgs && !inOtherDirection) {
+ return otherSO->_isEquivalentTo(this, criterion, true);
+ }
+
+ // Equivalent formulations of 2SP can have different parameters
+ // Then convert to 1SP and compare.
+ if (!equivalent &&
+ methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
+ auto conv = dynamic_cast<const Conversion *>(this);
+ auto otherConv = dynamic_cast<const Conversion *>(other);
+ if (conv && otherConv) {
+ auto thisAs1SP = conv->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ auto otherAs1SP = otherConv->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ if (thisAs1SP && otherAs1SP) {
+ equivalent =
+ thisAs1SP->_isEquivalentTo(otherAs1SP.get(), criterion);
+ }
+ }
+ }
+ 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;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Validate the parameters used by a coodinate operation.
+ *
+ * Return whether the method is known or not, or a list of missing or extra
+ * parameters for the operations recognized by this implementation.
+ */
+std::list<std::string> SingleOperation::validateParameters() const {
+ std::list<std::string> res;
+
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const MethodMapping *methodMapping = nullptr;
+ const auto methodEPSGCode = l_method->getEPSGCode();
+ for (const auto &mapping : projectionMethodMappings) {
+ if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
+ methodName.c_str()) ||
+ (methodEPSGCode != 0 && methodEPSGCode == mapping.epsg_code)) {
+ methodMapping = &mapping;
+ }
+ }
+ if (methodMapping == nullptr) {
+ for (const auto &mapping : otherMethodMappings) {
+ if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
+ methodName.c_str()) ||
+ (methodEPSGCode != 0 && methodEPSGCode == mapping.epsg_code)) {
+ methodMapping = &mapping;
+ }
+ }
+ }
+ if (!methodMapping) {
+ res.emplace_back("Unknown method " + methodName);
+ return res;
+ }
+ if (methodMapping->wkt2_name != methodName) {
+ if (metadata::Identifier::isEquivalentName(methodMapping->wkt2_name,
+ methodName.c_str())) {
+ std::string msg("Method name ");
+ msg += methodName;
+ msg += " is equivalent to official ";
+ msg += methodMapping->wkt2_name;
+ msg += " but not strictly equal";
+ res.emplace_back(msg);
+ } else {
+ std::string msg("Method name ");
+ msg += methodName;
+ msg += ", matched to ";
+ msg += methodMapping->wkt2_name;
+ msg += ", through its EPSG code has not an equivalent name";
+ res.emplace_back(msg);
+ }
+ }
+ if (methodEPSGCode != 0 && methodEPSGCode != methodMapping->epsg_code) {
+ std::string msg("Method of EPSG code ");
+ msg += toString(methodEPSGCode);
+ msg += " does not match official code (";
+ msg += toString(methodMapping->epsg_code);
+ msg += ')';
+ res.emplace_back(msg);
+ }
+
+ // Check if expected parameters are found
+ for (int i = 0;
+ methodMapping->params && methodMapping->params[i] != nullptr; ++i) {
+ const auto *paramMapping = methodMapping->params[i];
+
+ const OperationParameterValue *opv = nullptr;
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = opParamvalue->parameter();
+ if ((paramMapping->epsg_code != 0 &&
+ parameter->getEPSGCode() == paramMapping->epsg_code) ||
+ ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) {
+ opv = opParamvalue;
+ break;
+ }
+ }
+ }
+
+ if (!opv) {
+ std::string msg("Cannot find expected parameter ");
+ msg += paramMapping->wkt2_name;
+ res.emplace_back(msg);
+ continue;
+ }
+ const auto &parameter = opv->parameter();
+ if (paramMapping->wkt2_name != parameter->nameStr()) {
+ if (ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) {
+ std::string msg("Parameter name ");
+ msg += parameter->nameStr();
+ msg += " is equivalent to official ";
+ msg += paramMapping->wkt2_name;
+ msg += " but not strictly equal";
+ res.emplace_back(msg);
+ } else {
+ std::string msg("Parameter name ");
+ msg += parameter->nameStr();
+ msg += ", matched to ";
+ msg += paramMapping->wkt2_name;
+ msg += ", through its EPSG code has not an equivalent name";
+ res.emplace_back(msg);
+ }
+ }
+ const auto paramEPSGCode = parameter->getEPSGCode();
+ if (paramEPSGCode != 0 && paramEPSGCode != paramMapping->epsg_code) {
+ std::string msg("Paramater of EPSG code ");
+ msg += toString(paramEPSGCode);
+ msg += " does not match official code (";
+ msg += toString(paramMapping->epsg_code);
+ msg += ')';
+ res.emplace_back(msg);
+ }
+ }
+
+ // Check if there are extra parameters
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = opParamvalue->parameter();
+ if (!getMapping(methodMapping, parameter)) {
+ std::string msg("Parameter ");
+ msg += parameter->nameStr();
+ msg += " found but not expected for this method";
+ res.emplace_back(msg);
+ }
+ }
+ }
+
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct ParameterValue::Private {
+ ParameterValue::Type type_{ParameterValue::Type::STRING};
+ std::unique_ptr<common::Measure> measure_{};
+ std::unique_ptr<std::string> stringValue_{};
+ int integerValue_{};
+ bool booleanValue_{};
+
+ explicit Private(const common::Measure &valueIn)
+ : type_(ParameterValue::Type::MEASURE),
+ measure_(internal::make_unique<common::Measure>(valueIn)) {}
+
+ Private(const std::string &stringValueIn, ParameterValue::Type typeIn)
+ : type_(typeIn),
+ stringValue_(internal::make_unique<std::string>(stringValueIn)) {}
+
+ explicit Private(int integerValueIn)
+ : type_(ParameterValue::Type::INTEGER), integerValue_(integerValueIn) {}
+
+ explicit Private(bool booleanValueIn)
+ : type_(ParameterValue::Type::BOOLEAN), booleanValue_(booleanValueIn) {}
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+ParameterValue::~ParameterValue() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+ParameterValue::ParameterValue(const common::Measure &measureIn)
+ : d(internal::make_unique<Private>(measureIn)) {}
+
+// ---------------------------------------------------------------------------
+
+ParameterValue::ParameterValue(const std::string &stringValueIn,
+ ParameterValue::Type typeIn)
+ : d(internal::make_unique<Private>(stringValueIn, typeIn)) {}
+
+// ---------------------------------------------------------------------------
+
+ParameterValue::ParameterValue(int integerValueIn)
+ : d(internal::make_unique<Private>(integerValueIn)) {}
+
+// ---------------------------------------------------------------------------
+
+ParameterValue::ParameterValue(bool booleanValueIn)
+ : d(internal::make_unique<Private>(booleanValueIn)) {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief 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
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+ConversionNNPtr
+Conversion::alterParametersLinearUnit(const common::UnitOfMeasure &unit,
+ bool convertToNewUnit) const {
+
+ std::vector<GeneralParameterValueNNPtr> newValues;
+ bool changesDone = false;
+ for (const auto &genOpParamvalue : parameterValues()) {
+ bool updated = false;
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &paramValue = opParamvalue->parameterValue();
+ if (paramValue->type() == ParameterValue::Type::MEASURE) {
+ const auto &measure = paramValue->value();
+ if (measure.unit().type() ==
+ common::UnitOfMeasure::Type::LINEAR) {
+ if (!measure.unit()._isEquivalentTo(
+ unit, util::IComparable::Criterion::EQUIVALENT)) {
+ const double newValue =
+ convertToNewUnit ? measure.convertToUnit(unit)
+ : measure.value();
+ newValues.emplace_back(OperationParameterValue::create(
+ opParamvalue->parameter(),
+ ParameterValue::create(
+ common::Measure(newValue, unit))));
+ updated = true;
+ }
+ }
+ }
+ }
+ if (updated) {
+ changesDone = true;
+ } else {
+ newValues.emplace_back(genOpParamvalue);
+ }
+ }
+ if (changesDone) {
+ auto conv = create(util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, "unknown"),
+ method(), newValues);
+ conv->setCRSs(this, false);
+ return conv;
+ } else {
+ return NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<Conversion>(shared_from_this()));
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief 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.get(common::IdentifiedObject::NAME_KEY)) {
+ std::string conversionName("UTM zone ");
+ conversionName += toString(zone);
+ conversionName += (north ? 'N' : 'S');
+
+ return createMapNameEPSGCode(conversionName,
+ (north ? 16000 : 17000) + zone);
+ } else {
+ return properties;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static util::PropertyMap
+addDefaultNameIfNeeded(const util::PropertyMap &properties,
+ const std::string &defaultName) {
+ if (!properties.get(common::IdentifiedObject::NAME_KEY)) {
+ return util::PropertyMap(properties)
+ .set(common::IdentifiedObject::NAME_KEY, defaultName);
+ } else {
+ return properties;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static ConversionNNPtr
+createConversion(const util::PropertyMap &properties,
+ const MethodMapping *mapping,
+ const std::vector<ParameterValueNNPtr> &values) {
+
+ std::vector<OperationParameterNNPtr> parameters;
+ for (int i = 0; mapping->params[i] != nullptr; i++) {
+ const auto *param = mapping->params[i];
+ auto paramProperties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, param->wkt2_name);
+ if (param->epsg_code != 0) {
+ paramProperties
+ .set(metadata::Identifier::CODESPACE_KEY,
+ metadata::Identifier::EPSG)
+ .set(metadata::Identifier::CODE_KEY, param->epsg_code);
+ }
+ auto parameter = OperationParameter::create(paramProperties);
+ parameters.push_back(parameter);
+ }
+
+ auto methodProperties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, mapping->wkt2_name);
+ if (mapping->epsg_code != 0) {
+ methodProperties
+ .set(metadata::Identifier::CODESPACE_KEY,
+ metadata::Identifier::EPSG)
+ .set(metadata::Identifier::CODE_KEY, mapping->epsg_code);
+ }
+ return Conversion::create(
+ addDefaultNameIfNeeded(properties, mapping->wkt2_name),
+ methodProperties, parameters, values);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+ConversionNNPtr
+Conversion::create(const util::PropertyMap &properties, int method_epsg_code,
+ const std::vector<ParameterValueNNPtr> &values) {
+ const MethodMapping *mapping = getMapping(method_epsg_code);
+ assert(mapping);
+ return createConversion(properties, mapping, values);
+}
+
+// ---------------------------------------------------------------------------
+
+ConversionNNPtr
+Conversion::create(const util::PropertyMap &properties,
+ const char *method_wkt2_name,
+ const std::vector<ParameterValueNNPtr> &values) {
+ const MethodMapping *mapping = getMapping(method_wkt2_name);
+ assert(mapping);
+ return createConversion(properties, mapping, values);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+struct VectorOfParameters : public std::vector<OperationParameterNNPtr> {
+ VectorOfParameters() : std::vector<OperationParameterNNPtr>() {}
+ explicit VectorOfParameters(
+ std::initializer_list<OperationParameterNNPtr> list)
+ : std::vector<OperationParameterNNPtr>(list) {}
+ VectorOfParameters(const VectorOfParameters &) = delete;
+
+ ~VectorOfParameters();
+};
+
+// This way, we disable inlining of destruction, and save a lot of space
+VectorOfParameters::~VectorOfParameters() = default;
+
+struct VectorOfValues : public std::vector<ParameterValueNNPtr> {
+ VectorOfValues() : std::vector<ParameterValueNNPtr>() {}
+ explicit VectorOfValues(std::initializer_list<ParameterValueNNPtr> list)
+ : std::vector<ParameterValueNNPtr>(list) {}
+
+ explicit VectorOfValues(std::initializer_list<common::Measure> list);
+ VectorOfValues(const VectorOfValues &) = delete;
+ VectorOfValues(VectorOfValues &&) = default;
+
+ ~VectorOfValues();
+};
+
+static std::vector<ParameterValueNNPtr> buildParameterValueFromMeasure(
+ const std::initializer_list<common::Measure> &list) {
+ std::vector<ParameterValueNNPtr> res;
+ for (const auto &v : list) {
+ res.emplace_back(ParameterValue::create(v));
+ }
+ return res;
+}
+
+VectorOfValues::VectorOfValues(std::initializer_list<common::Measure> list)
+ : std::vector<ParameterValueNNPtr>(buildParameterValueFromMeasure(list)) {}
+
+// This way, we disable inlining of destruction, and save a lot of space
+VectorOfValues::~VectorOfValues() = default;
+
+PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3) {
+ return VectorOfValues{ParameterValue::create(m1),
+ ParameterValue::create(m2),
+ ParameterValue::create(m3)};
+}
+
+PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3,
+ const common::Measure &m4) {
+ return VectorOfValues{
+ ParameterValue::create(m1), ParameterValue::create(m2),
+ ParameterValue::create(m3), ParameterValue::create(m4)};
+}
+
+PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3,
+ const common::Measure &m4,
+ const common::Measure &m5) {
+ return VectorOfValues{
+ ParameterValue::create(m1), ParameterValue::create(m2),
+ ParameterValue::create(m3), ParameterValue::create(m4),
+ ParameterValue::create(m5),
+ };
+}
+
+PROJ_NO_INLINE static VectorOfValues
+createParams(const common::Measure &m1, const common::Measure &m2,
+ const common::Measure &m3, const common::Measure &m4,
+ const common::Measure &m5, const common::Measure &m6) {
+ return VectorOfValues{
+ ParameterValue::create(m1), ParameterValue::create(m2),
+ ParameterValue::create(m3), ParameterValue::create(m4),
+ ParameterValue::create(m5), ParameterValue::create(m6),
+ };
+}
+
+PROJ_NO_INLINE static VectorOfValues
+createParams(const common::Measure &m1, const common::Measure &m2,
+ const common::Measure &m3, const common::Measure &m4,
+ const common::Measure &m5, const common::Measure &m6,
+ const common::Measure &m7) {
+ return VectorOfValues{
+ ParameterValue::create(m1), ParameterValue::create(m2),
+ ParameterValue::create(m3), ParameterValue::create(m4),
+ ParameterValue::create(m5), ParameterValue::create(m6),
+ ParameterValue::create(m7),
+ };
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief 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 to 17060 for the southern hemisphere,
+ * based on the Transverse Mercator projection method.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param zone UTM zone number between 1 and 60.
+ * @param north true for UTM northern hemisphere, false for UTM southern
+ * hemisphere.
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createUTM(const util::PropertyMap &properties,
+ int zone, bool north) {
+ return create(
+ getUTMConversionProperty(properties, zone, north),
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR,
+ createParams(common::Angle(UTM_LATITUDE_OF_NATURAL_ORIGIN),
+ common::Angle(zone * 6.0 - 183.0),
+ common::Scale(UTM_SCALE_FACTOR),
+ common::Length(UTM_FALSE_EASTING),
+ common::Length(north ? UTM_NORTH_FALSE_NORTHING
+ : UTM_SOUTH_FALSE_NORTHING)));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief 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, 0.0, 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, 0.0, 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 [Laborde Oblique Mercator]
+ *(https://proj4.org/operations/projections/labrd.html) projection method.
+ *
+ * This method is defined as [EPSG:9813]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9813)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeProjectionCentre See \ref latitude_projection_centre
+ * @param longitudeProjectionCentre See \ref longitude_projection_centre
+ * @param azimuthInitialLine See \ref azimuth_initial_line
+ * @param scale See \ref scale_factor_initial_line
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLabordeObliqueMercator(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeProjectionCentre,
+ const common::Angle &longitudeProjectionCentre,
+ const common::Angle &azimuthInitialLine, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR,
+ createParams(latitudeProjectionCentre,
+ longitudeProjectionCentre, azimuthInitialLine,
+ scale, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief 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 std::move(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 std::move(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 std::move(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 std::move(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 std::move(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 std::move(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 (std::abs(
+ conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) -
+ conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) <
+ 1e-15) {
+ esriParams =
+ paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin;
+ esriMethodName =
+ "Hotine_Oblique_Mercator_Azimuth_Natural_Origin";
+ } else {
+ esriParams =
+ paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin;
+ esriMethodName = "Rectified_Skew_Orthomorphic_Natural_Origin";
+ }
+ } else if (esriMapping->epsg_code ==
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) {
+ if (std::abs(
+ conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) -
+ conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) <
+ 1e-15) {
+ esriParams = paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center;
+ esriMethodName = "Hotine_Oblique_Mercator_Azimuth_Center";
+ } else {
+ esriParams = paramsESRI_Rectified_Skew_Orthomorphic_Center;
+ esriMethodName = "Rectified_Skew_Orthomorphic_Center";
+ }
+ } else if (esriMapping->epsg_code ==
+ EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
+ if (conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL) > 0) {
+ esriMethodName = "Stereographic_North_Pole";
+ } else {
+ esriMethodName = "Stereographic_South_Pole";
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+const char *Conversion::getESRIMethodName() const {
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const auto methodEPSGCode = l_method->getEPSGCode();
+ const ESRIParamMapping *esriParams = nullptr;
+ const char *esriMethodName = nullptr;
+ getESRIMethodNameAndParams(this, methodName, methodEPSGCode, esriMethodName,
+ esriParams);
+ return esriMethodName;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+const char *Conversion::getWKT1GDALMethodName() const {
+ const auto &l_method = method();
+ const auto methodEPSGCode = l_method->getEPSGCode();
+ if (methodEPSGCode ==
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
+ return "Mercator_1SP";
+ }
+ const MethodMapping *mapping = getMapping(l_method.get());
+ return mapping ? mapping->wkt1_name : nullptr;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void Conversion::_exportToWKT(io::WKTFormatter *formatter) const {
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const auto methodEPSGCode = l_method->getEPSGCode();
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+
+ if (!isWKT2 && formatter->useESRIDialect()) {
+ if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
+ auto eqConv =
+ convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
+ if (eqConv) {
+ eqConv->_exportToWKT(formatter);
+ return;
+ }
+ }
+ }
+
+ if (isWKT2) {
+ formatter->startNode(formatter->useDerivingConversion()
+ ? io::WKTConstants::DERIVINGCONVERSION
+ : io::WKTConstants::CONVERSION,
+ !identifiers().empty());
+ formatter->addQuotedString(nameStr());
+ } else {
+ formatter->enter();
+ formatter->pushOutputUnit(false);
+ formatter->pushOutputId(false);
+ }
+
+ bool bAlreadyWritten = false;
+ if (!isWKT2 && formatter->useESRIDialect()) {
+ const ESRIParamMapping *esriParams = nullptr;
+ const char *esriMethodName = nullptr;
+ getESRIMethodNameAndParams(this, methodName, methodEPSGCode,
+ esriMethodName, esriParams);
+ if (esriMethodName && esriParams) {
+ formatter->startNode(io::WKTConstants::PROJECTION, false);
+ formatter->addQuotedString(esriMethodName);
+ formatter->endNode();
+
+ for (int i = 0; esriParams[i].esri_name != nullptr; i++) {
+ const auto &esriParam = esriParams[i];
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString(esriParam.esri_name);
+ if (esriParam.wkt2_name) {
+ const auto &pv = parameterValue(esriParam.wkt2_name,
+ esriParam.epsg_code);
+ if (pv && pv->type() == ParameterValue::Type::MEASURE) {
+ const auto &v = pv->value();
+ // as we don't output the natural unit, output
+ // to the registered linear / angular unit.
+ const auto &unitType = v.unit().type();
+ if (unitType == common::UnitOfMeasure::Type::LINEAR) {
+ formatter->add(v.convertToUnit(
+ *(formatter->axisLinearUnit())));
+ } else if (unitType ==
+ common::UnitOfMeasure::Type::ANGULAR) {
+ const auto &angUnit =
+ *(formatter->axisAngularUnit());
+ double val = v.convertToUnit(angUnit);
+ if (angUnit == common::UnitOfMeasure::DEGREE) {
+ if (val > 180.0) {
+ val -= 360.0;
+ } else if (val < -180.0) {
+ val += 360.0;
+ }
+ }
+ formatter->add(val);
+ } else {
+ formatter->add(v.getSIValue());
+ }
+ } else if (ci_find(esriParam.esri_name, "scale") !=
+ std::string::npos) {
+ formatter->add(1.0);
+ } else {
+ formatter->add(0.0);
+ }
+ } else {
+ formatter->add(esriParam.fixed_value);
+ }
+ formatter->endNode();
+ }
+ bAlreadyWritten = true;
+ }
+ } else if (!isWKT2) {
+ if (methodEPSGCode ==
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
+ const double latitudeOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ if (latitudeOrigin != 0) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
+ }
+
+ bAlreadyWritten = true;
+ formatter->startNode(io::WKTConstants::PROJECTION, false);
+ formatter->addQuotedString("Mercator_1SP");
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString("central_meridian");
+ const double centralMeridian = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ formatter->add(centralMeridian);
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString("scale_factor");
+ formatter->add(1.0);
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString("false_easting");
+ const double falseEasting =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING);
+ formatter->add(falseEasting);
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString("false_northing");
+ const double falseNorthing =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
+ formatter->add(falseNorthing);
+ formatter->endNode();
+ } else if (starts_with(methodName, "PROJ ")) {
+ bAlreadyWritten = true;
+ formatter->startNode(io::WKTConstants::PROJECTION, false);
+ formatter->addQuotedString("custom_proj4");
+ formatter->endNode();
+ }
+ }
+
+ if (!bAlreadyWritten) {
+ l_method->_exportToWKT(formatter);
+
+ const MethodMapping *mapping =
+ !isWKT2 ? getMapping(l_method.get()) : nullptr;
+ for (const auto &genOpParamvalue : parameterValues()) {
+
+ // EPSG has normally no Latitude of natural origin for Equidistant
+ // Cylindrical but PROJ can handle it, so output the parameter if
+ // not zero
+ if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) {
+ auto opParamvalue =
+ dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue &&
+ opParamvalue->parameter()->getEPSGCode() ==
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) {
+ const auto &paramValue = opParamvalue->parameterValue();
+ if (paramValue->type() == ParameterValue::Type::MEASURE) {
+ const auto &measure = paramValue->value();
+ if (measure.getSIValue() == 0) {
+ continue;
+ }
+ }
+ }
+ }
+ genOpParamvalue->_exportToWKT(formatter, mapping);
+ }
+ }
+
+ if (isWKT2) {
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+ formatter->endNode();
+ } else {
+ formatter->popOutputUnit();
+ formatter->popOutputId();
+ formatter->leave();
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+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
+
+// ---------------------------------------------------------------------------
+
+bool Conversion::addWKTExtensionNode(io::WKTFormatter *formatter) const {
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ if (!isWKT2) {
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const int methodEPSGCode = l_method->getEPSGCode();
+ int zone = 0;
+ bool north = true;
+ if (l_method->getPrivate()->projMethodOverride_ == "etmerc" &&
+ !isUTM(zone, north)) {
+ auto projFormatter = io::PROJStringFormatter::create(
+ io::PROJStringFormatter::Convention::PROJ_4);
+ projFormatter->setUseETMercForTMerc(true);
+ formatter->startNode(io::WKTConstants::EXTENSION, false);
+ formatter->addQuotedString("PROJ4");
+ _exportToPROJString(projFormatter.get());
+ projFormatter->addParam("no_defs");
+ formatter->addQuotedString(projFormatter->toString());
+ formatter->endNode();
+ return true;
+ } else if (methodEPSGCode ==
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR ||
+ nameStr() == "Popular Visualisation Mercator") {
+
+ auto projFormatter = io::PROJStringFormatter::create(
+ 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();
+ return true;
+ }
+ } 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();
+ return true;
+ }
+ } else if (methodName ==
+ PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) {
+ auto projFormatter = io::PROJStringFormatter::create(
+ io::PROJStringFormatter::Convention::PROJ_4);
+ formatter->startNode(io::WKTConstants::EXTENSION, false);
+ formatter->addQuotedString("PROJ4");
+ _exportToPROJString(projFormatter.get());
+ projFormatter->addParam("no_defs");
+ formatter->addQuotedString(projFormatter->toString());
+ formatter->endNode();
+ return true;
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void Conversion::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const // throw(FormattingException)
+{
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const int methodEPSGCode = l_method->getEPSGCode();
+ const bool isZUnitConversion =
+ methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT;
+ const bool isAffineParametric =
+ methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION;
+ const bool isGeographicGeocentric =
+ methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC;
+ const bool 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;
+ bool etMercSettingSet = false;
+ useETMerc = formatter->getUseETMercForTMerc(etMercSettingSet) ||
+ l_method->getPrivate()->projMethodOverride_ == "etmerc";
+ if (isUTM(zone, north) && !(etMercSettingSet && !useETMerc)) {
+ bConversionDone = true;
+ formatter->addStep("utm");
+ formatter->addParam("zone", zone);
+ if (!north) {
+ formatter->addParam("south");
+ }
+ }
+ } else if (methodEPSGCode ==
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) {
+ const double azimuth =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE,
+ common::UnitOfMeasure::DEGREE);
+ const double angleRectifiedToSkewGrid = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
+ common::UnitOfMeasure::DEGREE);
+ // Map to Swiss Oblique Mercator / somerc
+ if (std::fabs(azimuth - 90) < 1e-4 &&
+ std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) {
+ bConversionDone = true;
+ formatter->addStep("somerc");
+ formatter->addParam(
+ "lat_0", parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ common::UnitOfMeasure::DEGREE));
+ formatter->addParam(
+ "lon_0", parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ common::UnitOfMeasure::DEGREE));
+ formatter->addParam(
+ "k_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE));
+ formatter->addParam("x_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_FALSE_EASTING));
+ formatter->addParam("y_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_FALSE_NORTHING));
+ }
+ } else if (methodEPSGCode ==
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) {
+ const double azimuth =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE,
+ common::UnitOfMeasure::DEGREE);
+ const double angleRectifiedToSkewGrid = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
+ common::UnitOfMeasure::DEGREE);
+ // Map to Swiss Oblique Mercator / somerc
+ if (std::fabs(azimuth - 90) < 1e-4 &&
+ std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) {
+ bConversionDone = true;
+ formatter->addStep("somerc");
+ formatter->addParam(
+ "lat_0", parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ common::UnitOfMeasure::DEGREE));
+ formatter->addParam(
+ "lon_0", parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ common::UnitOfMeasure::DEGREE));
+ formatter->addParam(
+ "k_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE));
+ formatter->addParam(
+ "x_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE));
+ formatter->addParam(
+ "y_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE));
+ }
+ } else if (methodEPSGCode == EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED) {
+ double colatitude =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS,
+ common::UnitOfMeasure::DEGREE);
+ double latitudePseudoStandardParallel = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL,
+ common::UnitOfMeasure::DEGREE);
+ // 30deg 17' 17.30311'' = 30.28813975277777776
+ // 30deg 17' 17.303'' = 30.288139722222223 as used in GDAL WKT1
+ if (std::fabs(colatitude - 30.2881397) > 1e-7) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS);
+ }
+ if (std::fabs(latitudePseudoStandardParallel - 78.5) > 1e-8) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL);
+ }
+ } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
+ double latitudeOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ if (latitudeOrigin != 0) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
+ }
+ } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B) {
+ const auto &scaleFactor = parameterValueMeasure(WKT1_SCALE_FACTOR, 0);
+ if (scaleFactor.unit().type() != common::UnitOfMeasure::Type::UNKNOWN &&
+ std::fabs(scaleFactor.getSIValue() - 1.0) > 1e-10) {
+ throw io::FormattingException(
+ "Unexpected presence of scale factor in Mercator (variant B)");
+ }
+ double latitudeOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ if (latitudeOrigin != 0) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
+ }
+ // 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 &&
+ std::fabs(measure.value() -
+ UTM_LATITUDE_OF_NATURAL_ORIGIN) < 1e-10) {
+ bLatitudeNatOriginUTM = true;
+ } else if (
+ (epsg_code ==
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN ||
+ epsg_code ==
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN) &&
+ measure.unit()._isEquivalentTo(
+ common::UnitOfMeasure::DEGREE,
+ util::IComparable::Criterion::EQUIVALENT)) {
+ double dfZone = (measure.value() + 183.0) / 6.0;
+ if (dfZone > 0.9 && dfZone < 60.1 &&
+ std::abs(dfZone - std::round(dfZone)) < 1e-10) {
+ zone = static_cast<int>(std::lround(dfZone));
+ }
+ } else if (
+ epsg_code ==
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN &&
+ measure.unit()._isEquivalentTo(
+ common::UnitOfMeasure::SCALE_UNITY,
+ util::IComparable::Criterion::EQUIVALENT) &&
+ std::fabs(measure.value() - UTM_SCALE_FACTOR) < 1e-10) {
+ bScaleFactorUTM = true;
+ } else if (epsg_code == EPSG_CODE_PARAMETER_FALSE_EASTING &&
+ measure.value() == UTM_FALSE_EASTING &&
+ measure.unit()._isEquivalentTo(
+ common::UnitOfMeasure::METRE,
+ util::IComparable::Criterion::EQUIVALENT)) {
+ bFalseEastingUTM = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_FALSE_NORTHING &&
+ measure.unit()._isEquivalentTo(
+ common::UnitOfMeasure::METRE,
+ util::IComparable::Criterion::EQUIVALENT)) {
+ if (std::fabs(measure.value() -
+ UTM_NORTH_FALSE_NORTHING) < 1e-10) {
+ bFalseNorthingUTM = true;
+ north = true;
+ } else if (std::fabs(measure.value() -
+ UTM_SOUTH_FALSE_NORTHING) <
+ 1e-10) {
+ bFalseNorthingUTM = true;
+ north = false;
+ }
+ }
+ }
+ }
+ }
+ if (bLatitudeNatOriginUTM && zone > 0 && bScaleFactorUTM &&
+ bFalseEastingUTM && bFalseNorthingUTM) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a Conversion object where some parameters are better
+ * identified.
+ *
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::identify() const {
+ auto newConversion = Conversion::nn_make_shared<Conversion>(*this);
+ newConversion->assignSelf(newConversion);
+
+ if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
+ // Check for UTM
+ int zone = 0;
+ bool north = true;
+ if (isUTM(zone, north)) {
+ newConversion->setProperties(
+ getUTMConversionProperty(util::PropertyMap(), zone, north));
+ }
+ }
+
+ return newConversion;
+}
+
+//! @cond Doxygen_Suppress
+// ---------------------------------------------------------------------------
+
+InvalidOperation::InvalidOperation(const char *message) : Exception(message) {}
+
+// ---------------------------------------------------------------------------
+
+InvalidOperation::InvalidOperation(const std::string &message)
+ : Exception(message) {}
+
+// ---------------------------------------------------------------------------
+
+InvalidOperation::InvalidOperation(const InvalidOperation &) = default;
+
+// ---------------------------------------------------------------------------
+
+InvalidOperation::~InvalidOperation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct Transformation::Private {
+
+ TransformationPtr forwardOperation_{};
+
+ 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
+
+// ---------------------------------------------------------------------------
+
+Transformation::Transformation(const Transformation &other)
+ : CoordinateOperation(other), SingleOperation(other),
+ d(internal::make_unique<Private>(*other.d)) {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the source crs::CRS of the transformation.
+ *
+ * @return the source CRS.
+ */
+const crs::CRSNNPtr &Transformation::sourceCRS() PROJ_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
+TransformationNNPtr Transformation::shallowClone() const {
+ auto conv = Transformation::nn_make_shared<Transformation>(*this);
+ conv->assignSelf(conv);
+ conv->setCRSs(this, false);
+ return conv;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+/** \brief Return the TOWGS84 parameters of the transformation.
+ *
+ * If this transformation uses Coordinate Frame Rotation, Position Vector
+ * transformation or Geocentric translations, a vector of 7 double values
+ * using the Position Vector convention (EPSG:9606) is returned. Those values
+ * can be used as the value of the WKT1 TOWGS84 parameter or
+ * PROJ +towgs84 parameter.
+ *
+ * @return a vector of 7 values if valid, otherwise a io::FormattingException
+ * is thrown.
+ * @throws io::FormattingException
+ */
+std::vector<double>
+Transformation::getTOWGS84Parameters() const // throw(io::FormattingException)
+{
+ // GDAL WKT1 assumes EPSG:9606 / Position Vector convention
+
+ bool sevenParamsTransform = false;
+ bool threeParamsTransform = false;
+ bool invertRotSigns = false;
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const int methodEPSGCode = l_method->getEPSGCode();
+ const auto paramCount = parameterValues().size();
+ if ((paramCount == 7 &&
+ ci_find(methodName, "Coordinate Frame") != std::string::npos) ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) {
+ sevenParamsTransform = true;
+ invertRotSigns = true;
+ } else if ((paramCount == 7 &&
+ ci_find(methodName, "Position Vector") != std::string::npos) ||
+ methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
+ sevenParamsTransform = true;
+ invertRotSigns = false;
+ } else if ((paramCount == 3 &&
+ ci_find(methodName, "Geocentric translations") !=
+ std::string::npos) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
+ threeParamsTransform = true;
+ }
+
+ if (threeParamsTransform || sevenParamsTransform) {
+ std::vector<double> params(7, 0.0);
+ bool foundX = false;
+ bool foundY = false;
+ bool foundZ = false;
+ bool foundRotX = false;
+ bool foundRotY = false;
+ bool foundRotZ = false;
+ bool foundScale = false;
+ const double rotSign = invertRotSigns ? -1.0 : 1.0;
+
+ const auto fixNegativeZero = [](double x) {
+ if (x == 0.0)
+ return 0.0;
+ return x;
+ };
+
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &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] = fixNegativeZero(
+ rotSign *
+ measure.convertToUnit(
+ common::UnitOfMeasure::ARC_SECOND));
+ foundRotX = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) {
+ params[4] = fixNegativeZero(
+ rotSign *
+ measure.convertToUnit(
+ common::UnitOfMeasure::ARC_SECOND));
+ foundRotY = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) {
+ params[5] = fixNegativeZero(
+ rotSign *
+ measure.convertToUnit(
+ common::UnitOfMeasure::ARC_SECOND));
+ foundRotZ = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) {
+ params[6] = measure.convertToUnit(
+ common::UnitOfMeasure::PARTS_PER_MILLION);
+ foundScale = true;
+ }
+ }
+ }
+ }
+ if (foundX && foundY && foundZ &&
+ (threeParamsTransform ||
+ (foundRotX && foundRotY && foundRotZ && foundScale))) {
+ return params;
+ } else {
+ throw io::FormattingException(
+ "Missing required parameter values in transformation");
+ }
+ }
+
+#if 0
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS ||
+ methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
+ auto offsetLat =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
+ auto offsetLong =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
+
+ auto offsetHeight =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
+
+ if (offsetLat.getSIValue() == 0.0 && offsetLong.getSIValue() == 0.0 &&
+ offsetHeight.getSIValue() == 0.0) {
+ std::vector<double> params(7, 0.0);
+ return params;
+ }
+ }
+#endif
+
+ throw io::FormattingException(
+ "Transformation cannot be formatted as WKT1 TOWGS84 parameters");
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief 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->extractGeodeticCRS();
+ if (!transformSourceCRS) {
+ throw InvalidOperation(
+ "Cannot find GeodeticCRS in sourceCRS of TOWGS84 transformation");
+ }
+
+ util::PropertyMap properties;
+ properties.set(common::IdentifiedObject::NAME_KEY,
+ concat("Transformation from ", transformSourceCRS->nameStr(),
+ " to WGS84"));
+
+ auto targetCRS =
+ dynamic_cast<const crs::GeographicCRS *>(transformSourceCRS.get())
+ ? util::nn_static_pointer_cast<crs::CRS>(
+ crs::GeographicCRS::EPSG_4326)
+ : util::nn_static_pointer_cast<crs::CRS>(
+ crs::GeodeticCRS::EPSG_4978);
+
+ if (TOWGS84Parameters.size() == 3) {
+ return createGeocentricTranslations(
+ properties, NN_NO_CHECK(transformSourceCRS), targetCRS,
+ TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2],
+ {});
+ }
+
+ return createPositionVector(properties, NN_NO_CHECK(transformSourceCRS),
+ targetCRS, TOWGS84Parameters[0],
+ TOWGS84Parameters[1], TOWGS84Parameters[2],
+ TOWGS84Parameters[3], TOWGS84Parameters[4],
+ TOWGS84Parameters[5], TOWGS84Parameters[6], {});
+}
+
+// ---------------------------------------------------------------------------
+/** \brief 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);
+ }
+}
+
+//! @endcond
+// ---------------------------------------------------------------------------
+
+void Transformation::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const // throw(FormattingException)
+{
+ if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ throw io::FormattingException(
+ "Transformation cannot be exported as a PROJ.4 string");
+ }
+
+ formatter->setCoordinateOperationOptimizations(true);
+
+ bool positionVectorConvention = true;
+ bool sevenParamsTransform = false;
+ bool threeParamsTransform = false;
+ bool fifteenParamsTransform = false;
+ const auto &l_method = method();
+ const int methodEPSGCode = l_method->getEPSGCode();
+ const auto &methodName = l_method->nameStr();
+ const auto paramCount = parameterValues().size();
+ const bool l_isTimeDependent = isTimeDependent(methodName);
+ const bool isPositionVector =
+ ci_find(methodName, "Position Vector") != std::string::npos ||
+ ci_find(methodName, "PV") != std::string::npos;
+ const bool isCoordinateFrame =
+ ci_find(methodName, "Coordinate Frame") != std::string::npos ||
+ ci_find(methodName, "CF") != std::string::npos;
+ if ((paramCount == 7 && isCoordinateFrame && !l_isTimeDependent) ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) {
+ positionVectorConvention = false;
+ sevenParamsTransform = true;
+ } else if (
+ (paramCount == 15 && isCoordinateFrame && l_isTimeDependent) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) {
+ positionVectorConvention = false;
+ fifteenParamsTransform = true;
+ } else if ((paramCount == 7 && isPositionVector && !l_isTimeDependent) ||
+ methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
+ sevenParamsTransform = true;
+ } else if (
+ (paramCount == 15 && isPositionVector && l_isTimeDependent) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) {
+ fifteenParamsTransform = true;
+ } else if ((paramCount == 3 &&
+ ci_find(methodName, "Geocentric translations") !=
+ std::string::npos) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
+ threeParamsTransform = true;
+ }
+ if (threeParamsTransform || sevenParamsTransform ||
+ fifteenParamsTransform) {
+ double x =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
+ double y =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
+ double z =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
+
+ 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) {
+
+ 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");
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ if (sourceCRSGeog && targetCRSGeog) {
+ const auto &unitSrc =
+ sourceCRSGeog->coordinateSystem()->axisList()[0]->unit();
+ const auto &unitDst =
+ targetCRSGeog->coordinateSystem()->axisList()[0]->unit();
+ if (!unitSrc._isEquivalentTo(
+ unitDst, util::IComparable::Criterion::EQUIVALENT)) {
+ formatter->addStep("unitconvert");
+ auto projUnit = unitSrc.exportToPROJString();
+ if (projUnit.empty()) {
+ formatter->addParam("xy_in", unitSrc.conversionToSI());
+ } else {
+ formatter->addParam("xy_in", projUnit);
+ }
+ projUnit = unitDst.exportToPROJString();
+ if (projUnit.empty()) {
+ formatter->addParam("xy_out", unitDst.conversionToSI());
+ } else {
+ formatter->addParam("xy_out", projUnit);
+ }
+ }
+ }
+ return true;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) {
+
+ auto sourceCRSGeod =
+ dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get());
+ auto targetCRSGeod =
+ dynamic_cast<const crs::GeodeticCRS *>(targetCRS().get());
+ if (sourceCRSGeod && targetCRSGeod) {
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRSGeod);
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRSGeod);
+ bool isSrcGeocentric = sourceCRSGeod->isGeocentric();
+ bool isSrcGeographic = sourceCRSGeog != nullptr;
+ bool isTargetGeocentric = targetCRSGeod->isGeocentric();
+ bool isTargetGeographic = targetCRSGeog != nullptr;
+ if ((isSrcGeocentric && isTargetGeographic) ||
+ (isSrcGeographic && isTargetGeocentric)) {
+
+ formatter->startInversion();
+ sourceCRSGeod->_exportToPROJString(formatter);
+ formatter->stopInversion();
+
+ targetCRSGeod->_exportToPROJString(formatter);
+
+ return true;
+ }
+ }
+
+ throw io::FormattingException("Invalid nature of source and/or "
+ "targetCRS for Geographic/Geocentric "
+ "conversion");
+ }
+
+ 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_{};
+ bool discardSuperseded_ = true;
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+CoordinateOperationContext::~CoordinateOperationContext() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationContext::CoordinateOperationContext()
+ : d(internal::make_unique<Private>()) {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the authority factory, or null */
+const io::AuthorityFactoryPtr &
+CoordinateOperationContext::getAuthorityFactory() const {
+ return d->authorityFactory_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the desired area of interest, or null */
+const metadata::ExtentPtr &
+CoordinateOperationContext::getAreaOfInterest() const {
+ return d->extent_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set the desired area of interest, or null */
+void CoordinateOperationContext::setAreaOfInterest(
+ const metadata::ExtentPtr &extent) {
+ d->extent_ = extent;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the desired accuracy (in metre), or 0 */
+double CoordinateOperationContext::getDesiredAccuracy() const {
+ return d->accuracy_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set the desired accuracy (in metre), or 0 */
+void CoordinateOperationContext::setDesiredAccuracy(double accuracy) {
+ d->accuracy_ = accuracy;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set how source and target CRS extent should be used
+ * when considering if a transformation can be used (only takes effect if
+ * no area of interest is explicitly defined).
+ *
+ * The default is
+ * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST.
+ */
+void CoordinateOperationContext::setSourceAndTargetCRSExtentUse(
+ SourceTargetCRSExtentUse use) {
+ d->sourceAndTargetCRSExtentUse_ = use;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return how source and target CRS extent should be used
+ * when considering if a transformation can be used (only takes effect if
+ * no area of interest is explicitly defined).
+ *
+ * The default is
+ * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST.
+ */
+CoordinateOperationContext::SourceTargetCRSExtentUse
+CoordinateOperationContext::getSourceAndTargetCRSExtentUse() const {
+ return d->sourceAndTargetCRSExtentUse_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set the spatial criterion to use when comparing the area of
+ * validity
+ * of coordinate operations with the area of interest / area of validity of
+ * source and target CRS.
+ *
+ * The default is STRICT_CONTAINMENT.
+ */
+void CoordinateOperationContext::setSpatialCriterion(
+ SpatialCriterion criterion) {
+ d->spatialCriterion_ = criterion;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the spatial criterion to use when comparing the area of
+ * validity
+ * of coordinate operations with the area of interest / area of validity of
+ * source and target CRS.
+ *
+ * The default is STRICT_CONTAINMENT.
+ */
+CoordinateOperationContext::SpatialCriterion
+CoordinateOperationContext::getSpatialCriterion() const {
+ return d->spatialCriterion_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether PROJ alternative grid names should be substituted to
+ * the official authority names.
+ *
+ * This only has effect is an authority factory with a non-null database context
+ * has been attached to this context.
+ *
+ * If set to false, it is still possible to
+ * obtain later the substitution by using io::PROJStringFormatter::create()
+ * with a non-null database context.
+ *
+ * The default is true.
+ */
+void CoordinateOperationContext::setUsePROJAlternativeGridNames(
+ bool usePROJNames) {
+ d->usePROJNames_ = usePROJNames;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether PROJ alternative grid names should be substituted to
+ * the official authority names.
+ *
+ * The default is true.
+ */
+bool CoordinateOperationContext::getUsePROJAlternativeGridNames() const {
+ return d->usePROJNames_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether transformations that are superseded (but not
+ * deprecated)
+ * should be discarded.
+ *
+ * The default is true.
+ */
+bool CoordinateOperationContext::getDiscardSuperseded() const {
+ return d->discardSuperseded_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether transformations that are superseded (but not deprecated)
+ * should be discarded.
+ *
+ * The default is true.
+ */
+void CoordinateOperationContext::setDiscardSuperseded(bool discard) {
+ d->discardSuperseded_ = discard;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set how grid availability is used.
+ *
+ * The default is USE_FOR_SORTING.
+ */
+void CoordinateOperationContext::setGridAvailabilityUse(
+ GridAvailabilityUse use) {
+ d->gridAvailabilityUse_ = use;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return how grid availability is used.
+ *
+ * The default is USE_FOR_SORTING.
+ */
+CoordinateOperationContext::GridAvailabilityUse
+CoordinateOperationContext::getGridAvailabilityUse() const {
+ return d->gridAvailabilityUse_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether an intermediate pivot CRS can be used for researching
+ * coordinate operations between a source and target CRS.
+ *
+ * Concretely if in the database there is an operation from A to C
+ * (or C to A), and another one from C to B (or B to C), but no direct
+ * operation between A and B, setting this parameter to 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.
+ *
+ * If authorityFactory->getAuthority() is the empty string, then coordinate
+ * operations from any authority will be searched, with the restrictions set
+ * in the authority_to_authority_preference database table.
+ * If authorityFactory->getAuthority() is set to "any", then coordinate
+ * operations from any authority will be searched
+ * If authorityFactory->getAuthority() is a non-empty string different of "any",
+ * then coordinate operatiosn will be searched only in that authority namespace.
+ *
+ * @param authorityFactory Authority factory, or null if no database lookup
+ * is allowed.
+ * Use io::authorityFactory::create(context, std::string()) to allow all
+ * authorities to be used.
+ * @param extent Area of interest, or null if none is known.
+ * @param accuracy Maximum allowed accuracy in metre, as specified in or
+ * 0 to get best accuracy.
+ * @return a new context.
+ */
+CoordinateOperationContextNNPtr CoordinateOperationContext::create(
+ const io::AuthorityFactoryPtr &authorityFactory,
+ const metadata::ExtentPtr &extent, double accuracy) {
+ auto ctxt = NN_NO_CHECK(
+ CoordinateOperationContext::make_unique<CoordinateOperationContext>());
+ ctxt->d->authorityFactory_ = authorityFactory;
+ ctxt->d->extent_ = extent;
+ ctxt->d->accuracy_ = accuracy;
+ return ctxt;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct CoordinateOperationFactory::Private {
+
+ struct Context {
+ // This is the 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 FilterResults {
+
+ FilterResults(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);
+ }
+
+ FilterResults &andSort() {
+ sort();
+
+ // And now that we have a sorted list, we can remove uninteresting
+ // results
+ // ...
+ removeSyntheticNullTransforms();
+ removeUninterestingOps();
+ removeDuplicateOps();
+ removeSyntheticNullTransforms();
+ return *this;
+ }
+
+ // ----------------------------------------------------------------------
+
+ // cppcheck-suppress functionStatic
+ const std::vector<CoordinateOperationNNPtr> &getRes() { return res; }
+
+ // ----------------------------------------------------------------------
+ private:
+ const std::vector<CoordinateOperationNNPtr> &sourceList;
+ const CoordinateOperationContextNNPtr &context;
+ const 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;
+ const auto gridAvailabilityUse = context->getGridAvailabilityUse();
+ for (const auto &op : res) {
+ bool dummy = false;
+ auto extentOp = getExtent(op, true, dummy);
+ double area = 0.0;
+ if (extentOp) {
+ if (areaOfInterest) {
+ area = getPseudoArea(
+ extentOp->intersection(NN_NO_CHECK(areaOfInterest)));
+ } else if (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() &&
+ (gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ USE_FOR_SORTING ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY)) {
+ const auto gridsNeeded = op->gridsNeeded(
+ context->getAuthorityFactory()->databaseContext());
+ for (const auto &gridDesc : gridsNeeded) {
+ hasGrids = true;
+ if (gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ USE_FOR_SORTING &&
+ !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() {
+
+ if (res.size() <= 1) {
+ return;
+ }
+
+ // When going from EPSG:4807 (NTF Paris) to EPSG:4171 (RGC93), we get
+ // EPSG:7811, NTF (Paris) to RGF93 (2), 1 m
+ // and unknown id, NTF (Paris) to NTF (1) + Inverse of RGF93 to NTF (2),
+ // 1 m
+ // both have same PROJ string and extent
+ // Do not keep the later (that has more steps) as it adds no value.
+
+ std::set<std::string> setPROJPlusExtent;
+ std::vector<CoordinateOperationNNPtr> resTemp;
+ for (const auto &op : res) {
+ auto formatter = io::PROJStringFormatter::create();
+ try {
+ std::string key(op->exportToPROJString(formatter.get()));
+ bool dummy = false;
+ auto extentOp = getExtent(op, true, dummy);
+ if (extentOp) {
+ const auto &geogElts = extentOp->geographicElements();
+ if (geogElts.size() == 1) {
+ auto bbox = dynamic_cast<
+ const metadata::GeographicBoundingBox *>(
+ geogElts[0].get());
+ if (bbox) {
+ double w = bbox->westBoundLongitude();
+ double s = bbox->southBoundLatitude();
+ double e = bbox->eastBoundLongitude();
+ double n = bbox->northBoundLatitude();
+ key += "-";
+ key += toString(w);
+ key += "-";
+ key += toString(s);
+ key += "-";
+ key += toString(e);
+ key += "-";
+ key += toString(n);
+ }
+ }
+ }
+
+ if (setPROJPlusExtent.find(key) == setPROJPlusExtent.end()) {
+ resTemp.emplace_back(op);
+ setPROJPlusExtent.insert(key);
+ }
+ } catch (const std::exception &) {
+ resTemp.emplace_back(op);
+ }
+ }
+ res = std::move(resTemp);
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+/** \brief Filter operations and sort them given context.
+ *
+ * If a desired accuracy is specified, only keep operations whose accuracy
+ * is at least the desired one.
+ * If an area of interest is specified, only keep operations whose area of
+ * use include the area of interest.
+ * Then sort remaining operations by descending area of use, and increasing
+ * accuracy.
+ */
+static std::vector<CoordinateOperationNNPtr>
+filterAndSort(const std::vector<CoordinateOperationNNPtr> &sourceList,
+ const CoordinateOperationContextNNPtr &context,
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) {
+ return FilterResults(sourceList, context, sourceCRS, targetCRS, false)
+ .andSort()
+ .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);
+ const auto &authFactoryName = authFactory->getAuthority();
+
+ 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()) {
+ std::vector<std::string> authorities;
+ if (authFactoryName == "any") {
+ authorities.emplace_back();
+ }
+ if (authFactoryName.empty()) {
+ authorities = authFactory->databaseContext()
+ ->getAllowedAuthorities(
+ srcAuthName, targetAuthName);
+ if (authorities.empty()) {
+ authorities.emplace_back();
+ }
+ } else {
+ authorities.emplace_back(authFactoryName);
+ }
+ for (const auto &authority : authorities) {
+ const auto tmpAuthFactory =
+ io::AuthorityFactory::create(
+ authFactory->databaseContext(),
+ authority == "any" ? std::string() : authority);
+ auto res =
+ tmpAuthFactory
+ ->createFromCoordinateReferenceSystemCodes(
+ srcAuthName, srcCode, targetAuthName,
+ targetCode,
+ context->getUsePROJAlternativeGridNames(),
+ context->getGridAvailabilityUse() ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID,
+ context->getDiscardSuperseded());
+ 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);
+ const auto &authFactoryName = authFactory->getAuthority();
+
+ 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()) {
+ std::vector<std::string> authorities;
+ if (authFactoryName == "any") {
+ authorities.emplace_back();
+ }
+ if (authFactoryName.empty()) {
+ authorities = authFactory->databaseContext()
+ ->getAllowedAuthorities(
+ srcAuthName, targetAuthName);
+ if (authorities.empty()) {
+ authorities.emplace_back();
+ }
+ } else {
+ authorities.emplace_back(authFactoryName);
+ }
+ for (const auto &authority : authorities) {
+ const auto tmpAuthFactory =
+ io::AuthorityFactory::create(
+ authFactory->databaseContext(),
+ authority == "any" ? std::string() : authority);
+
+ auto res =
+ tmpAuthFactory->createFromCRSCodesWithIntermediates(
+ srcAuthName, srcCode, targetAuthName,
+ targetCode,
+ context->getUsePROJAlternativeGridNames(),
+ context->getGridAvailabilityUse() ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID,
+ context->getDiscardSuperseded(),
+ 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()));
+
+ auto createTransformations = [&](const crs::CRSNNPtr &candidateSrcGeod,
+ const crs::CRSNNPtr &candidateDstGeod,
+ const CoordinateOperationNNPtr &opFirst,
+ bool isNullFirst) {
+ const auto opsSecond =
+ createOperations(candidateSrcGeod, candidateDstGeod, context);
+ const auto opsThird =
+ createOperations(candidateDstGeod, targetCRS, context);
+ assert(!opsThird.empty());
+
+ for (auto &opSecond : opsSecond) {
+ // Check that it is not a transformation synthetized by
+ // ourselves
+ if (!hasIdentifiers(opSecond)) {
+ continue;
+ }
+ // And even if it is a referenced transformation, check that
+ // it is not a trivial one
+ auto so = dynamic_cast<const SingleOperation *>(opSecond.get());
+ if (so && isAxisOrderReversal(so->method()->getEPSGCode())) {
+ continue;
+ }
+
+ std::vector<CoordinateOperationNNPtr> subOps;
+ if (isNullFirst) {
+ opSecond->setCRSs(
+ sourceCRS, NN_CHECK_ASSERT(opSecond->targetCRS()), nullptr);
+ } else {
+ subOps.emplace_back(opFirst);
+ }
+ 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));
+ }
+ };
+
+ // Start in priority with candidates that have exactly the same name as
+ // the sourcCRS and targetCRS. Typically for the case of init=IGNF:XXXX
+ for (const auto &candidateSrcGeod : candidatesSrcGeod) {
+ if (candidateSrcGeod->nameStr() == sourceCRS->nameStr()) {
+ for (const auto &candidateDstGeod : candidatesDstGeod) {
+ if (candidateDstGeod->nameStr() == targetCRS->nameStr()) {
+ const auto opsFirst =
+ createOperations(sourceCRS, candidateSrcGeod, context);
+ assert(!opsFirst.empty());
+ const bool isNullFirst =
+ isNullTransformation(opsFirst[0]->nameStr());
+ createTransformations(candidateSrcGeod, candidateDstGeod,
+ opsFirst[0], isNullFirst);
+ if (!res.empty()) {
+ return;
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ 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) {
+ createTransformations(candidateSrcGeod, candidateDstGeod,
+ opsFirst[0], isNullFirst);
+ }
+ 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 = FilterResults(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;
+
+ const auto &sourceProj4Ext = sourceCRS->getExtensionProj4();
+ const auto &targetProj4Ext = targetCRS->getExtensionProj4();
+ if (!sourceProj4Ext.empty() || !targetProj4Ext.empty()) {
+
+ auto sourceProjExportable =
+ dynamic_cast<io::IPROJStringExportable *>(sourceCRS.get());
+ auto targetProjExportable =
+ dynamic_cast<io::IPROJStringExportable *>(targetCRS.get());
+ if (!sourceProjExportable) {
+ throw InvalidOperation("Source CRS is not PROJ exportable");
+ }
+ if (!targetProjExportable) {
+ throw InvalidOperation("Target CRS is not PROJ exportable");
+ }
+ auto projFormatter = io::PROJStringFormatter::create(
+ io::PROJStringFormatter::Convention::PROJ_4);
+ projFormatter->startInversion();
+ sourceProjExportable->_exportToPROJString(projFormatter.get());
+
+ auto geogSrc =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
+ if (geogSrc) {
+ auto proj5Formatter = io::PROJStringFormatter::create(
+ io::PROJStringFormatter::Convention::PROJ_5);
+ geogSrc->addAngularUnitConvertAndAxisSwap(proj5Formatter.get());
+ projFormatter->ingestPROJString(proj5Formatter->toString());
+ }
+
+ projFormatter->stopInversion();
+
+ targetProjExportable->_exportToPROJString(projFormatter.get());
+
+ auto geogDst =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
+ if (geogDst) {
+ auto proj5Formatter = io::PROJStringFormatter::create(
+ io::PROJStringFormatter::Convention::PROJ_5);
+ geogDst->addAngularUnitConvertAndAxisSwap(proj5Formatter.get());
+ projFormatter->ingestPROJString(proj5Formatter->toString());
+ }
+
+ const auto PROJString = projFormatter->toString();
+ auto properties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr()));
+ res.emplace_back(SingleOperation::createPROJBased(
+ properties, PROJString, sourceCRS, targetCRS, {}));
+ return res;
+ }
+
+ 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 &&
+ !srcDatum->_isEquivalentTo(
+ dstDatum.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ 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 && geogCRSOfBaseOfBoundSrc &&
+ (hubSrcGeog->_isEquivalentTo(
+ geogDst, util::IComparable::Criterion::EQUIVALENT) ||
+ hubSrcGeog->is2DPartOf3D(NN_NO_CHECK(geogDst)))) {
+ 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 the datum are equivalent, this is also fine
+ } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog->datum() &&
+ geogDst->datum() &&
+ hubSrcGeog->datum()->_isEquivalentTo(
+ geogDst->datum().get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto opsFirst =
+ createOperations(boundSrc->baseCRS(),
+ NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context);
+ auto opsLast = createOperations(hubSrc, targetCRS, context);
+ if (!opsFirst.empty() && !opsLast.empty()) {
+ for (const auto &opFirst : opsFirst) {
+ for (const auto &opLast : opsLast) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {opFirst, boundSrc->transformation(),
+ opLast},
+ !allowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ }
+ if (!res.empty()) {
+ return res;
+ }
+ }
+ // Consider WGS 84 and NAD83 as equivalent in that context if the
+ // geogCRSOfBaseOfBoundSrc ellipsoid is Clarke66 (for NAD27)
+ // Case of "+proj=latlong +ellps=clrk66
+ // +nadgrids=ntv1_can.dat,conus"
+ // to "+proj=latlong +datum=NAD83"
+ } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog->datum() &&
+ geogDst->datum() &&
+ geogCRSOfBaseOfBoundSrc->ellipsoid()->_isEquivalentTo(
+ datum::Ellipsoid::CLARKE_1866.get(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ hubSrcGeog->datum()->_isEquivalentTo(
+ datum::GeodeticReferenceFrame::EPSG_6326.get(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ geogDst->datum()->_isEquivalentTo(
+ datum::GeodeticReferenceFrame::EPSG_6269.get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto nnGeogCRSOfBaseOfBoundSrc =
+ NN_NO_CHECK(geogCRSOfBaseOfBoundSrc);
+ if (boundSrc->baseCRS()->_isEquivalentTo(
+ nnGeogCRSOfBaseOfBoundSrc.get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto transf = boundSrc->transformation()->shallowClone();
+ transf->setProperties(util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildTransfName(boundSrc->baseCRS()->nameStr(),
+ targetCRS->nameStr())));
+ transf->setCRSs(boundSrc->baseCRS(), targetCRS, nullptr);
+ res.emplace_back(transf);
+ return res;
+ } else {
+ auto opsFirst = createOperations(
+ boundSrc->baseCRS(), nnGeogCRSOfBaseOfBoundSrc, context);
+ auto transf = boundSrc->transformation()->shallowClone();
+ transf->setProperties(util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildTransfName(nnGeogCRSOfBaseOfBoundSrc->nameStr(),
+ targetCRS->nameStr())));
+ transf->setCRSs(nnGeogCRSOfBaseOfBoundSrc, targetCRS, nullptr);
+ if (!opsFirst.empty()) {
+ for (const auto &opFirst : opsFirst) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {opFirst, transf},
+ !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/iso19111/coordinatesystem.cpp b/src/iso19111/coordinatesystem.cpp
new file mode 100644
index 00000000..a3ad04e0
--- /dev/null
+++ b/src/iso19111/coordinatesystem.cpp
@@ -0,0 +1,1279 @@
+/******************************************************************************
+ *
+ * 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 {
+ if (axisName == "Geodetic latitude") {
+ axisDesignation = "Latitude";
+ } else if (axisName == "Geodetic longitude") {
+ axisDesignation = "Longitude";
+ } 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
+CoordinateSystemAxisNNPtr
+CoordinateSystemAxis::alterUnit(const common::UnitOfMeasure &newUnit) const {
+ return create(util::PropertyMap().set(IdentifiedObject::NAME_KEY, name()),
+ abbreviation(), direction(), newUnit, meridian());
+}
+//! @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() != io::WKTFormatter::OutputAxisRule::YES) {
+ 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
+EllipsoidalCSNNPtr EllipsoidalCS::alterAngularUnit(
+ const common::UnitOfMeasure &angularUnit) const {
+ const auto &l_axisList = CoordinateSystem::getPrivate()->axisList;
+ if (l_axisList.size() == 2) {
+ return EllipsoidalCS::create(util::PropertyMap(),
+ l_axisList[0]->alterUnit(angularUnit),
+ l_axisList[1]->alterUnit(angularUnit));
+ } else {
+ assert(l_axisList.size() == 3);
+ return EllipsoidalCS::create(
+ util::PropertyMap(), l_axisList[0]->alterUnit(angularUnit),
+ l_axisList[1]->alterUnit(angularUnit), l_axisList[2]);
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+EllipsoidalCSNNPtr
+EllipsoidalCS::alterLinearUnit(const common::UnitOfMeasure &linearUnit) const {
+ const auto &l_axisList = CoordinateSystem::getPrivate()->axisList;
+ if (l_axisList.size() == 2) {
+ return EllipsoidalCS::create(util::PropertyMap(), l_axisList[0],
+ l_axisList[1]);
+ } else {
+ assert(l_axisList.size() == 3);
+ return EllipsoidalCS::create(util::PropertyMap(), l_axisList[0],
+ l_axisList[1],
+ l_axisList[2]->alterUnit(linearUnit));
+ }
+}
+//! @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
+VerticalCSNNPtr VerticalCS::alterUnit(const common::UnitOfMeasure &unit) const {
+ const auto &l_axisList = CoordinateSystem::getPrivate()->axisList;
+ return VerticalCS::nn_make_shared<VerticalCS>(
+ l_axisList[0]->alterUnit(unit));
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @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 a Northing (first) and Easting
+ * (second) axis.
+ *
+ * @param unit Linear unit of the axes.
+ * @return a new CartesianCS.
+ */
+CartesianCSNNPtr
+CartesianCS::createNorthingEasting(const common::UnitOfMeasure &unit) {
+ return create(util::PropertyMap(),
+ CoordinateSystemAxis::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY,
+ AxisName::Northing),
+ AxisAbbreviation::N, AxisDirection::NORTH, unit),
+ CoordinateSystemAxis::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY,
+ AxisName::Easting),
+ AxisAbbreviation::E, AxisDirection::EAST, unit));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a CartesianCS with a Westing (first) and Southing
+ * (second) axis.
+ *
+ * @param unit Linear unit of the axes.
+ * @return a new CartesianCS.
+ */
+CartesianCSNNPtr
+CartesianCS::createWestingSouthing(const common::UnitOfMeasure &unit) {
+ return create(util::PropertyMap(),
+ CoordinateSystemAxis::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY,
+ AxisName::Easting),
+ AxisAbbreviation::Y, AxisDirection::WEST, unit),
+ CoordinateSystemAxis::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY,
+ AxisName::Northing),
+ AxisAbbreviation::X, AxisDirection::SOUTH, unit));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a CartesianCS, north-pole centered,
+ * with a Easting (first) South-Oriented and
+ * Northing (second) South-Oriented axis.
+ *
+ * @param unit Linear unit of the axes.
+ * @return a new CartesianCS.
+ */
+CartesianCSNNPtr CartesianCS::createNorthPoleEastingSouthNorthingSouth(
+ const common::UnitOfMeasure &unit) {
+ return create(util::PropertyMap(),
+ CoordinateSystemAxis::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY,
+ AxisName::Easting),
+ AxisAbbreviation::E, AxisDirection::SOUTH, unit,
+ Meridian::create(common::Angle(90))),
+ CoordinateSystemAxis::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY,
+ AxisName::Northing),
+ AxisAbbreviation::N, AxisDirection::SOUTH, unit,
+ Meridian::create(common::Angle(180))));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a CartesianCS, south-pole centered,
+ * with a Easting (first) North-Oriented and
+ * Northing (second) North-Oriented axis.
+ *
+ * @param unit Linear unit of the axes.
+ * @return a new CartesianCS.
+ */
+CartesianCSNNPtr CartesianCS::createSouthPoleEastingNorthNorthingNorth(
+ const common::UnitOfMeasure &unit) {
+ return create(util::PropertyMap(),
+ CoordinateSystemAxis::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY,
+ AxisName::Easting),
+ AxisAbbreviation::E, AxisDirection::NORTH, unit,
+ Meridian::create(common::Angle(90))),
+ CoordinateSystemAxis::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY,
+ AxisName::Northing),
+ AxisAbbreviation::N, AxisDirection::NORTH, unit,
+ Meridian::create(common::Angle(0))));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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
+CartesianCSNNPtr
+CartesianCS::alterUnit(const common::UnitOfMeasure &unit) const {
+ const auto &l_axisList = CoordinateSystem::getPrivate()->axisList;
+ if (l_axisList.size() == 2) {
+ return CartesianCS::create(util::PropertyMap(),
+ l_axisList[0]->alterUnit(unit),
+ l_axisList[1]->alterUnit(unit));
+ } else {
+ assert(l_axisList.size() == 3);
+ return CartesianCS::create(
+ util::PropertyMap(), l_axisList[0]->alterUnit(unit),
+ l_axisList[1]->alterUnit(unit), l_axisList[2]->alterUnit(unit));
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @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/iso19111/crs.cpp b/src/iso19111/crs.cpp
new file mode 100644
index 00000000..a05470ff
--- /dev/null
+++ b/src/iso19111/crs.cpp
@@ -0,0 +1,4971 @@
+/******************************************************************************
+ *
+ * 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 <algorithm>
+#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_{};
+ std::string extensionProj4_{};
+ bool implicitCS_ = false;
+
+ void setImplicitCS(const util::PropertyMap &properties) {
+ const auto pVal = properties.get("IMPLICIT_CS");
+ if (pVal) {
+ if (const auto genVal =
+ dynamic_cast<const util::BoxedValue *>(pVal->get())) {
+ if (genVal->type() == util::BoxedValue::Type::BOOLEAN &&
+ genVal->booleanValue()) {
+ implicitCS_ = true;
+ }
+ }
+ }
+ }
+};
+//! @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
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+const std::string &CRS::getExtensionProj4() const noexcept {
+ return d->extensionProj4_;
+}
+//! @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;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static util::PropertyMap
+createPropertyMap(const common::IdentifiedObject *obj) {
+ auto props = util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ obj->nameStr());
+ if (obj->isDeprecated()) {
+ props.set(common::IdentifiedObject::DEPRECATED_KEY, true);
+ }
+ return props;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+CRSNNPtr CRS::alterGeodeticCRS(const GeodeticCRSNNPtr &newGeodCRS) const {
+ auto geodCRS = dynamic_cast<const GeodeticCRS *>(this);
+ if (geodCRS) {
+ return newGeodCRS;
+ }
+
+ auto projCRS = dynamic_cast<const ProjectedCRS *>(this);
+ if (projCRS) {
+ return ProjectedCRS::create(createPropertyMap(this), newGeodCRS,
+ projCRS->derivingConversionRef(),
+ projCRS->coordinateSystem());
+ }
+
+ auto compoundCRS = dynamic_cast<const CompoundCRS *>(this);
+ if (compoundCRS) {
+ std::vector<CRSNNPtr> components;
+ for (const auto &subCrs : compoundCRS->componentReferenceSystems()) {
+ components.emplace_back(subCrs->alterGeodeticCRS(newGeodCRS));
+ }
+ return CompoundCRS::create(createPropertyMap(this), components);
+ }
+
+ return NN_NO_CHECK(
+ std::dynamic_pointer_cast<CRS>(shared_from_this().as_nullable()));
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+CRSNNPtr CRS::alterCSLinearUnit(const common::UnitOfMeasure &unit) const {
+ {
+ auto projCRS = dynamic_cast<const ProjectedCRS *>(this);
+ if (projCRS) {
+ return ProjectedCRS::create(
+ createPropertyMap(this), projCRS->baseCRS(),
+ projCRS->derivingConversionRef(),
+ projCRS->coordinateSystem()->alterUnit(unit));
+ }
+ }
+
+ {
+ auto geodCRS = dynamic_cast<const GeodeticCRS *>(this);
+ if (geodCRS && geodCRS->isGeocentric()) {
+ auto cs = dynamic_cast<const cs::CartesianCS *>(
+ geodCRS->coordinateSystem().get());
+ assert(cs);
+ return GeodeticCRS::create(
+ createPropertyMap(this), geodCRS->datum(),
+ geodCRS->datumEnsemble(), cs->alterUnit(unit));
+ }
+ }
+
+ {
+ auto geogCRS = dynamic_cast<const GeographicCRS *>(this);
+ if (geogCRS && geogCRS->coordinateSystem()->axisList().size() == 3) {
+ return GeographicCRS::create(
+ createPropertyMap(this), geogCRS->datum(),
+ geogCRS->datumEnsemble(),
+ geogCRS->coordinateSystem()->alterLinearUnit(unit));
+ }
+ }
+
+ {
+ auto vertCRS = dynamic_cast<const VerticalCRS *>(this);
+ if (vertCRS) {
+ return VerticalCRS::create(
+ createPropertyMap(this), vertCRS->datum(),
+ vertCRS->datumEnsemble(),
+ vertCRS->coordinateSystem()->alterUnit(unit));
+ }
+ }
+
+ {
+ auto engCRS = dynamic_cast<const EngineeringCRS *>(this);
+ if (engCRS) {
+ auto cartCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(
+ engCRS->coordinateSystem());
+ if (cartCS) {
+ auto props = createPropertyMap(this);
+ props.set("FORCE_OUTPUT_CS", true);
+ return EngineeringCRS::create(props, engCRS->datum(),
+ cartCS->alterUnit(unit));
+ } else {
+ auto vertCS = util::nn_dynamic_pointer_cast<cs::VerticalCS>(
+ engCRS->coordinateSystem());
+ if (vertCS) {
+ auto props = createPropertyMap(this);
+ props.set("FORCE_OUTPUT_CS", true);
+ return EngineeringCRS::create(props, engCRS->datum(),
+ vertCS->alterUnit(unit));
+ }
+ }
+ }
+ }
+
+ return NN_NO_CHECK(
+ std::dynamic_pointer_cast<CRS>(shared_from_this().as_nullable()));
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \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,
+ bool allowIntermediateCRS) 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) {
+ if (geodCRS->_isEquivalentTo(
+ GeographicCRS::EPSG_4978.get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ return thisAsCRS;
+ }
+ hubCRS = util::nn_static_pointer_cast<CRS>(GeodeticCRS::EPSG_4978);
+ } else if (!geogCRS ||
+ geogCRS->_isEquivalentTo(
+ GeographicCRS::EPSG_4326.get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ return thisAsCRS;
+ } else {
+ geodCRS = geogCRS;
+ }
+
+ if (!dbContext) {
+ return thisAsCRS;
+ }
+
+ const auto &l_domains = domains();
+ metadata::ExtentPtr extent;
+ if (!l_domains.empty()) {
+ extent = l_domains[0]->domainOfValidity();
+ }
+
+ std::string crs_authority;
+ const auto &l_identifiers = identifiers();
+ // If the object has an authority, restrict the transformations to
+ // come from that codespace too. This avoids for example EPSG:4269
+ // (NAD83) to use a (dubious) ESRI transformation.
+ if (!l_identifiers.empty()) {
+ crs_authority = *(l_identifiers[0]->codeSpace());
+ }
+
+ auto authorities = dbContext->getAllowedAuthorities(crs_authority, "EPSG");
+ if (authorities.empty()) {
+ authorities.emplace_back();
+ }
+ for (const auto &authority : authorities) {
+ try {
+
+ auto authFactory = io::AuthorityFactory::create(
+ NN_NO_CHECK(dbContext),
+ authority == "any" ? std::string() : authority);
+ auto ctxt = operation::CoordinateOperationContext::create(
+ authFactory, extent, 0.0);
+ ctxt->setAllowUseIntermediateCRS(allowIntermediateCRS);
+ // 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 && !starts_with(transf->nameStr(), "Null geo")) {
+ 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 &&
+ !starts_with(transf->nameStr(),
+ "Null geo")) {
+ 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
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+CRSNNPtr CRS::alterName(const std::string &newName) const {
+ auto crs = shallowClone();
+ auto newNameMod(newName);
+ auto props = util::PropertyMap();
+ if (ends_with(newNameMod, " (deprecated)")) {
+ newNameMod.resize(newNameMod.size() - strlen(" (deprecated)"));
+ props.set(common::IdentifiedObject::DEPRECATED_KEY, true);
+ }
+ props.set(common::IdentifiedObject::NAME_KEY, newNameMod);
+ crs->setProperties(props);
+ return crs;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+CRSNNPtr CRS::alterId(const std::string &authName,
+ const std::string &code) const {
+ auto crs = shallowClone();
+ auto props = util::PropertyMap();
+ props.set(metadata::Identifier::CODESPACE_KEY, authName)
+ .set(metadata::Identifier::CODE_KEY, code);
+ crs->setProperties(props);
+ return crs;
+}
+
+//! @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. The list is sorted by decreasing
+ * confidence.
+ *
+ * 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);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return CRSs that are non-deprecated substitutes for the current CRS.
+ */
+std::list<CRSNNPtr>
+CRS::getNonDeprecated(const io::DatabaseContextNNPtr &dbContext) const {
+ std::list<CRSNNPtr> res;
+ const auto &l_identifiers = identifiers();
+ if (l_identifiers.empty()) {
+ return res;
+ }
+ const char *tableName = nullptr;
+ if (dynamic_cast<const GeodeticCRS *>(this)) {
+ tableName = "geodetic_crs";
+ } else if (dynamic_cast<const ProjectedCRS *>(this)) {
+ tableName = "projected_crs";
+ } else if (dynamic_cast<const VerticalCRS *>(this)) {
+ tableName = "vertical_crs";
+ } else if (dynamic_cast<const CompoundCRS *>(this)) {
+ tableName = "compound_crs";
+ }
+ if (!tableName) {
+ return res;
+ }
+ const auto &id = l_identifiers[0];
+ auto tmpRes =
+ dbContext->getNonDeprecated(tableName, *(id->codeSpace()), id->code());
+ for (const auto &pair : tmpRes) {
+ res.emplace_back(io::AuthorityFactory::create(dbContext, pair.first)
+ ->createCoordinateReferenceSystem(pair.second));
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @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);
+ properties.getStringValue("EXTENSION_PROJ4",
+ crs->CRS::getPrivate()->extensionProj4_);
+
+ 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);
+ properties.getStringValue("EXTENSION_PROJ4",
+ crs->CRS::getPrivate()->extensionProj4_);
+ 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 WKT1_ESRI");
+ }
+
+ 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);
+ }
+
+ const auto oldAxisOutputRule = formatter->outputAxis();
+ if (oldAxisOutputRule ==
+ io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE &&
+ isGeocentric()) {
+ formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES);
+ }
+ cs->_exportToWKT(formatter);
+ formatter->setOutputAxis(oldAxisOutputRule);
+
+ ObjectUsage::baseExportToWKT(formatter);
+
+ if (!isWKT2 && !formatter->useESRIDialect()) {
+ const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
+ if (!extensionProj4.empty()) {
+ formatter->startNode(io::WKTConstants::EXTENSION, false);
+ formatter->addQuotedString("PROJ4");
+ formatter->addQuotedString(extensionProj4);
+ formatter->endNode();
+ }
+ }
+
+ 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._isEquivalentTo(common::UnitOfMeasure::METRE,
+ util::IComparable::Criterion::EQUIVALENT)) {
+ 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);
+ } else if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ formatter->addParam("units", "m");
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void GeodeticCRS::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
+{
+ if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
+ if (!extensionProj4.empty()) {
+ formatter->ingestPROJString(extensionProj4);
+ formatter->addNoDefs(false);
+ return;
+ }
+ }
+
+ 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");
+ }
+ addDatumInfoToPROJString(formatter);
+ 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 bool l_implicitCS = CRS::getPrivate()->implicitCS_;
+ const auto crsCriterion =
+ l_implicitCS
+ ? util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS
+ : util::IComparable::Criterion::EQUIVALENT;
+
+ 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(), crsCriterion);
+ 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, crsCriterion]() {
+ for (const auto &id : thisDatum->identifiers()) {
+ try {
+ auto tempRes = authorityFactory->createGeodeticCRSFromDatum(
+ *id->codeSpace(), id->code(), geodetic_crs_type);
+ for (const auto &crs : tempRes) {
+ if (_isEquivalentTo(crs.get(), crsCriterion)) {
+ res.emplace_back(crs, 70);
+ }
+ }
+ } catch (const std::exception &) {
+ }
+ }
+ };
+
+ const auto &thisEllipsoid(ellipsoid());
+ auto searchByEllipsoid = [this, &authorityFactory, &res, &thisDatum,
+ &thisEllipsoid, &geodetic_crs_type,
+ l_implicitCS]() {
+ 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) &&
+ (!l_implicitCS ||
+ 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(), crsCriterion);
+ res.emplace_back(crs, match ? 100 : 25);
+ return res;
+ } catch (const std::exception &) {
+ }
+ }
+ } else {
+ bool gotAbove25Pct = false;
+ 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(), crsCriterion)) {
+ 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);
+ gotAbove25Pct = true;
+ } else {
+ res.emplace_back(crsNN, 25);
+ }
+ }
+ if (!res.empty()) {
+ break;
+ }
+ }
+ if (!gotAbove25Pct && 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);
+ properties.getStringValue("EXTENSION_PROJ4",
+ crs->CRS::getPrivate()->extensionProj4_);
+ crs->CRS::getPrivate()->setImplicitCS(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->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
+ if (!extensionProj4.empty()) {
+ formatter->ingestPROJString(extensionProj4);
+ formatter->addNoDefs(false);
+ return;
+ }
+ }
+
+ 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);
+ }
+
+ const auto oldAxisOutputRule = formatter->outputAxis();
+ if (oldAxisOutputRule ==
+ io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE) {
+ formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES);
+ }
+ cs->_exportToWKT(formatter);
+ formatter->setOutputAxis(oldAxisOutputRule);
+
+ 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;
+
+ const auto &l_identifiers = identifiers();
+ // Try to perfectly round-trip ESRI projectedCRS if the current object
+ // perfectly matches the database definition
+ const auto &dbContext = formatter->databaseContext();
+
+ auto l_name = nameStr();
+ std::string l_alias;
+ if (formatter->useESRIDialect() && dbContext) {
+ l_alias = dbContext->getAliasFromOfficialName(l_name, "projected_crs",
+ "ESRI");
+ }
+
+ if (!isWKT2 && formatter->useESRIDialect() && !l_identifiers.empty() &&
+ *(l_identifiers[0]->codeSpace()) == "ESRI" && dbContext) {
+ try {
+ const auto definition = dbContext->getTextDefinition(
+ "projected_crs", "ESRI", l_identifiers[0]->code());
+ if (starts_with(definition, "PROJCS")) {
+ auto crsFromFromDef = io::WKTParser()
+ .attachDatabaseContext(dbContext)
+ .createFromWKT(definition);
+ if (_isEquivalentTo(
+ dynamic_cast<IComparable *>(crsFromFromDef.get()),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ formatter->ingestWKTNode(
+ io::WKTNode::createFrom(definition));
+ return;
+ }
+ }
+ } catch (const std::exception &) {
+ }
+ } else if (!isWKT2 && formatter->useESRIDialect() && !l_alias.empty()) {
+ try {
+ auto res =
+ io::AuthorityFactory::create(NN_NO_CHECK(dbContext), "ESRI")
+ ->createObjectsFromName(
+ l_alias,
+ {io::AuthorityFactory::ObjectType::PROJECTED_CRS},
+ false);
+ if (res.size() == 1) {
+ const auto definition = dbContext->getTextDefinition(
+ "projected_crs", "ESRI",
+ res.front()->identifiers()[0]->code());
+ if (starts_with(definition, "PROJCS")) {
+ if (_isEquivalentTo(
+ dynamic_cast<IComparable *>(res.front().get()),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ formatter->ingestWKTNode(
+ io::WKTNode::createFrom(definition));
+ return;
+ }
+ }
+ }
+ } catch (const std::exception &) {
+ }
+ }
+
+ const auto &l_coordinateSystem = d->coordinateSystem();
+ const auto &axisList = l_coordinateSystem->axisList();
+ if (axisList.size() == 3 && !(isWKT2 && formatter->use2018Keywords())) {
+ io::FormattingException::Throw(
+ "Projected 3D CRS can only be exported since WKT2:2018");
+ }
+
+ const auto exportAxis = [&l_coordinateSystem, &axisList, &formatter]() {
+ const auto oldAxisOutputRule = formatter->outputAxis();
+ if (oldAxisOutputRule ==
+ io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE) {
+ if (&axisList[0]->direction() == &cs::AxisDirection::EAST &&
+ &axisList[1]->direction() == &cs::AxisDirection::NORTH) {
+ formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES);
+ }
+ }
+ l_coordinateSystem->_exportToWKT(formatter);
+ formatter->setOutputAxis(oldAxisOutputRule);
+ };
+
+ if (!isWKT2 && !formatter->useESRIDialect() &&
+ starts_with(nameStr(), "Popular Visualisation CRS / Mercator")) {
+ formatter->startNode(io::WKTConstants::PROJCS, !l_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();
+
+ axisList[0]->unit()._exportToWKT(formatter);
+ exportAxis();
+ derivingConversionRef()->addWKTExtensionNode(formatter);
+ ObjectUsage::baseExportToWKT(formatter);
+ formatter->endNode();
+ return;
+ }
+
+ formatter->startNode(isWKT2 ? io::WKTConstants::PROJCRS
+ : io::WKTConstants::PROJCS,
+ !l_identifiers.empty());
+
+ if (formatter->useESRIDialect()) {
+ if (l_alias.empty()) {
+ l_name = io::WKTFormatter::morphNameToESRI(l_name);
+ } else {
+ l_name = l_alias;
+ }
+ }
+ 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 {
+ const auto oldAxisOutputRule = formatter->outputAxis();
+ formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::NO);
+ l_baseCRS->_exportToWKT(formatter);
+ formatter->setOutputAxis(oldAxisOutputRule);
+ }
+
+ 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);
+ }
+
+ exportAxis();
+
+ if (!isWKT2 && !formatter->useESRIDialect()) {
+ const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
+ if (!extensionProj4.empty()) {
+ formatter->startNode(io::WKTConstants::EXTENSION, false);
+ formatter->addQuotedString("PROJ4");
+ formatter->addQuotedString(extensionProj4);
+ formatter->endNode();
+ } else {
+ derivingConversionRef()->addWKTExtensionNode(formatter);
+ }
+ }
+
+ ObjectUsage::baseExportToWKT(formatter);
+ formatter->endNode();
+ return;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+void ProjectedCRS::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
+{
+ if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
+ if (!extensionProj4.empty()) {
+ formatter->ingestPROJString(extensionProj4);
+ formatter->addNoDefs(false);
+ return;
+ }
+ }
+
+ 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();
+ properties.getStringValue("EXTENSION_PROJ4",
+ crs->CRS::getPrivate()->extensionProj4_);
+ crs->CRS::getPrivate()->setImplicitCS(properties);
+ 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
+ProjectedCRSNNPtr
+ProjectedCRS::alterParametersLinearUnit(const common::UnitOfMeasure &unit,
+ bool convertToNewUnit) const {
+ return create(createPropertyMap(this), baseCRS(),
+ derivingConversionRef()->alterParametersLinearUnit(
+ unit, convertToNewUnit),
+ coordinateSystem());
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @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._isEquivalentTo(common::UnitOfMeasure::METRE,
+ util::IComparable::Criterion::EQUIVALENT)) {
+ 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);
+ }
+ }
+ } else if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ // could come from the hardcoded def of webmerc
+ if (!formatter->hasParam("units")) {
+ formatter->addParam("units", "m");
+ }
+ }
+
+ 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. The list is sorted by decreasing
+ * confidence.
+ *
+ * 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_EXCEPT_AXIS_ORDER_GEOGCRS);
+ 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_EXCEPT_AXIS_ORDER_GEOGCRS)) {
+ if (crs->nameStr() == thisName) {
+ res.clear();
+ res.emplace_back(crsNN, 100);
+ return res;
+ }
+ res.emplace_back(crsNN, eqName ? 90 : 70);
+ } else if (crs->nameStr() == thisName &&
+ CRS::getPrivate()->implicitCS_ &&
+ l_baseCRS->_isEquivalentTo(
+ crs->baseCRS().get(),
+ util::IComparable::Criterion::
+ EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS) &&
+ derivingConversionRef()->_isEquivalentTo(
+ crs->derivingConversionRef().get(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ objects.size() == 1) {
+ res.clear();
+ res.emplace_back(crsNN, 100);
+ return res;
+ } 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_EXCEPT_AXIS_ORDER_GEOGCRS)) {
+ 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.get(common::IdentifiedObject::NAME_KEY)) {
+ 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. The list is sorted by decreasing
+ * confidence.
+ *
+ * 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) {
+
+ auto geodCRS = baseCRSIn->extractGeodeticCRS();
+ auto targetCRS =
+ geodCRS.get() == nullptr ||
+ dynamic_cast<const crs::GeographicCRS *>(geodCRS.get())
+ ? util::nn_static_pointer_cast<crs::CRS>(
+ crs::GeographicCRS::EPSG_4326)
+ : util::nn_static_pointer_cast<crs::CRS>(
+ crs::GeodeticCRS::EPSG_4978);
+ return create(
+ baseCRSIn, targetCRS,
+ 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;
+ }
+ const auto standardCriterion = getStandardCriterion(criterion);
+ return d->baseCRS_->_isEquivalentTo(otherBoundCRS->d->baseCRS_.get(),
+ criterion) &&
+ d->hubCRS_->_isEquivalentTo(otherBoundCRS->d->hubCRS_.get(),
+ criterion) &&
+ d->transformation_->_isEquivalentTo(
+ otherBoundCRS->d->transformation_.get(), standardCriterion);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @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 &) {
+ }
+ bool foundOp = false;
+ for (const auto &op : ops) {
+ std::string opTransfPROJString;
+ bool opTransfPROJStringValid = false;
+ if (op->nameStr().find("Null geographic") == 0) {
+ if (isTOWGS84Compatible()) {
+ auto params =
+ transformation()->getTOWGS84Parameters();
+ if (params ==
+ std::vector<double>{0, 0, 0, 0, 0, 0, 0}) {
+ res.emplace_back(create(candidateBaseCRS,
+ d->hubCRS_,
+ transformation()),
+ pair.second);
+ foundOp = true;
+ break;
+ }
+ }
+ continue;
+ }
+ 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);
+ foundOp = true;
+ break;
+ }
+ }
+ if (!foundOp) {
+ res.emplace_back(
+ create(candidateBaseCRS, d->hubCRS_, transformation()),
+ std::min(70, pair.second));
+ }
+ }
+ }
+ }
+ 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 {
+ bool forceOutputCS_ = false;
+};
+//! @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(internal::make_unique<Private>()) {}
+
+// ---------------------------------------------------------------------------
+
+EngineeringCRS::EngineeringCRS(const EngineeringCRS &other)
+ : SingleCRS(other), d(internal::make_unique<Private>(*(other.d))) {}
+
+// ---------------------------------------------------------------------------
+
+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);
+
+ const auto pVal = properties.get("FORCE_OUTPUT_CS");
+ if (pVal) {
+ if (const auto genVal =
+ dynamic_cast<const util::BoxedValue *>(pVal->get())) {
+ if (genVal->type() == util::BoxedValue::Type::BOOLEAN &&
+ genVal->booleanValue()) {
+ crs->d->forceOutputCS_ = true;
+ }
+ }
+ }
+
+ return crs;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @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);
+ }
+ if (!isWKT2 && d->forceOutputCS_) {
+ coordinateSystem()->axisList()[0]->unit()._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/iso19111/datum.cpp b/src/iso19111/datum.cpp
new file mode 100644
index 00000000..16e86296
--- /dev/null
+++ b/src/iso19111/datum.cpp
@@ -0,0 +1,1996 @@
+/******************************************************************************
+ *
+ * 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::createREFERENCE_MERIDIAN() {
+ return create(util::PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "Reference meridian"),
+ 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;
+ }
+ // In MapInfo, the Paris prime meridian is returned as 2.3372291666667
+ // instead of the official value of 2.33722917, which is a relative
+ // error in the 1e-9 range.
+ return longitude()._isEquivalentTo(otherPM->longitude(), criterion, 1e-8);
+}
+//! @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;
+ }
+ }
+ }
+ // Replace spaces by underscore, except if it is a special MapInfo
+ // datum name
+ } else if (!starts_with(l_name, "MIF ")) {
+ l_name = io::WKTFormatter::morphNameToESRI(l_name);
+ 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/iso19111/factory.cpp b/src/iso19111/factory.cpp
new file mode 100644
index 00000000..47d31db9
--- /dev/null
+++ b/src/iso19111/factory.cpp
@@ -0,0 +1,4973 @@
+/******************************************************************************
+ *
+ * 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::list<SQLRow>;
+using ListOfParams = std::list<SQLValues>;
+
+// ---------------------------------------------------------------------------
+
+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 ListOfParams &parameters = ListOfParams());
+
+ 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_;
+ };
+
+ std::map<std::string, std::list<SQLRow>> &getMapCanonicalizeGRFName() {
+ return mapCanonicalizeGRFName_;
+ }
+
+ // 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);
+
+ datum::PrimeMeridianPtr
+ // cppcheck-suppress functionStatic
+ getPrimeMeridianFromCache(const std::string &code);
+ // cppcheck-suppress functionStatic
+ void cache(const std::string &code, const datum::PrimeMeridianNNPtr &pm);
+
+ // cppcheck-suppress functionStatic
+ cs::CoordinateSystemPtr
+ getCoordinateSystemFromCache(const std::string &code);
+ // cppcheck-suppress functionStatic
+ void cache(const std::string &code, const cs::CoordinateSystemNNPtr &cs);
+
+ // cppcheck-suppress functionStatic
+ metadata::ExtentPtr getExtentFromCache(const std::string &code);
+ // cppcheck-suppress functionStatic
+ void cache(const std::string &code, const metadata::ExtentNNPtr &extent);
+
+ // cppcheck-suppress functionStatic
+ bool getCRSToCRSCoordOpFromCache(
+ const std::string &code,
+ std::vector<operation::CoordinateOperationNNPtr> &list);
+ // cppcheck-suppress functionStatic
+ void cache(const std::string &code,
+ const std::vector<operation::CoordinateOperationNNPtr> &list);
+
+ struct GridInfoCache {
+ std::string fullFilename{};
+ std::string packageName{};
+ std::string url{};
+ bool found = false;
+ bool directDownload = false;
+ bool openLicense = false;
+ bool gridAvailable = false;
+ };
+
+ // cppcheck-suppress functionStatic
+ bool getGridInfoFromCache(const std::string &code, GridInfoCache &info);
+ // cppcheck-suppress functionStatic
+ void cache(const std::string &code, const GridInfoCache &info);
+
+ private:
+ friend class DatabaseContext;
+
+ 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;
+ std::string lastMetadataValue_{};
+ std::map<std::string, std::list<SQLRow>> mapCanonicalizeGRFName_{};
+
+ using LRUCacheOfObjects = lru11::Cache<std::string, util::BaseObjectPtr>;
+
+ static constexpr size_t CACHE_SIZE = 128;
+ LRUCacheOfObjects cacheUOM_{CACHE_SIZE};
+ LRUCacheOfObjects cacheCRS_{CACHE_SIZE};
+ LRUCacheOfObjects cacheGeodeticDatum_{CACHE_SIZE};
+ LRUCacheOfObjects cachePrimeMeridian_{CACHE_SIZE};
+ LRUCacheOfObjects cacheCS_{CACHE_SIZE};
+ LRUCacheOfObjects cacheExtent_{CACHE_SIZE};
+ lru11::Cache<std::string, std::vector<operation::CoordinateOperationNNPtr>>
+ cacheCRSToCrsCoordOp_{CACHE_SIZE};
+ lru11::Cache<std::string, GridInfoCache> cacheGridInfo_{CACHE_SIZE};
+
+ static void insertIntoCache(LRUCacheOfObjects &cache,
+ const std::string &code,
+ const util::BaseObjectPtr &obj);
+
+ static void getFromCache(LRUCacheOfObjects &cache, const std::string &code,
+ util::BaseObjectPtr &obj);
+
+ 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;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void DatabaseContext::Private::insertIntoCache(LRUCacheOfObjects &cache,
+ const std::string &code,
+ const util::BaseObjectPtr &obj) {
+ cache.insert(code, obj);
+}
+
+// ---------------------------------------------------------------------------
+
+void DatabaseContext::Private::getFromCache(LRUCacheOfObjects &cache,
+ const std::string &code,
+ util::BaseObjectPtr &obj) {
+ cache.tryGet(code, obj);
+}
+
+// ---------------------------------------------------------------------------
+
+bool DatabaseContext::Private::getCRSToCRSCoordOpFromCache(
+ const std::string &code,
+ std::vector<operation::CoordinateOperationNNPtr> &list) {
+ return cacheCRSToCrsCoordOp_.tryGet(code, list);
+}
+
+// ---------------------------------------------------------------------------
+
+void DatabaseContext::Private::cache(
+ const std::string &code,
+ const std::vector<operation::CoordinateOperationNNPtr> &list) {
+ cacheCRSToCrsCoordOp_.insert(code, list);
+}
+
+// ---------------------------------------------------------------------------
+
+crs::CRSPtr DatabaseContext::Private::getCRSFromCache(const std::string &code) {
+ util::BaseObjectPtr obj;
+ getFromCache(cacheCRS_, code, obj);
+ return std::static_pointer_cast<crs::CRS>(obj);
+}
+
+// ---------------------------------------------------------------------------
+
+void DatabaseContext::Private::cache(const std::string &code,
+ const crs::CRSNNPtr &crs) {
+ insertIntoCache(cacheCRS_, code, crs.as_nullable());
+}
+
+// ---------------------------------------------------------------------------
+
+common::UnitOfMeasurePtr
+DatabaseContext::Private::getUOMFromCache(const std::string &code) {
+ util::BaseObjectPtr obj;
+ getFromCache(cacheUOM_, code, obj);
+ return std::static_pointer_cast<common::UnitOfMeasure>(obj);
+}
+
+// ---------------------------------------------------------------------------
+
+void DatabaseContext::Private::cache(const std::string &code,
+ const common::UnitOfMeasureNNPtr &uom) {
+ insertIntoCache(cacheUOM_, code, uom.as_nullable());
+}
+
+// ---------------------------------------------------------------------------
+
+datum::GeodeticReferenceFramePtr
+DatabaseContext::Private::getGeodeticDatumFromCache(const std::string &code) {
+ util::BaseObjectPtr obj;
+ getFromCache(cacheGeodeticDatum_, code, obj);
+ return std::static_pointer_cast<datum::GeodeticReferenceFrame>(obj);
+}
+
+// ---------------------------------------------------------------------------
+
+void DatabaseContext::Private::cache(
+ const std::string &code, const datum::GeodeticReferenceFrameNNPtr &datum) {
+ insertIntoCache(cacheGeodeticDatum_, code, datum.as_nullable());
+}
+
+// ---------------------------------------------------------------------------
+
+datum::PrimeMeridianPtr
+DatabaseContext::Private::getPrimeMeridianFromCache(const std::string &code) {
+ util::BaseObjectPtr obj;
+ getFromCache(cachePrimeMeridian_, code, obj);
+ return std::static_pointer_cast<datum::PrimeMeridian>(obj);
+}
+
+// ---------------------------------------------------------------------------
+
+void DatabaseContext::Private::cache(const std::string &code,
+ const datum::PrimeMeridianNNPtr &pm) {
+ insertIntoCache(cachePrimeMeridian_, code, pm.as_nullable());
+}
+
+// ---------------------------------------------------------------------------
+
+cs::CoordinateSystemPtr DatabaseContext::Private::getCoordinateSystemFromCache(
+ const std::string &code) {
+ util::BaseObjectPtr obj;
+ getFromCache(cacheCS_, code, obj);
+ return std::static_pointer_cast<cs::CoordinateSystem>(obj);
+}
+
+// ---------------------------------------------------------------------------
+
+void DatabaseContext::Private::cache(const std::string &code,
+ const cs::CoordinateSystemNNPtr &cs) {
+ insertIntoCache(cacheCS_, code, cs.as_nullable());
+}
+
+// ---------------------------------------------------------------------------
+
+metadata::ExtentPtr
+DatabaseContext::Private::getExtentFromCache(const std::string &code) {
+ util::BaseObjectPtr obj;
+ getFromCache(cacheExtent_, code, obj);
+ return std::static_pointer_cast<metadata::Extent>(obj);
+}
+
+// ---------------------------------------------------------------------------
+
+void DatabaseContext::Private::cache(const std::string &code,
+ const metadata::ExtentNNPtr &extent) {
+ insertIntoCache(cacheExtent_, code, extent.as_nullable());
+}
+
+// ---------------------------------------------------------------------------
+
+bool DatabaseContext::Private::getGridInfoFromCache(const std::string &code,
+ GridInfoCache &info) {
+ return cacheGridInfo_.tryGet(code, info);
+}
+
+// ---------------------------------------------------------------------------
+
+void DatabaseContext::Private::cache(const std::string &code,
+ const GridInfoCache &info) {
+ cacheGridInfo_.insert(code, info);
+}
+
+// ---------------------------------------------------------------------------
+
+#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 ListOfParams &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(column_count);
+ for (int i = 0; i < column_count; i++) {
+ const char *txt = reinterpret_cast<const char *>(
+ sqlite3_column_text(stmt, i));
+ if (txt) {
+ row[i] = txt;
+ }
+ }
+ result.emplace_back(std::move(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(); }
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a metadata item.
+ *
+ * Value remains valid while this is alive and to the next call to getMetadata
+ */
+const char *DatabaseContext::getMetadata(const char *key) const {
+ auto res =
+ d->run("SELECT value FROM metadata WHERE key = ?", {std::string(key)});
+ if (res.empty()) {
+ return nullptr;
+ }
+ d->lastMetadataValue_ = res.front()[0];
+ return d->lastMetadataValue_.c_str();
+}
+
+// ---------------------------------------------------------------------------
+
+//! @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;
+ }
+ const auto &row = res.front();
+ projFilename = row[0];
+ projFormat = row[1];
+ inverse = row[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 {
+ Private::GridInfoCache info;
+ if (d->getGridInfoFromCache(projFilename, info)) {
+ fullFilename = info.fullFilename;
+ packageName = info.packageName;
+ url = info.url;
+ directDownload = info.directDownload;
+ openLicense = info.openLicense;
+ gridAvailable = info.gridAvailable;
+ return info.found;
+ }
+
+ 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});
+ bool ret = !res.empty();
+ if (ret) {
+ const auto &row = res.front();
+ packageName = std::move(row[0]);
+ url = row[1].empty() ? std::move(row[2]) : std::move(row[1]);
+ openLicense = (row[3].empty() ? row[4] : row[3]) == "1";
+ directDownload = (row[5].empty() ? row[6] : row[5]) == "1";
+
+ info.fullFilename = fullFilename;
+ info.packageName = packageName;
+ info.url = url;
+ info.directDownload = directDownload;
+ info.openLicense = openLicense;
+ info.gridAvailable = gridAvailable;
+ }
+ info.found = ret;
+ d->cache(projFilename, info);
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
+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();
+ }
+ const auto &row = res.front();
+ res = d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
+ "auth_name = ? AND code = ? AND source = ?",
+ {tableName, row[0], row[1], source});
+ if (res.empty()) {
+ return std::string();
+ }
+ return res.front()[0];
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the 'text_definition' column of a table for an object
+ *
+ * @param tableName Table name/category.
+ * @param authName Authority name of the object.
+ * @param code Code of the object
+ * @return Text definition (or empty)
+ * @throw FactoryException
+ */
+std::string DatabaseContext::getTextDefinition(const std::string &tableName,
+ const std::string &authName,
+ const std::string &code) const {
+ std::string sql("SELECT text_definition FROM \"");
+ sql += replaceAll(tableName, "\"", "\"\"");
+ sql += "\" WHERE auth_name = ? AND code = ?";
+ auto res = d->run(sql, {authName, code});
+ if (res.empty()) {
+ return std::string();
+ }
+ return res.front()[0];
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the allowed authorities when researching transformations
+ * between different authorities.
+ *
+ * @throw FactoryException
+ */
+std::vector<std::string> DatabaseContext::getAllowedAuthorities(
+ const std::string &sourceAuthName,
+ const std::string &targetAuthName) const {
+ auto res = d->run(
+ "SELECT allowed_authorities FROM authority_to_authority_preference "
+ "WHERE source_auth_name = ? AND target_auth_name = ?",
+ {sourceAuthName, targetAuthName});
+ if (res.empty()) {
+ res = d->run(
+ "SELECT allowed_authorities FROM authority_to_authority_preference "
+ "WHERE source_auth_name = ? AND target_auth_name = 'any'",
+ {sourceAuthName});
+ }
+ if (res.empty()) {
+ res = d->run(
+ "SELECT allowed_authorities FROM authority_to_authority_preference "
+ "WHERE source_auth_name = 'any' AND target_auth_name = ?",
+ {targetAuthName});
+ }
+ if (res.empty()) {
+ res = d->run(
+ "SELECT allowed_authorities FROM authority_to_authority_preference "
+ "WHERE source_auth_name = 'any' AND target_auth_name = 'any'",
+ {});
+ }
+ if (res.empty()) {
+ return std::vector<std::string>();
+ }
+ return split(res.front()[0], ',');
+}
+
+// ---------------------------------------------------------------------------
+
+std::list<std::pair<std::string, std::string>>
+DatabaseContext::getNonDeprecated(const std::string &tableName,
+ const std::string &authName,
+ const std::string &code) const {
+ auto sqlRes =
+ d->run("SELECT replacement_auth_name, replacement_code, source "
+ "FROM deprecation "
+ "WHERE table_name = ? AND deprecated_auth_name = ? "
+ "AND deprecated_code = ?",
+ {tableName, authName, code});
+ std::list<std::pair<std::string, std::string>> res;
+ for (const auto &row : sqlRes) {
+ const auto &source = row[2];
+ if (source == "PROJ") {
+ const auto &replacement_auth_name = row[0];
+ const auto &replacement_code = row[1];
+ res.emplace_back(replacement_auth_name, replacement_code);
+ }
+ }
+ if (!res.empty()) {
+ return res;
+ }
+ for (const auto &row : sqlRes) {
+ const auto &replacement_auth_name = row[0];
+ const auto &replacement_code = row[1];
+ res.emplace_back(replacement_auth_name, replacement_code);
+ }
+ return res;
+}
+
+//! @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(); }
+
+ inline AuthorityFactoryNNPtr createFactory(const std::string &auth_name) {
+ if (auth_name == authority_) {
+ return NN_NO_CHECK(thisFactory_.lock());
+ }
+ return AuthorityFactory::create(context_, auth_name);
+ }
+
+ 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 ListOfParams &parameters = ListOfParams());
+
+ SQLResultSet runWithCodeParam(const std::string &sql,
+ const std::string &code);
+
+ SQLResultSet runWithCodeParam(const char *sql, const std::string &code);
+
+ bool hasAuthorityRestriction() const {
+ return !authority_.empty() && authority_ != "any";
+ }
+
+ private:
+ DatabaseContextNNPtr context_;
+ std::string authority_;
+ std::weak_ptr<AuthorityFactory> thisFactory_{};
+};
+
+// ---------------------------------------------------------------------------
+
+SQLResultSet AuthorityFactory::Private::run(const std::string &sql,
+ const ListOfParams &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);
+ if (deprecated) {
+ props.set(common::IdentifiedObject::DEPRECATED_KEY, true);
+ }
+ 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());
+}
+
+// ---------------------------------------------------------------------------
+
+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", d->authority(), 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.front()[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.front()[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 {
+ const auto cacheKey(d->authority() + code);
+ {
+ auto extent = d->context()->d->getExtentFromCache(cacheKey);
+ if (extent) {
+ return NN_NO_CHECK(extent);
+ }
+ }
+ 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", d->authority(),
+ code);
+ }
+ try {
+ const auto &row = res.front();
+ 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);
+
+ auto extent = metadata::Extent::create(
+ util::optional<std::string>(name),
+ std::vector<metadata::GeographicExtentNNPtr>{bbox},
+ std::vector<metadata::VerticalExtentNNPtr>(),
+ std::vector<metadata::TemporalExtentNNPtr>());
+ d->context()->d->cache(code, extent);
+ return extent;
+
+ } 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 {
+ const auto cacheKey(d->authority() + code);
+ {
+ auto uom = d->context()->d->getUOMFromCache(cacheKey);
+ 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",
+ d->authority(), code);
+ }
+ try {
+ const auto &row = res.front();
+ 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, d->authority(), code);
+ d->context()->d->cache(cacheKey, uom);
+ return uom;
+ } catch (const std::exception &ex) {
+ throw buildFactoryException("unit of measure", code, ex);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static double normalizeMeasure(const std::string &uom_code,
+ const std::string &value,
+ std::string &normalized_uom_code) {
+ if (uom_code == "9110") // DDD.MMSSsss.....
+ {
+ double normalized_value = c_locale_stod(value);
+ 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();
+ return normalized_value;
+ } else {
+ normalized_uom_code = uom_code;
+ return c_locale_stod(value);
+ }
+}
+//! @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 {
+ const auto cacheKey(d->authority() + code);
+ {
+ auto pm = d->context()->d->getPrimeMeridianFromCache(cacheKey);
+ if (pm) {
+ return NN_NO_CHECK(pm);
+ }
+ }
+ 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",
+ d->authority(), code);
+ }
+ try {
+ const auto &row = res.front();
+ 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);
+ const double normalized_value =
+ normalizeMeasure(uom_code, longitude, normalized_uom_code);
+
+ auto uom = d->createUnitOfMeasure(uom_auth_name, normalized_uom_code);
+ auto props = d->createProperties(code, name, deprecated, nullptr);
+ auto pm = datum::PrimeMeridian::create(
+ props, common::Angle(normalized_value, uom));
+ d->context()->d->cache(cacheKey, pm);
+ return pm;
+ } 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.front()[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",
+ d->authority(), code);
+ }
+ try {
+ const auto &row = res.front();
+ 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 {
+ const auto cacheKey(d->authority() + code);
+ {
+ auto datum = d->context()->d->getGeodeticDatumFromCache(cacheKey);
+ 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",
+ d->authority(), code);
+ }
+ try {
+ const auto &row = res.front();
+ 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->context()->d->cache(cacheKey, 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",
+ d->authority(), code);
+ }
+ try {
+ const auto &row = res.front();
+ 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 = ?",
+ {d->authority(), code, d->authority(), code});
+ if (res.empty()) {
+ throw NoSuchAuthorityCodeException("datum not found", d->authority(),
+ code);
+ }
+ if (res.front()[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 {
+ const auto cacheKey(d->authority() + code);
+ {
+ auto cs = d->context()->d->getCoordinateSystemFromCache(cacheKey);
+ if (cs) {
+ return NN_NO_CHECK(cs);
+ }
+ }
+ 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",
+ d->authority(), code);
+ }
+
+ const auto &csType = res.front()[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));
+ }
+
+ const auto cacheAndRet = [this,
+ &cacheKey](const cs::CoordinateSystemNNPtr &cs) {
+ d->context()->d->cache(cacheKey, cs);
+ return cs;
+ };
+
+ auto props = util::PropertyMap()
+ .set(metadata::Identifier::CODESPACE_KEY, d->authority())
+ .set(metadata::Identifier::CODE_KEY, code);
+ if (csType == "ellipsoidal") {
+ if (axisList.size() == 2) {
+ return cacheAndRet(
+ cs::EllipsoidalCS::create(props, axisList[0], axisList[1]));
+ }
+ if (axisList.size() == 3) {
+ return cacheAndRet(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 cacheAndRet(
+ cs::CartesianCS::create(props, axisList[0], axisList[1]));
+ }
+ if (axisList.size() == 3) {
+ return cacheAndRet(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 cacheAndRet(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 {
+ const auto cacheKey(d->authority() + code);
+ auto crs = std::dynamic_pointer_cast<crs::GeodeticCRS>(
+ d->context()->d->getCRSFromCache(cacheKey));
+ 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",
+ d->authority(), code);
+ }
+ try {
+ const auto &row = res.front();
+ 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->context()->d->cache(cacheKey, 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->context()->d->cache(cacheKey, 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",
+ d->authority(), code);
+ }
+ try {
+ const auto &row = res.front();
+ 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 {
+
+ static const char *sql =
+ "SELECT name, area_of_use_auth_name, area_of_use_code, "
+ "method_auth_name, method_code, method_name, "
+
+ "param1_auth_name, param1_code, param1_name, param1_value, "
+ "param1_uom_auth_name, param1_uom_code, "
+
+ "param2_auth_name, param2_code, param2_name, param2_value, "
+ "param2_uom_auth_name, param2_uom_code, "
+
+ "param3_auth_name, param3_code, param3_name, param3_value, "
+ "param3_uom_auth_name, param3_uom_code, "
+
+ "param4_auth_name, param4_code, param4_name, param4_value, "
+ "param4_uom_auth_name, param4_uom_code, "
+
+ "param5_auth_name, param5_code, param5_name, param5_value, "
+ "param5_uom_auth_name, param5_uom_code, "
+
+ "param6_auth_name, param6_code, param6_name, param6_value, "
+ "param6_uom_auth_name, param6_uom_code, "
+
+ "param7_auth_name, param7_code, param7_name, param7_value, "
+ "param7_uom_auth_name, param7_uom_code, "
+
+ "deprecated FROM conversion WHERE auth_name = ? AND code = ?";
+
+ auto res = d->runWithCodeParam(sql, code);
+ if (res.empty()) {
+ throw NoSuchAuthorityCodeException("conversion not found",
+ d->authority(), code);
+ }
+ try {
+ const auto &row = res.front();
+ 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;
+ constexpr int N_MAX_PARAMS = 7;
+ 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);
+ const double normalized_value = normalizeMeasure(
+ param_uom_code, param_value, normalized_uom_code);
+ 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",
+ d->authority(), code);
+ }
+ try {
+ const auto &row = res.front();
+ 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",
+ d->authority(), code);
+ }
+ try {
+ const auto &row = res.front();
+ 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 {
+ const auto cacheKey(d->authority() + code);
+ auto crs = d->context()->d->getCRSFromCache(cacheKey);
+ 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", d->authority(),
+ code);
+ }
+ const auto &type = res.front()[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,
+ std::string());
+}
+
+operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
+ const std::string &code, bool allowConcatenated,
+ bool usePROJAlternativeGridNames, const std::string &typeIn) const {
+ std::string type(typeIn);
+ if (type.empty()) {
+ 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",
+ d->authority(), code);
+ }
+ type = res.front()[0];
+ }
+
+ if (type == "conversion") {
+ return createConversion(code);
+ }
+
+ if (type == "helmert_transformation") {
+
+ auto 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", d->authority(), code);
+ }
+ try {
+ const auto &row = res.front();
+ 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") {
+ auto 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",
+ d->authority(), code);
+ }
+ try {
+ const auto &row = res.front();
+ 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 = ?";
+
+ auto res = d->runWithCodeParam(buffer.str(), code);
+ if (res.empty()) {
+ // shouldn't happen if foreign keys are OK
+ throw NoSuchAuthorityCodeException("other_transformation not found",
+ d->authority(), code);
+ }
+ try {
+ const auto &row = res.front();
+ 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);
+ const double normalized_value = normalizeMeasure(
+ param_uom_code, param_value, normalized_uom_code);
+ 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") {
+ auto 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", d->authority(), code);
+ }
+ try {
+ const auto &row = res.front();
+ 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,
+ std::string()));
+ operations.push_back(
+ d->createFactory(step2_auth_name)
+ ->createCoordinateOperation(step2_code, false,
+ usePROJAlternativeGridNames,
+ std::string()));
+
+ if (!step3_auth_name.empty()) {
+ operations.push_back(
+ d->createFactory(step3_auth_name)
+ ->createCoordinateOperation(step3_code, false,
+ usePROJAlternativeGridNames,
+ std::string()));
+ }
+
+ // 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(
+ d->authority(), sourceCRSCode, d->authority(), targetCRSCode, false,
+ 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.
+ * @param discardSuperseded Whether cordinate operations that are superseded
+ * (but not deprecated) 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,
+ bool discardSuperseded) const {
+
+ auto cacheKey(d->authority());
+ cacheKey += sourceCRSAuthName;
+ cacheKey += sourceCRSCode;
+ cacheKey += targetCRSAuthName;
+ cacheKey += targetCRSCode;
+ cacheKey += (usePROJAlternativeGridNames ? '1' : '0');
+ cacheKey += (discardIfMissingGrid ? '1' : '0');
+ cacheKey += (discardSuperseded ? '1' : '0');
+
+ std::vector<operation::CoordinateOperationNNPtr> list;
+
+ if (d->context()->d->getCRSToCRSCoordOpFromCache(cacheKey, list)) {
+ return list;
+ }
+
+ // Look-up first for conversion which is the most precise.
+ std::string sql("SELECT conversion_auth_name, "
+ "geodetic_crs_auth_name, geodetic_crs_code FROM "
+ "projected_crs WHERE auth_name = ? AND code = ?");
+ auto params = ListOfParams{targetCRSAuthName, targetCRSCode};
+ auto res = d->run(sql, params);
+ if (!res.empty()) {
+ const auto &row = res.front();
+ bool ok = row[1] == sourceCRSAuthName && row[2] == sourceCRSCode;
+ if (ok && d->hasAuthorityRestriction()) {
+ ok = row[0] == d->authority();
+ }
+ if (ok) {
+ auto targetCRS = d->createFactory(targetCRSAuthName)
+ ->createProjectedCRS(targetCRSCode);
+ auto conv = targetCRS->derivingConversion();
+ list.emplace_back(conv);
+ d->context()->d->cache(cacheKey, list);
+ return list;
+ }
+ }
+ if (discardSuperseded) {
+ sql = "SELECT cov.auth_name, cov.code, cov.table_name, "
+ "ss.replacement_auth_name, ss.replacement_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 "
+ "LEFT JOIN supersession ss ON "
+ "ss.superseded_table_name = cov.table_name AND "
+ "ss.superseded_auth_name = cov.auth_name AND "
+ "ss.superseded_code = cov.code AND "
+ "ss.superseded_table_name = ss.replacement_table_name "
+ "WHERE source_crs_auth_name = ? AND source_crs_code = ? AND "
+ "target_crs_auth_name = ? AND target_crs_code = ? AND "
+ "cov.deprecated = 0";
+ } else {
+ sql = "SELECT cov.auth_name, cov.code, cov.table_name 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 = 0";
+ }
+ params = {sourceCRSAuthName, sourceCRSCode, targetCRSAuthName,
+ targetCRSCode};
+ if (d->hasAuthorityRestriction()) {
+ sql += " AND cov.auth_name = ?";
+ params.emplace_back(d->authority());
+ }
+ 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);
+ std::set<std::pair<std::string, std::string>> setTransf;
+ if (discardSuperseded) {
+ for (const auto &row : res) {
+ const auto &auth_name = row[0];
+ const auto &code = row[1];
+ setTransf.insert(
+ std::pair<std::string, std::string>(auth_name, code));
+ }
+ }
+ for (const auto &row : res) {
+ if (discardSuperseded) {
+ const auto &replacement_auth_name = row[3];
+ const auto &replacement_code = row[4];
+ if (!replacement_auth_name.empty() &&
+ setTransf.find(std::pair<std::string, std::string>(
+ replacement_auth_name, replacement_code)) !=
+ setTransf.end()) {
+ // Skip transformations that are superseded by others that got
+ // returned in the result set.
+ continue;
+ }
+ }
+
+ const auto &auth_name = row[0];
+ const auto &code = row[1];
+ const auto &table_name = row[2];
+ auto op = d->createFactory(auth_name)->createCoordinateOperation(
+ code, true, usePROJAlternativeGridNames, table_name);
+ if (!d->rejectOpDueToMissingGrid(op, discardIfMissingGrid)) {
+ list.emplace_back(op);
+ }
+ }
+ d->context()->d->cache(cacheKey, list);
+ 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 discardSuperseded Whether cordinate operations that are superseded
+ * (but not deprecated) 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,
+ bool discardSuperseded,
+ 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(
+ discardSuperseded
+ ?
+
+ "SELECT v1.table_name as table1, "
+ "v1.auth_name AS auth_name1, v1.code AS code1, "
+ "v1.accuracy AS accuracy1, "
+ "v2.table_name as table2, "
+ "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, "
+ "ss1.replacement_auth_name AS replacement_auth_name1, "
+ "ss1.replacement_code AS replacement_code1, "
+ "ss2.replacement_auth_name AS replacement_auth_name2, "
+ "ss2.replacement_code AS replacement_code2 "
+ "FROM coordinate_operation_view v1 "
+ "JOIN coordinate_operation_view v2 "
+ :
+
+ "SELECT v1.table_name as table1, "
+ "v1.auth_name AS auth_name1, v1.code AS code1, "
+ "v1.accuracy AS accuracy1, "
+ "v2.table_name as table2, "
+ "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 joinSupersession(
+ "LEFT JOIN supersession ss1 ON "
+ "ss1.superseded_table_name = v1.table_name AND "
+ "ss1.superseded_auth_name = v1.auth_name AND "
+ "ss1.superseded_code = v1.code AND "
+ "ss1.superseded_table_name = ss1.replacement_table_name "
+ "LEFT JOIN supersession ss2 ON "
+ "ss2.superseded_table_name = v2.table_name AND "
+ "ss2.superseded_auth_name = v2.auth_name AND "
+ "ss2.superseded_code = v2.code AND "
+ "ss2.superseded_table_name = ss2.replacement_table_name ");
+ const std::string joinArea(
+ (discardSuperseded ? joinSupersession : std::string()) +
+ "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 = ListOfParams{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 (d->hasAuthorityRestriction()) {
+ additionalWhere += "AND v1.auth_name = ? AND v2.auth_name = ? ";
+ params.emplace_back(d->authority());
+ params.emplace_back(d->authority());
+ }
+ 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);
+
+ const auto filterOutSuperseded = [](SQLResultSet &&resultSet) {
+ std::set<std::pair<std::string, std::string>> setTransf1;
+ std::set<std::pair<std::string, std::string>> setTransf2;
+ for (const auto &row : resultSet) {
+ // table1
+ const auto &auth_name1 = row[1];
+ const auto &code1 = row[2];
+ // accuracy1
+ // table2
+ const auto &auth_name2 = row[5];
+ const auto &code2 = row[6];
+ setTransf1.insert(
+ std::pair<std::string, std::string>(auth_name1, code1));
+ setTransf2.insert(
+ std::pair<std::string, std::string>(auth_name2, code2));
+ }
+ SQLResultSet filteredResultSet;
+ for (const auto &row : resultSet) {
+ const auto &replacement_auth_name1 = row[16];
+ const auto &replacement_code1 = row[17];
+ const auto &replacement_auth_name2 = row[18];
+ const auto &replacement_code2 = row[19];
+ if (!replacement_auth_name1.empty() &&
+ setTransf1.find(std::pair<std::string, std::string>(
+ replacement_auth_name1, replacement_code1)) !=
+ setTransf1.end()) {
+ // Skip transformations that are superseded by others that got
+ // returned in the result set.
+ continue;
+ }
+ if (!replacement_auth_name2.empty() &&
+ setTransf2.find(std::pair<std::string, std::string>(
+ replacement_auth_name2, replacement_code2)) !=
+ setTransf2.end()) {
+ // Skip transformations that are superseded by others that got
+ // returned in the result set.
+ continue;
+ }
+ filteredResultSet.emplace_back(row);
+ }
+ return filteredResultSet;
+ };
+
+ if (discardSuperseded) {
+ res = filterOutSuperseded(std::move(res));
+ }
+ for (const auto &row : res) {
+ const auto &table1 = row[0];
+ const auto &auth_name1 = row[1];
+ const auto &code1 = row[2];
+ // const auto &accuracy1 = row[3];
+ const auto &table2 = row[4];
+ const auto &auth_name2 = row[5];
+ const auto &code2 = row[6];
+ // const auto &accuracy2 = row[7];
+ auto op1 = d->createFactory(auth_name1)
+ ->createCoordinateOperation(
+ code1, true, usePROJAlternativeGridNames, table1);
+ if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
+ targetCRSAuthName, targetCRSCode)) {
+ continue;
+ }
+ auto op2 = d->createFactory(auth_name2)
+ ->createCoordinateOperation(
+ code2, true, usePROJAlternativeGridNames, table2);
+ 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);
+ if (discardSuperseded) {
+ res = filterOutSuperseded(std::move(res));
+ }
+ for (const auto &row : res) {
+ const auto &table1 = row[0];
+ const auto &auth_name1 = row[1];
+ const auto &code1 = row[2];
+ // const auto &accuracy1 = row[3];
+ const auto &table2 = row[4];
+ const auto &auth_name2 = row[5];
+ const auto &code2 = row[6];
+ // const auto &accuracy2 = row[7];
+ auto op1 = d->createFactory(auth_name1)
+ ->createCoordinateOperation(
+ code1, true, usePROJAlternativeGridNames, table1);
+ if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
+ targetCRSAuthName, targetCRSCode)) {
+ continue;
+ }
+ auto op2 = d->createFactory(auth_name2)
+ ->createCoordinateOperation(
+ code2, true, usePROJAlternativeGridNames, table2);
+ 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);
+ if (discardSuperseded) {
+ res = filterOutSuperseded(std::move(res));
+ }
+ for (const auto &row : res) {
+ const auto &table1 = row[0];
+ const auto &auth_name1 = row[1];
+ const auto &code1 = row[2];
+ // const auto &accuracy1 = row[3];
+ const auto &table2 = row[4];
+ const auto &auth_name2 = row[5];
+ const auto &code2 = row[6];
+ // const auto &accuracy2 = row[7];
+ auto op1 = d->createFactory(auth_name1)
+ ->createCoordinateOperation(
+ code1, true, usePROJAlternativeGridNames, table1);
+ if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
+ targetCRSAuthName, targetCRSCode)) {
+ continue;
+ }
+ auto op2 = d->createFactory(auth_name2)
+ ->createCoordinateOperation(
+ code2, true, usePROJAlternativeGridNames, table2);
+ 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);
+ if (discardSuperseded) {
+ res = filterOutSuperseded(std::move(res));
+ }
+ for (const auto &row : res) {
+ const auto &table1 = row[0];
+ const auto &auth_name1 = row[1];
+ const auto &code1 = row[2];
+ // const auto &accuracy1 = row[3];
+ const auto &table2 = row[4];
+ const auto &auth_name2 = row[5];
+ const auto &code2 = row[6];
+ // const auto &accuracy2 = row[7];
+ auto op1 = d->createFactory(auth_name1)
+ ->createCoordinateOperation(
+ code1, true, usePROJAlternativeGridNames, table1);
+ if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
+ targetCRSAuthName, targetCRSCode)) {
+ continue;
+ }
+ auto op2 = d->createFactory(auth_name2)
+ ->createCoordinateOperation(
+ code2, true, usePROJAlternativeGridNames, table2);
+ 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 = 0";
+ }
+
+ auto res = d->run(sql, {d->authority()});
+ 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", d->authority(),
+ code);
+ }
+ return res.front()[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 tryEquivalentNameSpelling whether the comparison of aliasedName with
+ * the alt_name column of the alis_name table should be done with using
+ * metadata::Identifier::isEquivalentName() rather than strict string
+ * comparison;
+ * @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, bool tryEquivalentNameSpelling,
+ std::string &outTableName, std::string &outAuthName,
+ std::string &outCode) const {
+
+ if (tryEquivalentNameSpelling) {
+ std::string sql(
+ "SELECT table_name, auth_name, code, alt_name FROM alias_name");
+ ListOfParams params;
+ if (!tableName.empty()) {
+ sql += " WHERE table_name = ?";
+ params.push_back(tableName);
+ }
+ if (!source.empty()) {
+ if (!tableName.empty()) {
+ sql += " AND ";
+ } else {
+ sql += " WHERE ";
+ }
+ sql += "source = ?";
+ params.push_back(source);
+ }
+ auto res = d->run(sql, params);
+ if (res.empty()) {
+ return std::string();
+ }
+ for (const auto &row : res) {
+ const auto &alt_name = row[3];
+ if (metadata::Identifier::isEquivalentName(alt_name.c_str(),
+ aliasedName.c_str())) {
+ outTableName = row[0];
+ outAuthName = row[1];
+ outCode = row[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.front()[0];
+ }
+ }
+ return std::string();
+ } else {
+ std::string sql(
+ "SELECT table_name, auth_name, code FROM alias_name WHERE "
+ "alt_name = ?");
+ ListOfParams 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();
+ }
+ const auto &row = res.front();
+ outTableName = row[0];
+ outAuthName = row[1];
+ outCode = row[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.front()[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) const {
+
+ 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 ");
+ ListOfParams params{deprecated ? 1.0 : 0.0};
+ if (!approximateMatch) {
+ sql += "name LIKE ? AND ";
+ params.push_back(searchedNameWithoutDeprecated);
+ }
+ if (d->hasAuthorityRestriction()) {
+ sql += " auth_name = ? AND ";
+ params.emplace_back(d->authority());
+ }
+
+ if (allowedObjectTypes.empty()) {
+ sql += "table_name IN ("
+ "'prime_meridian','ellipsoid','geodetic_datum',"
+ "'vertical_datum','geodetic_crs','projected_crs',"
+ "'vertical_crs','compound_crs','conversion',"
+ "'helmert_transformation','grid_transformation',"
+ "'other_transformation','concatenated_operation'"
+ ")";
+ } else {
+ std::string tableNameList;
+ std::string otherConditions;
+ for (const auto type : allowedObjectTypes) {
+ switch (type) {
+ case ObjectType::PRIME_MERIDIAN:
+ addToListString(tableNameList, "'prime_meridian'");
+ break;
+ case ObjectType::ELLIPSOID:
+ addToListString(tableNameList, "'ellipsoid'");
+ break;
+ case ObjectType::DATUM:
+ addToListString(tableNameList,
+ "'geodetic_datum','vertical_datum'");
+ break;
+ case ObjectType::GEODETIC_REFERENCE_FRAME:
+ addToListString(tableNameList, "'geodetic_datum'");
+ break;
+ case ObjectType::VERTICAL_REFERENCE_FRAME:
+ addToListString(tableNameList, "'vertical_datum'");
+ break;
+ case ObjectType::CRS:
+ addToListString(tableNameList, "'geodetic_crs','projected_crs',"
+ "'vertical_crs','compound_crs'");
+ break;
+ case ObjectType::GEODETIC_CRS:
+ addToListString(tableNameList, "'geodetic_crs'");
+ break;
+ case ObjectType::GEOCENTRIC_CRS:
+ addToListStringWithOR(otherConditions,
+ "(table_name = " GEOCENTRIC " 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;
+
+ // Querying geodetic datum is a super hot path when importing from WKT1
+ // so cache results.
+ if (allowedObjectTypes.size() == 1 &&
+ allowedObjectTypes[0] == ObjectType::GEODETIC_REFERENCE_FRAME &&
+ approximateMatch && d->authority().empty()) {
+ auto &mapCanonicalizeGRFName =
+ d->context()->getPrivate()->getMapCanonicalizeGRFName();
+ if (mapCanonicalizeGRFName.empty()) {
+ auto sqlRes = d->run(sql, params);
+ for (const auto &row : sqlRes) {
+ const auto &name = row[3];
+ const auto canonicalizedName(
+ metadata::Identifier::canonicalizeName(name));
+ mapCanonicalizeGRFName[canonicalizedName].push_back(row);
+ }
+ }
+ auto iter = mapCanonicalizeGRFName.find(canonicalizedSearchedName);
+ if (iter != mapCanonicalizeGRFName.end()) {
+ const auto &listOfRow = iter->second;
+ for (const auto &row : listOfRow) {
+ const auto &auth_name = row[1];
+ const auto &code = row[2];
+ auto factory = d->createFactory(auth_name);
+ res.emplace_back(factory->createGeodeticDatum(code));
+ if (limitResultCount > 0 && res.size() == limitResultCount) {
+ break;
+ }
+ }
+ } else {
+ for (const auto &pair : mapCanonicalizeGRFName) {
+ const auto &listOfRow = pair.second;
+ for (const auto &row : listOfRow) {
+ const auto &name = row[3];
+ if (approximateMatch) {
+ bool match =
+ ci_find(name, searchedNameWithoutDeprecated) !=
+ std::string::npos;
+ if (!match) {
+ const auto &canonicalizedName(pair.first);
+ match = ci_find(canonicalizedName,
+ canonicalizedSearchedName) !=
+ std::string::npos;
+ }
+ if (!match) {
+ continue;
+ }
+ }
+
+ const auto &auth_name = row[1];
+ const auto &code = row[2];
+ auto factory = d->createFactory(auth_name);
+ res.emplace_back(factory->createGeodeticDatum(code));
+ if (limitResultCount > 0 &&
+ res.size() == limitResultCount) {
+ break;
+ }
+ }
+ if (limitResultCount > 0 && res.size() == limitResultCount) {
+ break;
+ }
+ }
+ }
+ } else {
+ 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;
+ }
+ }
+ }
+
+ if (res.empty() && !deprecated) {
+ return createObjectsFromName(searchedName + " (deprecated)",
+ allowedObjectTypes, approximateMatch,
+ limitResultCount);
+ }
+
+ 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;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a list of area of use from their name
+ *
+ * @param name Searched name.
+ * @param approximateMatch Whether approximate name identification is allowed.
+ * @return list of (auth_name, code) of matched objects.
+ * @throw FactoryException
+ */
+std::list<std::pair<std::string, std::string>>
+AuthorityFactory::listAreaOfUseFromName(const std::string &name,
+ bool approximateMatch) const {
+ std::string sql(
+ "SELECT auth_name, code FROM area WHERE deprecated = 0 AND ");
+ ListOfParams params;
+ if (d->hasAuthorityRestriction()) {
+ sql += " auth_name = ? AND ";
+ params.emplace_back(d->authority());
+ }
+ sql += "name LIKE ?";
+ if (!approximateMatch) {
+ params.push_back(name);
+ } else {
+ params.push_back('%' + name + '%');
+ }
+ auto sqlRes = d->run(sql, params);
+ std::list<std::pair<std::string, std::string>> res;
+ for (const auto &row : sqlRes) {
+ res.emplace_back(row[0], row[1]);
+ }
+ 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))))");
+ ListOfParams 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");
+ ListOfParams params{datum_auth_name, datum_code};
+ if (d->hasAuthorityRestriction()) {
+ sql += " AND auth_name = ?";
+ params.emplace_back(d->authority());
+ }
+ 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");
+ ListOfParams params{ellipsoid_auth_name, ellipsoid_code};
+ if (d->hasAuthorityRestriction()) {
+ sql += " AND geodetic_crs.auth_name = ?";
+ params.emplace_back(d->authority());
+ }
+ 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, ListOfParams &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 ");
+ ListOfParams 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 (d->hasAuthorityRestriction()) {
+ sql += " AND projected_crs.auth_name = ?";
+ params.emplace_back(d->authority());
+ }
+
+ 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 (d->hasAuthorityRestriction()) {
+ sql += " AND auth_name = ?";
+ params.emplace_back(d->authority());
+ }
+
+ 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 ");
+ ListOfParams 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 (d->hasAuthorityRestriction()) {
+ if (addAnd) {
+ sql += " AND ";
+ }
+ sql += "auth_name = ?";
+ params.emplace_back(d->authority());
+ }
+
+ 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/iso19111/internal.cpp b/src/iso19111/internal.cpp
new file mode 100644
index 00000000..c43605d1
--- /dev/null
+++ b/src/iso19111/internal.cpp
@@ -0,0 +1,374 @@
+/******************************************************************************
+ *
+ * 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 <cstdint>
+#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>
+
+#include "sqlite3.h"
+
+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);
+}
+
+// ---------------------------------------------------------------------------
+
+bool ci_less(const std::string &a, const std::string &b) noexcept {
+#ifdef _MSC_VER
+ return _stricmp(a.c_str(), b.c_str()) < 0;
+#else
+ return strcasecmp(a.c_str(), b.c_str()) < 0;
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+/**
+ * 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) {
+
+ const auto s_size = s.size();
+ // Fast path
+ if (s_size > 0 && s_size < 15) {
+ std::int64_t acc = 0;
+ std::int64_t div = 1;
+ bool afterDot = false;
+ size_t i = 0;
+ if (s[0] == '-') {
+ ++i;
+ div = -1;
+ } else if (s[0] == '+') {
+ ++i;
+ }
+ for (; i < s_size; ++i) {
+ const auto ch = s[i];
+ if (ch >= '0' && ch <= '9') {
+ acc = acc * 10 + ch - '0';
+ if (afterDot) {
+ div *= 10;
+ }
+ } else if (ch == '.') {
+ afterDot = true;
+ } else {
+ div = 0;
+ }
+ }
+ if (div) {
+ return static_cast<double>(acc) / div;
+ }
+ }
+
+ 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;
+}
+
+// ---------------------------------------------------------------------------
+
+#ifdef _WIN32
+
+// For some reason, sqlite3_snprintf() in the sqlite3 builds used on AppVeyor
+// doesn't round identically to the Unix builds, and thus breaks a number of
+// unit test. So to avoid this, use the stdlib formatting
+
+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;
+}
+
+#else
+
+std::string toString(int val) {
+ // use sqlite3 API that is slighly faster than std::ostringstream
+ // with forcing the C locale. sqlite3_snprintf() emulates a C locale.
+ constexpr int BUF_SIZE = 16;
+ char szBuffer[BUF_SIZE];
+ sqlite3_snprintf(BUF_SIZE, szBuffer, "%d", val);
+ return szBuffer;
+}
+
+std::string toString(double val, int precision) {
+ // use sqlite3 API that is slighly faster than std::ostringstream
+ // with forcing the C locale. sqlite3_snprintf() emulates a C locale.
+ constexpr int BUF_SIZE = 32;
+ char szBuffer[BUF_SIZE];
+ sqlite3_snprintf(BUF_SIZE, szBuffer, "%.*g", precision, val);
+ if (precision == 15 && strstr(szBuffer, "9999999999")) {
+ sqlite3_snprintf(BUF_SIZE, szBuffer, "%.14g", val);
+ }
+ return szBuffer;
+}
+
+#endif
+
+// ---------------------------------------------------------------------------
+
+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/iso19111/io.cpp b/src/iso19111/io.cpp
new file mode 100644
index 00000000..fe3680fb
--- /dev/null
+++ b/src/iso19111/io.cpp
@@ -0,0 +1,7501 @@
+/******************************************************************************
+ *
+ * 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"
+
+#include "pj_wkt1_parser.h"
+#include "pj_wkt2_parser.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;
+ OutputAxisRule outputAxis_ = WKTFormatter::OutputAxisRule::YES;
+ };
+ 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.
+ */
+WKTFormatter &
+WKTFormatter::setOutputAxis(OutputAxisRule 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;
+ d->params_.outputAxis_ =
+ WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE;
+ 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_ = WKTFormatter::OutputAxisRule::NO;
+ 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 inline std::string normalizeSerializedString(const std::string &in) {
+ return in;
+}
+#endif
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::add(double number, int precision) {
+ d->startNewChild();
+ if (number == 0.0) {
+ if (d->params_.useESRIDialect_) {
+ d->result_ += "0.0";
+ } else {
+ d->result_ += '0';
+ }
+ } else {
+ 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();
+}
+
+// ---------------------------------------------------------------------------
+
+WKTFormatter::OutputAxisRule 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;
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::ingestWKTNode(const WKTNodeNNPtr &node) {
+ startNode(node->value(), true);
+ for (const auto &child : node->children()) {
+ if (!child->children().empty()) {
+ ingestWKTNode(child);
+ } else {
+ add(child->value());
+ }
+ }
+ endNode();
+}
+
+#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] == ',') {
+ ++i;
+ 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::list<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,
+ const UnitOfMeasure::Type &unitType,
+ 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);
+
+ static void addExtensionProj4ToProp(const WKTNode::Private *nodeP,
+ PropertyMap &props);
+
+ ConversionNNPtr buildConversion(const WKTNodeNNPtr &node,
+ const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit);
+
+ static bool hasWebMercPROJ4String(const WKTNodeNNPtr &projCRSNode,
+ const WKTNodeNNPtr &projectionNode);
+
+ static std::string projectionGetParameter(const WKTNodeNNPtr &projCRSNode,
+ const char *paramName);
+
+ 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::list<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]));
+ if (ends_with(name, " (deprecated)")) {
+ name.resize(name.size() - strlen(" (deprecated)"));
+ properties->set(common::IdentifiedObject::DEPRECATED_KEY, true);
+ }
+
+ 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", false, 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", false, 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::TEMPORALQUANTITY);
+ 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 &
+fixupPrimeMeridan(const EllipsoidNNPtr &ellipsoid,
+ const PrimeMeridianNNPtr &pm) {
+ return (ellipsoid->celestialBody() != Ellipsoid::EARTH &&
+ pm.get() == PrimeMeridian::GREENWICH.get())
+ ? PrimeMeridian::REFERENCE_MERIDIAN
+ : 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", false, 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);
+ }
+ } else if (name.find('_') != std::string::npos) {
+ // Likely coming from WKT1
+ if (dbContext_) {
+ auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
+ std::string());
+ auto res = authFactory->createObjectsFromName(
+ name, {AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME},
+ true, 1);
+ bool foundDatumName = false;
+ if (!res.empty()) {
+ const auto &refDatum = res.front();
+ if (metadata::Identifier::isEquivalentName(
+ name.c_str(), refDatum->nameStr().c_str())) {
+ foundDatumName = true;
+ properties.set(IdentifiedObject::NAME_KEY,
+ refDatum->nameStr());
+ if (!properties.get(Identifier::CODESPACE_KEY) &&
+ refDatum->identifiers().size() == 1) {
+ const auto &id = refDatum->identifiers()[0];
+ auto identifiers = ArrayOfBaseObject::create();
+ identifiers->add(Identifier::create(
+ id->code(), PropertyMap()
+ .set(Identifier::CODESPACE_KEY,
+ *id->codeSpace())
+ .set(Identifier::AUTHORITY_KEY,
+ *id->codeSpace())));
+ properties.set(IdentifiedObject::IDENTIFIERS_KEY,
+ identifiers);
+ }
+ }
+ } else {
+ // Get official name from database if AUTHORITY is present
+ auto &idNode = nodeP->lookForChild(WKTConstants::AUTHORITY);
+ if (!isNull(idNode)) {
+ try {
+ auto id = buildId(idNode);
+ auto authFactory2 = AuthorityFactory::create(
+ NN_NO_CHECK(dbContext_), *id->codeSpace());
+ auto dbDatum =
+ authFactory2->createGeodeticDatum(id->code());
+ foundDatumName = true;
+ properties.set(IdentifiedObject::NAME_KEY,
+ dbDatum->nameStr());
+ } catch (const std::exception &) {
+ }
+ }
+ }
+
+ if (!foundDatumName) {
+ std::string outTableName;
+ std::string authNameFromAlias;
+ std::string codeFromAlias;
+ auto officialName = authFactory->getOfficialNameFromAlias(
+ name, "geodetic_datum", std::string(), true, outTableName,
+ authNameFromAlias, codeFromAlias);
+ if (!officialName.empty()) {
+ properties.set(IdentifiedObject::NAME_KEY, officialName);
+ }
+ }
+ }
+ }
+
+ 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);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+PROJ_NO_RETURN static void ThrowParsingExceptionMissingUNIT() {
+ throw ParsingException("buildCS: missing UNIT");
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateSystemAxisNNPtr
+WKTParser::Private::buildAxis(const WKTNodeNNPtr &node,
+ const UnitOfMeasure &unitIn,
+ const UnitOfMeasure::Type &unitType,
+ 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() ||
+ dirString == AxisDirectionWKT1::OTHER.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;
+ if (unit == UnitOfMeasure::NONE &&
+ unitType != UnitOfMeasure::Type::NONE &&
+ unitType != UnitOfMeasure::Type::TIME) {
+ ThrowParsingExceptionMissingUNIT();
+ }
+ }
+
+ 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);
+}
+
+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");
+ }
+
+ const auto unitType =
+ 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;
+ UnitOfMeasure unit = buildUnitInSubNode(parentNode, unitType);
+
+ std::vector<CoordinateSystemAxisNNPtr> axisList;
+ for (int i = 0; i < axisCount; i++) {
+ axisList.emplace_back(
+ buildAxis(parentNode->GP()->lookForChild(WKTConstants::AXIS, i),
+ unit, unitType, 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);
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTParser::Private::addExtensionProj4ToProp(const WKTNode::Private *nodeP,
+ PropertyMap &props) {
+ auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
+ const auto &extensionChildren = extensionNode->GP()->children();
+ if (extensionChildren.size() == 2) {
+ if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
+ props.set("EXTENSION_PROJ4", stripQuotes(extensionChildren[1]));
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+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 props = buildProperties(node);
+ addExtensionProj4ToProp(nodeP, props);
+
+ // No explicit AXIS node ? (WKT1)
+ if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) {
+ props.set("IMPLICIT_CS", true);
+ }
+
+ 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(props, 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(props, 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(props, 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 ||
+ ci_find(paramName, "heading") != 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["PROJ4", "+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);
+ }
+
+ struct ci_less_struct {
+ bool operator()(const std::string &lhs, const std::string &rhs) const
+ noexcept {
+ return ci_less(lhs, rhs);
+ }
+ };
+
+ // Build a map of present parameters
+ std::map<std::string, std::string, ci_less_struct> 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 (ci_equal(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;
+ }
+ const auto &esriParamName = iter->second;
+ auto iter2 = mapParamNameToValue.find(esriParamName);
+ auto mapParamNameToValueEnd = mapParamNameToValue.end();
+ if (iter2 == mapParamNameToValueEnd) {
+ // In case we don't find a direct match, try the aliases
+ for (iter2 = mapParamNameToValue.begin();
+ iter2 != mapParamNameToValueEnd; ++iter2) {
+ if (areEquivalentParameters(iter2->first, esriParamName)) {
+ break;
+ }
+ }
+ if (iter2 == mapParamNameToValueEnd) {
+ 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);
+}
+
+// ---------------------------------------------------------------------------
+
+std::string
+WKTParser::Private::projectionGetParameter(const WKTNodeNNPtr &projCRSNode,
+ const char *paramName) {
+ 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 &&
+ metadata::Identifier::isEquivalentName(
+ stripQuotes(childNodeChildren[0]).c_str(), paramName)) {
+ return childNodeChildren[1]->GP()->value();
+ }
+ }
+ }
+ return std::string();
+}
+
+// ---------------------------------------------------------------------------
+
+ConversionNNPtr WKTParser::Private::buildProjectionStandard(
+ const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode,
+ const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit) {
+ 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();
+
+ bool gdal_3026_hack = false;
+ if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
+ "Mercator_1SP") &&
+ projectionGetParameter(projCRSNode, "center_latitude").empty()) {
+
+ // Hack for https://trac.osgeo.org/gdal/ticket/3026
+ std::string lat0(
+ projectionGetParameter(projCRSNode, "latitude_of_origin"));
+ if (!lat0.empty() && lat0 != "0" && lat0 != "0.0") {
+ wkt1ProjectionName = "Mercator_2SP";
+ gdal_3026_hack = true;
+ } else {
+ // 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>(
+ tolower(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 &&
+ 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()));
+ }
+
+ 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()));
+ }
+
+ 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, UnitOfMeasure::Type::LINEAR, false,
+ 1)->direction() == &AxisDirection::SOUTH &&
+ &buildAxis(
+ projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 1),
+ defaultLinearUnit, UnitOfMeasure::Type::LINEAR, 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);
+ }
+ const auto &paramValue = childNodeChildren[1]->GP()->value();
+
+ PropertyMap propertiesParameter;
+ const std::string wkt1ParameterName(
+ stripQuotes(childNodeChildren[0]));
+ std::string parameterName(wkt1ParameterName);
+ if (gdal_3026_hack) {
+ if (ci_equal(parameterName, "latitude_of_origin")) {
+ parameterName = "standard_parallel_1";
+ } else if (ci_equal(parameterName, "scale_factor") &&
+ paramValue == "1") {
+ continue;
+ }
+ }
+ const auto *paramMapping =
+ mapping ? getMappingFromWKT1(mapping, parameterName) : nullptr;
+ if (mapping &&
+ mapping->epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
+ ci_equal(parameterName, "latitude_of_origin")) {
+ parameterName = EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN;
+ propertiesParameter.set(
+ Identifier::CODE_KEY,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
+ propertiesParameter.set(Identifier::CODESPACE_KEY,
+ Identifier::EPSG);
+ } else 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));
+ 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();
+}
+
+// ---------------------------------------------------------------------------
+
+static ProjectedCRSNNPtr createPseudoMercator(const PropertyMap &props) {
+ auto conversion = Conversion::createPopularVisualisationPseudoMercator(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(0),
+ Angle(0), Length(0), Length(0));
+ return ProjectedCRS::create(
+ props, GeographicCRS::EPSG_4326, conversion,
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+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);
+ }
+
+ 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 props = buildProperties(node);
+
+ const std::string projCRSName = stripQuotes(nodeP->children()[0]);
+ if (esriStyle_ && dbContext_) {
+ // 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", false, outTableName,
+ authNameFromAlias, codeFromAlias);
+ if (!officialName.empty()) {
+ props.set(IdentifiedObject::NAME_KEY, officialName);
+ }
+ }
+ }
+
+ if (isNull(conversionNode) && hasWebMercPROJ4String(node, projectionNode)) {
+ toWGS84Parameters_.clear();
+ return createPseudoMercator(props);
+ }
+
+ // WGS_84_Pseudo_Mercator: Particular case for corrupted ESRI WKT generated
+ // by older GDAL versions
+ // https://trac.osgeo.org/gdal/changeset/30732
+ // WGS_1984_Web_Mercator: deprecated ESRI:102113
+ if (metadata::Identifier::isEquivalentName(projCRSName.c_str(),
+ "WGS_84_Pseudo_Mercator") ||
+ metadata::Identifier::isEquivalentName(projCRSName.c_str(),
+ "WGS_1984_Web_Mercator")) {
+ toWGS84Parameters_.clear();
+ return createPseudoMercator(props);
+ }
+
+ 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);
+
+ // No explicit AXIS node ? (WKT1)
+ if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) {
+ props.set("IMPLICIT_CS", true);
+ }
+
+ 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 (methodCode ==
+ EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A ||
+ methodCode ==
+ EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) {
+ // 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.
+ // Same for WKT1_GDAL
+ const double lat0 = conversion->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ if (std::fabs(lat0 - 90) < 1e-10) {
+ cartesianCS =
+ CartesianCS::createNorthPoleEastingSouthNorthingSouth(
+ linearUnit)
+ .as_nullable();
+ } else if (std::fabs(lat0 - -90) < 1e-10) {
+ cartesianCS =
+ CartesianCS::createSouthPoleEastingNorthNorthingNorth(
+ linearUnit)
+ .as_nullable();
+ }
+ } else if (methodCode ==
+ EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
+ const double lat_ts = conversion->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL,
+ common::UnitOfMeasure::DEGREE);
+ if (lat_ts > 0) {
+ cartesianCS =
+ CartesianCS::createNorthPoleEastingSouthNorthingSouth(
+ linearUnit)
+ .as_nullable();
+ } else if (lat_ts < 0) {
+ cartesianCS =
+ CartesianCS::createSouthPoleEastingNorthNorthingNorth(
+ linearUnit)
+ .as_nullable();
+ }
+ } else if (methodCode ==
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
+ cartesianCS =
+ CartesianCS::createWestingSouthing(linearUnit).as_nullable();
+ }
+ }
+ if (!cartesianCS) {
+ ThrowNotExpectedCSType("Cartesian");
+ }
+
+ addExtensionProj4ToProp(nodeP, props);
+
+ 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>
+ *
+ * @param text One of the above mentionned text format
+ * @param dbContext Database context, or nullptr (in which case database
+ * lookups will not work)
+ * @param usePROJ4InitRules When set to true,
+ * init=epsg:XXXX syntax will be allowed and will be interpreted according to
+ * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
+ * order and will expect/output coordinates in radians. ProjectedCRS will have
+ * easting, northing axis order (except the ones with Transverse Mercator South
+ * Orientated projection). In that mode, the epsg:XXXX syntax will be also
+ * interprated the same way.
+ * @throw ParsingException
+ */
+BaseObjectNNPtr createFromUserInput(const std::string &text,
+ const DatabaseContextPtr &dbContext,
+ bool usePROJ4InitRules) {
+
+ for (const auto &wktConstants : WKTConstants::constants()) {
+ if (ci_starts_with(text, wktConstants)) {
+ return WKTParser()
+ .attachDatabaseContext(dbContext)
+ .setStrict(false)
+ .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)
+ .setUsePROJ4InitRules(usePROJ4InitRules)
+ .createFromPROJString(text);
+ }
+
+ auto tokens = split(text, ':');
+ if (tokens.size() == 2) {
+ if (!dbContext) {
+ throw ParsingException("no database context specified");
+ }
+ DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext));
+ const auto &authName = tokens[0];
+ const auto &code = tokens[1];
+ auto factory = AuthorityFactory::create(dbContextNNPtr, authName);
+ try {
+ return factory->createCoordinateReferenceSystem(code);
+ } catch (...) {
+ const auto authorities = dbContextNNPtr->getAuthorities();
+ for (const auto &authCandidate : authorities) {
+ if (ci_equal(authCandidate, authName)) {
+ return AuthorityFactory::create(dbContextNNPtr,
+ authCandidate)
+ ->createCoordinateReferenceSystem(code);
+ }
+ }
+ throw;
+ }
+ }
+
+ // 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.
+ *
+ * By default, validation is strict (to the extent of the checks that are
+ * actually implemented. Currently only WKT1 strict grammar is checked), and
+ * any issue detected will cause an exception to be thrown, unless
+ * setStrict(false) is called priorly.
+ *
+ * In non-strict mode, non-fatal issues will be recovered and simply listed
+ * in warningList(). This does not prevent more severe errors to cause an
+ * exception to be thrown.
+ *
+ * @throw ParsingException
+ */
+BaseObjectNNPtr WKTParser::createFromWKT(const std::string &wkt) {
+ WKTNodeNNPtr root = WKTNode::createFrom(wkt);
+ auto obj = d->build(root);
+
+ const auto dialect = guessDialect(wkt);
+ if (dialect == WKTGuessedDialect::WKT1_GDAL ||
+ dialect == WKTGuessedDialect::WKT1_ESRI) {
+ auto errorMsg = pj_wkt1_parse(wkt);
+ if (!errorMsg.empty()) {
+ d->emitRecoverableAssertion(errorMsg);
+ }
+ } else if (dialect == WKTGuessedDialect::WKT2_2015 ||
+ dialect == WKTGuessedDialect::WKT2_2018) {
+ auto errorMsg = pj_wkt2_parse(wkt);
+ if (!errorMsg.empty()) {
+ d->emitRecoverableAssertion(errorMsg);
+ }
+ }
+
+ return obj;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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);
+ if (formatter->getAddNoDefs() &&
+ formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4 &&
+ dynamic_cast<const crs::CRS *>(this)) {
+ if (!formatter->hasParam("no_defs")) {
+ formatter->addParam("no_defs");
+ }
+ }
+ 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;
+ bool useETMercForTMercSet_ = false;
+ bool addNoDefs_ = true;
+ bool coordOperationOptimizations_ = 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;
+ d->useETMercForTMercSet_ = true;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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 if (d->coordOperationOptimizations_ &&
+ step.name == "unitconvert" && paramCount == 2 &&
+ step.paramValues[0].keyEquals("xy_in") &&
+ step.paramValues[1].keyEquals("xy_out") &&
+ step.paramValues[0].value == step.paramValues[1].value) {
+ 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;
+ }
+
+ // axisswap order=2,1, unitconvert, axisswap order=2,1 -> can
+ // suppress axisswap
+ if (i + 1 < d->steps_.size() && prevStep.name == "axisswap" &&
+ curStep.name == "unitconvert" && prevStepParamCount == 1 &&
+ prevStep.paramValues[0].equals("order", "2,1")) {
+ auto iterNext = iterCur;
+ ++iterNext;
+ auto &nextStep = *iterNext;
+ if (nextStep.name == "axisswap" &&
+ nextStep.paramValues.size() == 1 &&
+ nextStep.paramValues[0].equals("order", "2,1")) {
+ d->steps_.erase(iterPrev);
+ d->steps_.erase(iterNext);
+ 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(bool &settingSetOut) const {
+ settingSetOut = d->useETMercForTMercSet_;
+ return d->useETMercForTMerc_;
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::setCoordinateOperationOptimizations(bool enable) {
+ d->coordOperationOptimizations_ = enable;
+}
+
+// ---------------------------------------------------------------------------
+
+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=")) {
+ auto stepName = word.substr(strlen("proj="));
+ if (prevWasInit) {
+ steps.back() = Step();
+ prevWasInit = false;
+ } else {
+ 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_.end(), 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;
+}
+
+// ---------------------------------------------------------------------------
+
+bool PROJStringFormatter::hasParam(const char *paramName) const {
+ if (!d->steps_.empty()) {
+ for (const auto &paramValue : d->steps_.back().paramValues) {
+ if (paramValue.keyEquals(paramName)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::addNoDefs(bool b) { d->addNoDefs_ = b; }
+
+// ---------------------------------------------------------------------------
+
+bool PROJStringFormatter::getAddNoDefs() const { return d->addNoDefs_; }
+
+// ---------------------------------------------------------------------------
+
+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_{};
+ bool usePROJ4InitRules_ = false;
+ std::vector<std::string> warningList_{};
+
+ std::string projString_{};
+
+ 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
+ const std::string &getParamValueK(const Step &step) {
+ for (const auto &pair : step.paramValues) {
+ if (ci_equal(pair.key, "k") || ci_equal(pair.key, "k_0")) {
+ 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 Set how init=epsg:XXXX syntax should be interpreted.
+ *
+ * @param enable When set to true,
+ * init=epsg:XXXX syntax will be allowed and will be interpreted according to
+ * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
+ * order and will expect/output coordinates in radians. ProjectedCRS will have
+ * easting, northing axis order (except the ones with Transverse Mercator South
+ * Orientated projection).
+ */
+PROJStringParser &PROJStringParser::setUsePROJ4InitRules(bool enable) {
+ d->usePROJ4InitRules_ = enable;
+ 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 isGeographicStep(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()) {
+ char *end;
+ double pmValue = dmstor(pmStr.c_str(), &end) * RAD_TO_DEG;
+ if (pmValue != HUGE_VAL && *end == '\0') {
+ pm = PrimeMeridian::create(createMapWithUnknownName(),
+ Angle(pmValue));
+ } else {
+ 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);
+ 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 &RStr = getParamValue(step, "R");
+ const auto &aStr = getParamValue(step, "a");
+ const auto &bStr = getParamValue(step, "b");
+ const auto &rfStr = getParamValue(step, "rf");
+ const auto &fStr = getParamValue(step, "f");
+ const auto &esStr = getParamValue(step, "es");
+ const auto &eStr = getParamValue(step, "e");
+ double a = -1.0;
+ double b = -1.0;
+ double rf = -1.0;
+ const util::optional<std::string> optionalEmptyString{};
+ const bool numericParamPresent =
+ !RStr.empty() || !aStr.empty() || !bStr.empty() || !rfStr.empty() ||
+ !fStr.empty() || !esStr.empty() || !eStr.empty();
+
+ PrimeMeridianNNPtr pm(buildPrimeMeridian(step));
+ PropertyMap grfMap;
+
+ // It is arguable that we allow the prime meridian of a datum defined by
+ // its name to be overriden, but this is found at least in a regression test
+ // of GDAL. So let's keep the ellipsoid part of the datum in that case and
+ // use the specified prime meridian.
+ const auto overridePmIfNeeded =
+ [&pm](const GeodeticReferenceFrameNNPtr &grf) {
+ if (pm->_isEquivalentTo(PrimeMeridian::GREENWICH.get())) {
+ return grf;
+ } else {
+ return GeodeticReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "Unknown based on " +
+ grf->ellipsoid()->nameStr() +
+ " ellipsoid"),
+ grf->ellipsoid(), grf->anchorDefinition(), pm);
+ }
+ };
+
+ // R take precedence
+ 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 (!datumStr.empty()) {
+ auto l_datum = [&datumStr, &overridePmIfNeeded, &grfMap,
+ &optionalEmptyString, &pm]() {
+ if (datumStr == "WGS84") {
+ return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
+ } else if (datumStr == "NAD83") {
+ return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6269);
+ } else if (datumStr == "NAD27") {
+ return overridePmIfNeeded(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);
+ }();
+ if (!numericParamPresent) {
+ return l_datum;
+ }
+ a = l_datum->ellipsoid()->semiMajorAxis().getSIValue();
+ rf = l_datum->ellipsoid()->computedInverseFlattening();
+ }
+
+ else if (!ellpsStr.empty()) {
+ auto l_datum = [&ellpsStr, &title, &grfMap, &optionalEmptyString,
+ &pm]() {
+ 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);
+ }
+ }();
+ if (!numericParamPresent) {
+ return l_datum;
+ }
+ a = l_datum->ellipsoid()->semiMajorAxis().getSIValue();
+ if (l_datum->ellipsoid()->semiMinorAxis().has_value()) {
+ b = l_datum->ellipsoid()->semiMinorAxis()->getSIValue();
+ } else {
+ rf = l_datum->ellipsoid()->computedInverseFlattening();
+ }
+ }
+
+ if (!aStr.empty()) {
+ try {
+ a = c_locale_stod(aStr);
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("Invalid a value");
+ }
+ }
+
+ if (a > 0 && (b > 0 || !bStr.empty())) {
+ if (!bStr.empty()) {
+ 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 (a > 0 && (rf >= 0 || !rfStr.empty())) {
+ if (!rfStr.empty()) {
+ 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 (a > 0 && !fStr.empty()) {
+ double f;
+ try {
+ f = c_locale_stod(fStr);
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("Invalid f value");
+ }
+ auto ellipsoid = Ellipsoid::createFlattenedSphere(
+ createMapWithUnknownName(), Length(a),
+ Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
+ ->identify();
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title.c_str()),
+ ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
+ }
+
+ else if (a > 0 && !eStr.empty()) {
+ double e;
+ try {
+ e = c_locale_stod(eStr);
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("Invalid e value");
+ }
+ double alpha = asin(e); /* angular eccentricity */
+ double f = 1 - cos(alpha); /* = 1 - sqrt (1 - es); */
+ auto ellipsoid = Ellipsoid::createFlattenedSphere(
+ createMapWithUnknownName(), Length(a),
+ Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
+ ->identify();
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title.c_str()),
+ ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
+ }
+
+ else if (a > 0 && !esStr.empty()) {
+ double es;
+ try {
+ es = c_locale_stod(esStr);
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("Invalid es value");
+ }
+ double f = 1 - sqrt(1 - es);
+ auto ellipsoid = Ellipsoid::createFlattenedSphere(
+ createMapWithUnknownName(), Length(a),
+ Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
+ ->identify();
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title.c_str()),
+ ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
+ }
+
+ // If only a is specified, create a sphere
+ if (a > 0 && bStr.empty() && rfStr.empty() && eStr.empty() &&
+ esStr.empty()) {
+ auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(),
+ Length(a), guessBodyName(a));
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title.c_str()),
+ ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
+ }
+
+ 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 (!fStr.empty() && aStr.empty()) {
+ throw ParsingException("f found, but a missing");
+ }
+
+ if (!eStr.empty() && aStr.empty()) {
+ throw ParsingException("e found, but a missing");
+ }
+
+ if (!esStr.empty() && aStr.empty()) {
+ throw ParsingException("es found, but a missing");
+ }
+
+ return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
+}
+
+// ---------------------------------------------------------------------------
+
+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]);
+}
+
+// ---------------------------------------------------------------------------
+
+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;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+GeographicCRSNNPtr
+PROJStringParser::Private::buildGeographicCRS(int iStep, int iUnitConvert,
+ int iAxisSwap, bool ignoreVUnits,
+ bool ignorePROJAxis) {
+ const auto &step = steps_[iStep];
+
+ const bool l_isGeographicStep = isGeographicStep(step.name);
+ const auto &title = l_isGeographicStep ? title_ : emptyString;
+
+ auto datum = buildDatum(step, title);
+
+ auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title);
+ if (l_isGeographicStep &&
+ (hasParamValue(step, "wktext") ||
+ hasParamValue(step, "lon_wrap") | hasParamValue(step, "geoc") ||
+ getNumericValue(getParamValue(step, "lon_0")) != 0.0)) {
+ props.set("EXTENSION_PROJ4", projString_);
+ }
+
+ return GeographicCRS::create(
+ props, 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 props = PropertyMap().set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title);
+ if (hasParamValue(step, "wktext")) {
+ props.set("EXTENSION_PROJ4", projString_);
+ }
+
+ auto cs = CartesianCS::createGeocentric(unit);
+ return GeodeticCRS::create(props, 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;
+}
+
+// ---------------------------------------------------------------------------
+
+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("inconsistent pm values between projectedCRS "
+ "and its base geographicalCRS");
+ }
+
+ auto axisType = AxisType::REGULAR;
+
+ if (step.name == "tmerc" &&
+ ((getParamValue(step, "axis") == "wsu" && iAxisSwap < 0) ||
+ (iAxisSwap > 0 &&
+ getParamValue(steps_[iAxisSwap], "order") == "-1,-2"))) {
+ mapping =
+ getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED);
+ } else if (step.name == "etmerc") {
+ mapping = getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR);
+ } 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 = getParamValueK(step);
+ 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.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->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_rot")) {
+ mapping = nullptr;
+ } else 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" && iAxisSwap < 0) ||
+ (iAxisSwap > 0 &&
+ getParamValue(steps_[iAxisSwap], "order") == "-2,-1"))) {
+ 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) &&
+ getNumericValue(getParamValueK(step)) == 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;
+ }
+ const auto &lat_ts = getParamValue(step, "lat_ts");
+ const auto &k = getParamValueK(step);
+ if (!lat_ts.empty() &&
+ std::fabs(getAngularValue(lat_ts) - lat_0) > 1e-10 &&
+ !k.empty() && std::fabs(getNumericValue(k) - 1) > 1e-10) {
+ throw ParsingException("lat_ts != lat_0 and k != 1 not "
+ "supported for Polar Stereographic");
+ }
+ if (!lat_ts.empty() &&
+ (k.empty() || std::fabs(getNumericValue(k) - 1) < 1e-10)) {
+ 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);
+ }
+ } else if (step.name == "laea") {
+ 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 (geogCRS->ellipsoid()->isSphere()) {
+ mapping = getMapping(
+ EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL);
+ }
+ } else if (step.name == "eqc") {
+ if (geogCRS->ellipsoid()->isSphere()) {
+ mapping =
+ getMapping(EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL);
+ }
+ }
+
+ 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 == "k" || proj_name == "k_0")
+ ? &getParamValueK(step)
+ : !proj_name.empty() ? &getParamValue(step, proj_name)
+ : &emptyString;
+ 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.28813975277777776;
+ } 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)));
+ }
+
+ if (step.name == "etmerc") {
+ methodMap.set("proj_method", "etmerc");
+ }
+
+ 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]);
+
+ auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title);
+
+ if (hasParamValue(step, "wktext")) {
+ props.set("EXTENSION_PROJ4", projString_);
+ }
+
+ CRSNNPtr crs = ProjectedCRS::create(props, 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 == "f" || 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
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const metadata::ExtentPtr nullExtent{};
+
+static const metadata::ExtentPtr &getExtent(const crs::CRS *crs) {
+ const auto &domains = crs->domains();
+ if (!domains.empty()) {
+ return domains[0]->domainOfValidity();
+ }
+ return nullExtent;
+}
+
+//! @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;
+
+ d->steps_.clear();
+ d->title_.clear();
+ d->globalParamValues_.clear();
+ d->projString_ = projString;
+ 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->buildBoundOrCompoundCRSIfNeeded(
+ 0, d->buildGeocentricCRS(0, (d->steps_.size() == 2 &&
+ d->steps_[1].name == "unitconvert")
+ ? 1
+ : -1));
+ }
+
+ // +init=xxxx:yyyy syntax
+ if (d->steps_.size() == 1 && d->steps_[0].isInit &&
+ d->steps_[0].paramValues.size() == 0) {
+
+ // Those used to come from a text init file
+ // We only support them in compatibility mode
+ const std::string &stepName = d->steps_[0].name;
+ if (ci_starts_with(stepName, "epsg:") ||
+ ci_starts_with(stepName, "IGNF:")) {
+ bool usePROJ4InitRules = d->usePROJ4InitRules_;
+ if (!usePROJ4InitRules) {
+ PJ_CONTEXT *ctx = proj_context_create();
+ if (ctx) {
+ usePROJ4InitRules = proj_context_get_use_proj4_init_rules(
+ ctx, FALSE) == TRUE;
+ proj_context_destroy(ctx);
+ }
+ }
+ if (!usePROJ4InitRules) {
+ throw ParsingException("init=epsg:/init=IGNF: syntax not "
+ "supported in non-PROJ4 emulation mode");
+ }
+
+ PJ_CONTEXT *ctx = proj_context_create();
+ char unused[256];
+ std::string initname(stepName);
+ initname.resize(initname.find(':'));
+ int file_found =
+ pj_find_file(ctx, initname.c_str(), unused, sizeof(unused));
+ proj_context_destroy(ctx);
+ if (!file_found) {
+ auto obj = createFromUserInput(stepName, d->dbContext_, true);
+ auto crs = dynamic_cast<CRS *>(obj.get());
+ if (crs) {
+ PropertyMap properties;
+ properties.set(IdentifiedObject::NAME_KEY, crs->nameStr());
+ const auto &extent = getExtent(crs);
+ if (extent) {
+ properties.set(
+ common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ NN_NO_CHECK(extent));
+ }
+ auto geogCRS = dynamic_cast<GeographicCRS *>(crs);
+ if (geogCRS) {
+ // Override with longitude latitude in radian
+ return GeographicCRS::create(
+ properties, geogCRS->datum(),
+ geogCRS->datumEnsemble(),
+ EllipsoidalCS::createLongitudeLatitude(
+ UnitOfMeasure::RADIAN));
+ }
+ auto projCRS = dynamic_cast<ProjectedCRS *>(crs);
+ if (projCRS) {
+ // Override with easting northing order
+ const auto &conv = projCRS->derivingConversionRef();
+ if (conv->method()->getEPSGCode() !=
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
+ return ProjectedCRS::create(
+ properties, projCRS->baseCRS(), conv,
+ CartesianCS::createEastingNorthing(
+ projCRS->coordinateSystem()
+ ->axisList()[0]
+ ->unit()));
+ }
+ }
+ }
+ return obj;
+ }
+ }
+
+ paralist *init = pj_mkparam(("init=" + d->steps_[0].name).c_str());
+ if (!init) {
+ throw ParsingException("out of memory");
+ }
+ PJ_CONTEXT *ctx = proj_context_create();
+ if (!ctx) {
+ pj_dealloc(init);
+ throw ParsingException("out of memory");
+ }
+ paralist *list = pj_expand_init(ctx, init);
+ proj_context_destroy(ctx);
+ if (!list) {
+ pj_dealloc(init);
+ throw ParsingException("cannot expand " + projString);
+ }
+ std::string expanded;
+ bool first = true;
+ bool has_init_term = false;
+ for (auto t = list; t;) {
+ if (!expanded.empty()) {
+ expanded += ' ';
+ }
+ if (first) {
+ // first parameter is the init= itself
+ first = false;
+ } else if (starts_with(t->param, "init=")) {
+ has_init_term = true;
+ } else {
+ expanded += t->param;
+ }
+
+ auto n = t->next;
+ pj_dealloc(t);
+ t = n;
+ }
+
+ if (!has_init_term) {
+ return createFromPROJString(expanded);
+ }
+ }
+
+ 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 (isGeographicStep(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);
+ proj_context_use_proj4_init_rules(pj_context, d->usePROJ4InitRules_);
+ 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/iso19111/metadata.cpp b/src/iso19111/metadata.cpp
new file mode 100644
index 00000000..2be9dac3
--- /dev/null
+++ b/src/iso19111/metadata.cpp
@@ -0,0 +1,1285 @@
+/******************************************************************************
+ *
+ * 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() = default;
+
+ 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)
+{
+ {
+ const auto pVal = properties.get(AUTHORITY_KEY);
+ if (pVal) {
+ if (auto genVal = dynamic_cast<const BoxedValue *>(pVal->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 *>(pVal->get())) {
+ authority_ = *citation;
+ } else {
+ throw InvalidValueTypeException("Invalid value type for " +
+ AUTHORITY_KEY);
+ }
+ }
+ }
+ }
+
+ {
+ const auto pVal = properties.get(CODE_KEY);
+ if (pVal) {
+ if (auto genVal = dynamic_cast<const BoxedValue *>(pVal->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);
+ }
+ }
+ }
+
+ properties.getStringValue(CODESPACE_KEY, codeSpace_);
+ properties.getStringValue(VERSION_KEY, version_);
+ properties.getStringValue(DESCRIPTION_KEY, description_);
+ properties.getStringValue(URI_KEY, uri_);
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+Identifier::Identifier(const std::string &codeIn,
+ const util::PropertyMap &properties)
+ : d(internal::make_unique<Private>(codeIn, properties)) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+Identifier::Identifier() : d(internal::make_unique<Private>()) {}
+
+// ---------------------------------------------------------------------------
+
+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);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+IdentifierNNPtr
+Identifier::createFromDescription(const std::string &descriptionIn) {
+ auto id = Identifier::nn_make_shared<Identifier>();
+ id->d->description_ = descriptionIn;
+ return id;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \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 == ')' || ch == '.' || ch == '&';
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const struct utf8_to_lower {
+ const char *utf8;
+ char ascii;
+} map_utf8_to_lower[] = {
+ {"\xc3\xa1", 'a'}, // a acute
+ {"\xc3\xa4", 'a'}, // a tremma
+
+ {"\xc4\x9b", 'e'}, // e reverse circumflex
+ {"\xc3\xa8", 'e'}, // e grave
+ {"\xc3\xa9", 'e'}, // e acute
+ {"\xc3\xab", 'e'}, // e tremma
+
+ {"\xc3\xad", 'i'}, // i grave
+
+ {"\xc3\xb4", 'o'}, // o circumflex
+ {"\xc3\xb6", 'o'}, // o tremma
+
+ {"\xc3\xa7", 'c'}, // c cedilla
+};
+
+static const struct utf8_to_lower *get_ascii_replacement(const char *c_str) {
+ for (const auto &pair : map_utf8_to_lower) {
+ if (*c_str == pair.utf8[0] &&
+ strncmp(c_str, pair.utf8, strlen(pair.utf8)) == 0) {
+ return &pair;
+ }
+ }
+ return nullptr;
+}
+//! @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 (static_cast<unsigned char>(ch) > 127) {
+ const auto *replacement = get_ascii_replacement(c_str + i);
+ if (replacement) {
+ res.push_back(replacement->ascii);
+ i += strlen(replacement->utf8) - 1;
+ 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) {
+ char aCh = a[i];
+ 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 (static_cast<unsigned char>(aCh) > 127) {
+ const auto *replacement = get_ascii_replacement(a + i);
+ if (replacement) {
+ aCh = replacement->ascii;
+ i += strlen(replacement->utf8) - 1;
+ }
+ }
+ if (static_cast<unsigned char>(bCh) > 127) {
+ const auto *replacement = get_ascii_replacement(b + j);
+ if (replacement) {
+ bCh = replacement->ascii;
+ j += strlen(replacement->utf8) - 1;
+ }
+ }
+ 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/iso19111/static.cpp b/src/iso19111/static.cpp
new file mode 100644
index 00000000..5de046f1
--- /dev/null
+++ b/src/iso19111/static.cpp
@@ -0,0 +1,644 @@
+/******************************************************************************
+ *
+ * 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(TEMPORALQUANTITY);
+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 "Reference Meridian" PrimeMeridian.
+ *
+ * This is a meridian of longitude 0 to be used with non-Earth bodies. */
+const PrimeMeridianNNPtr PrimeMeridian::REFERENCE_MERIDIAN(
+ PrimeMeridian::createREFERENCE_MERIDIAN());
+/** \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/iso19111/util.cpp b/src/iso19111/util.cpp
new file mode 100644
index 00000000..ac6357a2
--- /dev/null
+++ b/src/iso19111/util.cpp
@@ -0,0 +1,689 @@
+/******************************************************************************
+ *
+ * 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::list<std::pair<std::string, BaseObjectNNPtr>> list_{};
+
+ // cppcheck-suppress functionStatic
+ void set(const std::string &key, const BoxedValueNNPtr &val) {
+ for (auto &pair : list_) {
+ if (pair.first == key) {
+ pair.second = val;
+ return;
+ }
+ }
+ list_.emplace_back(key, val);
+ }
+};
+//! @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
+const BaseObjectNNPtr *PropertyMap::get(const std::string &key) const {
+ for (const auto &pair : d->list_) {
+ if (pair.first == key) {
+ return &(pair.second);
+ }
+ }
+ return nullptr;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set a BaseObjectNNPtr as the value of a key. */
+PropertyMap &PropertyMap::set(const std::string &key,
+ const BaseObjectNNPtr &val) {
+ for (auto &pair : d->list_) {
+ if (pair.first == key) {
+ pair.second = val;
+ return *this;
+ }
+ }
+ d->list_.emplace_back(key, val);
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set a string as the value of a key. */
+PropertyMap &PropertyMap::set(const std::string &key, const std::string &val) {
+ d->set(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 char *val) {
+ d->set(key, util::nn_make_shared<BoxedValue>(val));
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set a integer as the value of a key. */
+PropertyMap &PropertyMap::set(const std::string &key, int val) {
+ d->set(key, util::nn_make_shared<BoxedValue>(val));
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set a boolean as the value of a key. */
+PropertyMap &PropertyMap::set(const std::string &key, bool val) {
+ d->set(key, util::nn_make_shared<BoxedValue>(val));
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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)
+{
+ for (const auto &pair : d->list_) {
+ if (pair.first == key) {
+ auto genVal = dynamic_cast<const BoxedValue *>(pair.second.get());
+ if (genVal && genVal->type() == BoxedValue::Type::STRING) {
+ outVal = genVal->stringValue();
+ return true;
+ }
+ throw InvalidValueTypeException("Invalid value type for " + key);
+ }
+ }
+ return false;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool PropertyMap::getStringValue(
+ const std::string &key,
+ optional<std::string> &outVal) const // throw(InvalidValueTypeException)
+{
+ for (const auto &pair : d->list_) {
+ if (pair.first == key) {
+ auto genVal = dynamic_cast<const BoxedValue *>(pair.second.get());
+ if (genVal && genVal->type() == BoxedValue::Type::STRING) {
+ outVal = genVal->stringValue();
+ return true;
+ }
+ throw InvalidValueTypeException("Invalid value type for " + key);
+ }
+ }
+ return false;
+}
+//! @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