aboutsummaryrefslogtreecommitdiff
path: root/src/iso19111/operation
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2020-12-12 22:09:11 +0100
committerEven Rouault <even.rouault@spatialys.com>2020-12-12 22:23:28 +0100
commit4bc3fec94ae0c27c6327e163ea35098347bac0f1 (patch)
treef54d2b42f20e9a3e34cc6bdd9728b8f8ebc53f4b /src/iso19111/operation
parent6857d1a4a8eb6fcb7b88b0339413913ba2c3351a (diff)
downloadPROJ-4bc3fec94ae0c27c6327e163ea35098347bac0f1.tar.gz
PROJ-4bc3fec94ae0c27c6327e163ea35098347bac0f1.zip
Split coordinateoperation.cpp in many files in iso19111/operation directory
The big size of coordinateoperation.cpp could require significant amount of RAM to build it with -O2 level, and cause compiler crashes in some environments.
Diffstat (limited to 'src/iso19111/operation')
-rw-r--r--src/iso19111/operation/concatenatedoperation.cpp675
-rw-r--r--src/iso19111/operation/conversion.cpp3955
-rw-r--r--src/iso19111/operation/coordinateoperation_internal.hpp274
-rw-r--r--src/iso19111/operation/coordinateoperation_private.hpp88
-rw-r--r--src/iso19111/operation/coordinateoperationfactory.cpp5257
-rw-r--r--src/iso19111/operation/esriparammappings.cpp1123
-rw-r--r--src/iso19111/operation/esriparammappings.hpp86
-rw-r--r--src/iso19111/operation/operationmethod_private.hpp56
-rw-r--r--src/iso19111/operation/oputils.cpp643
-rw-r--r--src/iso19111/operation/oputils.hpp121
-rw-r--r--src/iso19111/operation/parammappings.cpp1551
-rw-r--r--src/iso19111/operation/parammappings.hpp114
-rw-r--r--src/iso19111/operation/projbasedoperation.cpp316
-rw-r--r--src/iso19111/operation/singleoperation.cpp2218
-rw-r--r--src/iso19111/operation/transformation.cpp3274
-rw-r--r--src/iso19111/operation/vectorofvaluesparams.cpp116
-rw-r--r--src/iso19111/operation/vectorofvaluesparams.hpp101
17 files changed, 19968 insertions, 0 deletions
diff --git a/src/iso19111/operation/concatenatedoperation.cpp b/src/iso19111/operation/concatenatedoperation.cpp
new file mode 100644
index 00000000..1c65a24b
--- /dev/null
+++ b/src/iso19111/operation/concatenatedoperation.cpp
@@ -0,0 +1,675 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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/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 "coordinateoperation_internal.hpp"
+#include "oputils.hpp"
+
+// PROJ include order is sensitive
+// clang-format off
+#include "proj.h"
+#include "proj_internal.h" // M_PI
+// clang-format on
+
+#include "proj_json_streaming_writer.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+using namespace NS_PROJ::internal;
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct ConcatenatedOperation::Private {
+ std::vector<CoordinateOperationNNPtr> operations_{};
+ bool computedName_ = false;
+
+ explicit Private(const std::vector<CoordinateOperationNNPtr> &operationsIn)
+ : operations_(operationsIn) {}
+ Private(const Private &) = default;
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+ConcatenatedOperation::~ConcatenatedOperation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+ConcatenatedOperation::ConcatenatedOperation(const ConcatenatedOperation &other)
+ : CoordinateOperation(other),
+ d(internal::make_unique<Private>(*(other.d))) {}
+//! @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_;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static bool compareStepCRS(const crs::CRS *a, const crs::CRS *b) {
+ const auto &aIds = a->identifiers();
+ const auto &bIds = b->identifiers();
+ if (aIds.size() == 1 && bIds.size() == 1 &&
+ aIds[0]->code() == bIds[0]->code() &&
+ *aIds[0]->codeSpace() == *bIds[0]->codeSpace()) {
+ return true;
+ }
+ return a->_isEquivalentTo(b, util::IComparable::Criterion::EQUIVALENT);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate 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;
+
+ crs::CRSPtr interpolationCRS;
+ bool interpolationCRSValid = true;
+ for (size_t i = 0; i < operationsIn.size(); i++) {
+ auto l_sourceCRS = operationsIn[i]->sourceCRS();
+ auto l_targetCRS = operationsIn[i]->targetCRS();
+
+ if (interpolationCRSValid) {
+ auto subOpInterpCRS = operationsIn[i]->interpolationCRS();
+ if (interpolationCRS == nullptr)
+ interpolationCRS = subOpInterpCRS;
+ else if (subOpInterpCRS == nullptr ||
+ !(subOpInterpCRS->isEquivalentTo(
+ interpolationCRS.get(),
+ util::IComparable::Criterion::EQUIVALENT))) {
+ interpolationCRS = nullptr;
+ interpolationCRSValid = false;
+ }
+ }
+
+ 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) {
+ if (!compareStepCRS(l_sourceCRS.get(), lastTargetCRS.get())) {
+#ifdef DEBUG_CONCATENATED_OPERATION
+ std::cerr << "Step " << i - 1 << ": "
+ << operationsIn[i - 1]->nameStr() << std::endl;
+ std::cerr << "Step " << i << ": " << operationsIn[i]->nameStr()
+ << std::endl;
+ {
+ auto f(io::WKTFormatter::create(
+ io::WKTFormatter::Convention::WKT2_2019));
+ std::cerr << "Source CRS of step " << i << ":" << std::endl;
+ std::cerr << l_sourceCRS->exportToWKT(f.get()) << std::endl;
+ }
+ {
+ auto f(io::WKTFormatter::create(
+ io::WKTFormatter::Convention::WKT2_2019));
+ std::cerr << "Target CRS of step " << i - 1 << ":"
+ << std::endl;
+ std::cerr << lastTargetCRS->exportToWKT(f.get())
+ << std::endl;
+ }
+#endif
+ 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()),
+ interpolationCRS);
+ op->setAccuracies(accuracies);
+#ifdef DEBUG_CONCATENATED_OPERATION
+ {
+ auto f(
+ io::WKTFormatter::create(io::WKTFormatter::Convention::WKT2_2019));
+ std::cerr << "ConcatenatedOperation::create()" << std::endl;
+ std::cerr << op->exportToWKT(f.get()) << std::endl;
+ }
+#endif
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+void ConcatenatedOperation::fixStepsDirection(
+ const crs::CRSNNPtr &concatOpSourceCRS,
+ const crs::CRSNNPtr &concatOpTargetCRS,
+ std::vector<CoordinateOperationNNPtr> &operationsInOut) {
+
+ // Set of heuristics to assign CRS to steps, and possibly reverse them.
+
+ const auto isGeographic = [](const crs::CRS *crs) -> bool {
+ return dynamic_cast<const crs::GeographicCRS *>(crs) != nullptr;
+ };
+
+ const auto isGeocentric = [](const crs::CRS *crs) -> bool {
+ auto geodCRS = dynamic_cast<const crs::GeodeticCRS *>(crs);
+ if (geodCRS && geodCRS->coordinateSystem()->axisList().size() == 3)
+ return true;
+ return false;
+ };
+
+ for (size_t i = 0; i < operationsInOut.size(); ++i) {
+ auto &op = operationsInOut[i];
+ auto l_sourceCRS = op->sourceCRS();
+ auto l_targetCRS = op->targetCRS();
+ auto conv = dynamic_cast<const Conversion *>(op.get());
+ if (conv && i == 0 && !l_sourceCRS && !l_targetCRS) {
+ auto derivedCRS =
+ dynamic_cast<const crs::DerivedCRS *>(concatOpSourceCRS.get());
+ if (derivedCRS) {
+ if (i + 1 < operationsInOut.size()) {
+ // use the sourceCRS of the next operation as our target CRS
+ l_targetCRS = operationsInOut[i + 1]->sourceCRS();
+ // except if it looks like the next operation should
+ // actually be reversed !!!
+ if (l_targetCRS &&
+ !compareStepCRS(l_targetCRS.get(),
+ derivedCRS->baseCRS().get()) &&
+ operationsInOut[i + 1]->targetCRS() &&
+ compareStepCRS(
+ operationsInOut[i + 1]->targetCRS().get(),
+ derivedCRS->baseCRS().get())) {
+ l_targetCRS = operationsInOut[i + 1]->targetCRS();
+ }
+ }
+ if (!l_targetCRS) {
+ l_targetCRS = derivedCRS->baseCRS().as_nullable();
+ }
+ auto invConv =
+ util::nn_dynamic_pointer_cast<InverseConversion>(op);
+ auto nn_targetCRS = NN_NO_CHECK(l_targetCRS);
+ if (invConv) {
+ invConv->inverse()->setCRSs(nn_targetCRS, concatOpSourceCRS,
+ nullptr);
+ op->setCRSs(concatOpSourceCRS, nn_targetCRS, nullptr);
+ } else {
+ op->setCRSs(nn_targetCRS, concatOpSourceCRS, nullptr);
+ op = op->inverse();
+ }
+ } else if (i + 1 < operationsInOut.size()) {
+ /* coverity[copy_paste_error] */
+ l_targetCRS = operationsInOut[i + 1]->sourceCRS();
+ if (l_targetCRS) {
+ op->setCRSs(concatOpSourceCRS, NN_NO_CHECK(l_targetCRS),
+ nullptr);
+ }
+ }
+ } else if (conv && i + 1 == operationsInOut.size() && !l_sourceCRS &&
+ !l_targetCRS) {
+ auto derivedCRS =
+ dynamic_cast<const crs::DerivedCRS *>(concatOpTargetCRS.get());
+ if (derivedCRS) {
+ if (i >= 1) {
+ // use the sourceCRS of the previous operation as our source
+ // CRS
+ l_sourceCRS = operationsInOut[i - 1]->targetCRS();
+ // except if it looks like the previous operation should
+ // actually be reversed !!!
+ if (l_sourceCRS &&
+ !compareStepCRS(l_sourceCRS.get(),
+ derivedCRS->baseCRS().get()) &&
+ operationsInOut[i - 1]->sourceCRS() &&
+ compareStepCRS(
+ operationsInOut[i - 1]->sourceCRS().get(),
+ derivedCRS->baseCRS().get())) {
+ l_targetCRS = operationsInOut[i - 1]->sourceCRS();
+ }
+ }
+ if (!l_sourceCRS) {
+ l_sourceCRS = derivedCRS->baseCRS().as_nullable();
+ }
+ op->setCRSs(NN_NO_CHECK(l_sourceCRS), concatOpTargetCRS,
+ nullptr);
+ } else if (i >= 1) {
+ l_sourceCRS = operationsInOut[i - 1]->targetCRS();
+ if (l_sourceCRS) {
+ derivedCRS = dynamic_cast<const crs::DerivedCRS *>(
+ l_sourceCRS.get());
+ if (conv->isEquivalentTo(
+ derivedCRS->derivingConversion().get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ op->setCRSs(concatOpTargetCRS, NN_NO_CHECK(l_sourceCRS),
+ nullptr);
+ op = op->inverse();
+ }
+ op->setCRSs(NN_NO_CHECK(l_sourceCRS), concatOpTargetCRS,
+ nullptr);
+ }
+ }
+ } else if (conv && i > 0 && i < operationsInOut.size() - 1) {
+ // For an intermediate conversion, use the target CRS of the
+ // previous step and the source CRS of the next step
+ l_sourceCRS = operationsInOut[i - 1]->targetCRS();
+ l_targetCRS = operationsInOut[i + 1]->sourceCRS();
+ if (l_sourceCRS && l_targetCRS) {
+ op->setCRSs(NN_NO_CHECK(l_sourceCRS), NN_NO_CHECK(l_targetCRS),
+ nullptr);
+ }
+ } else if (!conv && l_sourceCRS && l_targetCRS) {
+
+ // Transformations might be mentioned in their forward directions,
+ // whereas we should instead use the reverse path.
+ auto prevOpTarget = (i == 0) ? concatOpSourceCRS.as_nullable()
+ : operationsInOut[i - 1]->targetCRS();
+ if (compareStepCRS(l_sourceCRS.get(), prevOpTarget.get())) {
+ // do nothing
+ } else if (compareStepCRS(l_targetCRS.get(), prevOpTarget.get())) {
+ op = op->inverse();
+ }
+ // Below is needed for EPSG:9103 which chains NAD83(2011) geographic
+ // 2D with NAD83(2011) geocentric
+ else if (l_sourceCRS->nameStr() == prevOpTarget->nameStr() &&
+ ((isGeographic(l_sourceCRS.get()) &&
+ isGeocentric(prevOpTarget.get())) ||
+ (isGeocentric(l_sourceCRS.get()) &&
+ isGeographic(prevOpTarget.get())))) {
+ auto newOp(Conversion::createGeographicGeocentric(
+ NN_NO_CHECK(prevOpTarget), NN_NO_CHECK(l_sourceCRS)));
+ operationsInOut.insert(operationsInOut.begin() + i, newOp);
+ } else if (l_targetCRS->nameStr() == prevOpTarget->nameStr() &&
+ ((isGeographic(l_targetCRS.get()) &&
+ isGeocentric(prevOpTarget.get())) ||
+ (isGeocentric(l_targetCRS.get()) &&
+ isGeographic(prevOpTarget.get())))) {
+ auto newOp(Conversion::createGeographicGeocentric(
+ NN_NO_CHECK(prevOpTarget), NN_NO_CHECK(l_targetCRS)));
+ operationsInOut.insert(operationsInOut.begin() + i, newOp);
+ }
+ }
+ }
+
+ if (!operationsInOut.empty()) {
+ auto l_sourceCRS = operationsInOut.front()->sourceCRS();
+ if (l_sourceCRS &&
+ !compareStepCRS(l_sourceCRS.get(), concatOpSourceCRS.get())) {
+ throw InvalidOperation("The source CRS of the first step of "
+ "concatenated operation is not the same "
+ "as the source CRS of the concatenated "
+ "operation itself");
+ }
+
+ auto l_targetCRS = operationsInOut.back()->targetCRS();
+ if (l_targetCRS &&
+ !compareStepCRS(l_targetCRS.get(), concatOpTargetCRS.get())) {
+ if (l_targetCRS->nameStr() == concatOpTargetCRS->nameStr() &&
+ ((isGeographic(l_targetCRS.get()) &&
+ isGeocentric(concatOpTargetCRS.get())) ||
+ (isGeocentric(l_targetCRS.get()) &&
+ isGeographic(concatOpTargetCRS.get())))) {
+ auto newOp(Conversion::createGeographicGeocentric(
+ NN_NO_CHECK(l_targetCRS), concatOpTargetCRS));
+ operationsInOut.push_back(newOp);
+ } else {
+ throw InvalidOperation("The target CRS of the last step of "
+ "concatenated operation is not the same "
+ "as the target CRS of the concatenated "
+ "operation itself");
+ }
+ }
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate 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;
+ bool hasBallparkTransformation = false;
+ for (const auto &subOp : operationsIn) {
+ hasBallparkTransformation |= subOp->hasBallparkTransformation();
+ 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 concatenated "
+ "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->setHasBallparkTransformation(hasBallparkTransformation);
+ 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_;
+ op->setHasBallparkTransformation(hasBallparkTransformation());
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void ConcatenatedOperation::_exportToWKT(io::WKTFormatter *formatter) const {
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ if (!isWKT2 || !formatter->use2019Keywords()) {
+ throw io::FormattingException(
+ "Transformation can only be exported to WKT2:2019");
+ }
+
+ formatter->startNode(io::WKTConstants::CONCATENATEDOPERATION,
+ !identifiers().empty());
+ formatter->addQuotedString(nameStr());
+
+ if (formatter->use2019Keywords()) {
+ const auto &version = operationVersion();
+ if (version.has_value()) {
+ formatter->startNode(io::WKTConstants::VERSION, false);
+ formatter->addQuotedString(*version);
+ formatter->endNode();
+ }
+ }
+
+ exportSourceCRSAndTargetCRSToWKT(this, formatter);
+
+ const bool canExportOperationId =
+ !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId());
+
+ const bool hasDomains = !domains().empty();
+ if (hasDomains) {
+ formatter->pushDisableUsage();
+ }
+
+ for (const auto &operation : operations()) {
+ formatter->startNode(io::WKTConstants::STEP, false);
+ if (canExportOperationId && !operation->identifiers().empty()) {
+ // fake that top node has no id, so that the operation id is
+ // considered
+ formatter->pushHasId(false);
+ operation->_exportToWKT(formatter);
+ formatter->popHasId();
+ } else {
+ operation->_exportToWKT(formatter);
+ }
+ formatter->endNode();
+ }
+
+ if (hasDomains) {
+ formatter->popDisableUsage();
+ }
+
+ ObjectUsage::baseExportToWKT(formatter);
+ formatter->endNode();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void ConcatenatedOperation::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext("ConcatenatedOperation",
+ !identifiers().empty()));
+
+ writer->AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer->Add("unnamed");
+ } else {
+ writer->Add(l_name);
+ }
+
+ writer->AddObjKey("source_crs");
+ formatter->setAllowIDInImmediateChild();
+ sourceCRS()->_exportToJSON(formatter);
+
+ writer->AddObjKey("target_crs");
+ formatter->setAllowIDInImmediateChild();
+ targetCRS()->_exportToJSON(formatter);
+
+ writer->AddObjKey("steps");
+ {
+ auto parametersContext(writer->MakeArrayContext(false));
+ for (const auto &operation : operations()) {
+ formatter->setAllowIDInImmediateChild();
+ operation->_exportToJSON(formatter);
+ }
+ }
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+CoordinateOperationNNPtr ConcatenatedOperation::_shallowClone() const {
+ auto op =
+ ConcatenatedOperation::nn_make_shared<ConcatenatedOperation>(*this);
+ std::vector<CoordinateOperationNNPtr> ops;
+ for (const auto &subOp : d->operations_) {
+ ops.emplace_back(subOp->shallowClone());
+ }
+ op->d->operations_ = ops;
+ op->assignSelf(op);
+ op->setCRSs(this, false);
+ return util::nn_static_pointer_cast<CoordinateOperation>(op);
+}
+//! @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 io::DatabaseContextPtr &dbContext) const {
+ auto otherCO = dynamic_cast<const ConcatenatedOperation *>(other);
+ if (otherCO == nullptr ||
+ (criterion == util::IComparable::Criterion::STRICT &&
+ !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) {
+ 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,
+ dbContext)) {
+ return false;
+ }
+ }
+ return true;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+std::set<GridDescription> ConcatenatedOperation::gridsNeeded(
+ const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const {
+ std::set<GridDescription> res;
+ for (const auto &operation : operations()) {
+ const auto l_gridsNeeded = operation->gridsNeeded(
+ databaseContext, considerKnownGridsAsAvailable);
+ for (const auto &gridDesc : l_gridsNeeded) {
+ res.insert(gridDesc);
+ }
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
diff --git a/src/iso19111/operation/conversion.cpp b/src/iso19111/operation/conversion.cpp
new file mode 100644
index 00000000..1808dbe7
--- /dev/null
+++ b/src/iso19111/operation/conversion.cpp
@@ -0,0 +1,3955 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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/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 "proj/internal/tracing.hpp"
+
+#include "coordinateoperation_internal.hpp"
+#include "esriparammappings.hpp"
+#include "operationmethod_private.hpp"
+#include "oputils.hpp"
+#include "parammappings.hpp"
+#include "vectorofvaluesparams.hpp"
+
+// PROJ include order is sensitive
+// clang-format off
+#include "proj.h"
+#include "proj_internal.h" // M_PI
+// clang-format on
+#include "proj_constants.h"
+
+#include "proj_json_streaming_writer.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+using namespace NS_PROJ::internal;
+
+// ---------------------------------------------------------------------------
+
+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;
+//! @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;
+}
+
+CoordinateOperationNNPtr Conversion::_shallowClone() const {
+ return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone());
+}
+//! @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 Instantiate 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 Instantiate 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
+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 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);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a [Universal Transverse Mercator]
+ *(https://proj.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 Instantiate a conversion based on the [Transverse Mercator]
+ *(https://proj.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 Instantiate a conversion based on the [Gauss Schreiber Transverse
+ *Mercator]
+ *(https://proj.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 Instantiate a conversion based on the [Transverse Mercator South
+ *Orientated]
+ *(https://proj.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 Instantiate a conversion based on the [Two Point Equidistant]
+ *(https://proj.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 Instantiate 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 Instantiate a conversion based on the [Albers Conic Equal Area]
+ *(https://proj.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 Instantiate a conversion based on the [Lambert Conic Conformal 1SP]
+ *(https://proj.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 Instantiate a conversion based on the [Lambert Conic Conformal (2SP)]
+ *(https://proj.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 Instantiate a conversion based on the [Lambert Conic Conformal (2SP
+ *Michigan)]
+ *(https://proj.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 Instantiate a conversion based on the [Lambert Conic Conformal (2SP
+ *Belgium)]
+ *(https://proj.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 Instantiate a conversion based on the [Modified Azimuthal
+ *Equidistant]
+ *(https://proj.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 Instantiate a conversion based on the [Guam Projection]
+ *(https://proj.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 Instantiate a conversion based on the [Bonne]
+ *(https://proj.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 Instantiate a conversion based on the [Lambert Cylindrical Equal Area
+ *(Spherical)]
+ *(https://proj.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 GeographicCRS.
+ *
+ * @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 Instantiate a conversion based on the [Lambert Cylindrical Equal Area
+ *(ellipsoidal form)]
+ *(https://proj.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 Instantiate a conversion based on the [Cassini-Soldner]
+ * (https://proj.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 Instantiate a conversion based on the [Equidistant Conic]
+ *(https://proj.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 Instantiate a conversion based on the [Eckert I]
+ * (https://proj.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 Instantiate a conversion based on the [Eckert II]
+ * (https://proj.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 Instantiate a conversion based on the [Eckert III]
+ * (https://proj.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 Instantiate a conversion based on the [Eckert IV]
+ * (https://proj.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 Instantiate a conversion based on the [Eckert V]
+ * (https://proj.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 Instantiate a conversion based on the [Eckert VI]
+ * (https://proj.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 Instantiate a conversion based on the [Equidistant Cylindrical]
+ *(https://proj.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 Instantiate a conversion based on the [Equidistant Cylindrical
+ *(Spherical)]
+ *(https://proj.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 Instantiate a conversion based on the [Gall (Stereographic)]
+ * (https://proj.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 Instantiate a conversion based on the [Goode Homolosine]
+ * (https://proj.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 Instantiate a conversion based on the [Interrupted Goode Homolosine]
+ * (https://proj.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 Instantiate a conversion based on the [Geostationary Satellite View]
+ * (https://proj.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 Instantiate a conversion based on the [Geostationary Satellite View]
+ * (https://proj.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 Instantiate a conversion based on the [Gnomonic]
+ *(https://proj.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 Instantiate a conversion based on the [Hotine Oblique Mercator
+ *(Variant A)]
+ *(https://proj.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://proj.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 Instantiate a conversion based on the [Hotine Oblique Mercator
+ *(Variant B)]
+ *(https://proj.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://proj.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 Instantiate a conversion based on the [Hotine Oblique Mercator Two
+ *Point Natural Origin]
+ *(https://proj.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 Instantiate a conversion based on the [Laborde Oblique Mercator]
+ *(https://proj.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 Instantiate a conversion based on the [International Map of the World
+ *Polyconic]
+ *(https://proj.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 Instantiate a conversion based on the [Krovak (north oriented)]
+ *(https://proj.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 Instantiate a conversion based on the [Krovak]
+ *(https://proj.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 Instantiate a conversion based on the [Lambert Azimuthal Equal Area]
+ *(https://proj.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 Instantiate a conversion based on the [Miller Cylindrical]
+ *(https://proj.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 Instantiate a conversion based on the [Mercator]
+ *(https://proj.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 Instantiate a conversion based on the [Mercator]
+ *(https://proj.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 Instantiate a conversion based on the [Popular Visualisation Pseudo
+ *Mercator]
+ *(https://proj.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 Instantiate a conversion based on the [Mollweide]
+ * (https://proj.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 Instantiate a conversion based on the [New Zealand Map Grid]
+ * (https://proj.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 Instantiate a conversion based on the [Oblique Stereographic
+ *(Alternative)]
+ *(https://proj.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 Instantiate a conversion based on the [Orthographic]
+ *(https://proj.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 Before PROJ 7.2, only the spherical formulation was implemented.
+ *
+ * @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 Instantiate a conversion based on the [American Polyconic]
+ *(https://proj.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 Instantiate a conversion based on the [Polar Stereographic (Variant
+ *A)]
+ *(https://proj.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 Instantiate a conversion based on the [Polar Stereographic (Variant
+ *B)]
+ *(https://proj.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 Instantiate a conversion based on the [Robinson]
+ * (https://proj.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 Instantiate a conversion based on the [Sinusoidal]
+ * (https://proj.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 Instantiate a conversion based on the [Stereographic]
+ *(https://proj.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 Instantiate a conversion based on the [Van der Grinten]
+ * (https://proj.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 Instantiate a conversion based on the [Wagner I]
+ * (https://proj.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 Instantiate a conversion based on the [Wagner II]
+ * (https://proj.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 Instantiate a conversion based on the [Wagner III]
+ * (https://proj.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 Instantiate a conversion based on the [Wagner IV]
+ * (https://proj.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 Instantiate a conversion based on the [Wagner V]
+ * (https://proj.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 Instantiate a conversion based on the [Wagner VI]
+ * (https://proj.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 Instantiate a conversion based on the [Wagner VII]
+ * (https://proj.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 Instantiate a conversion based on the [Quadrilateralized Spherical
+ *Cube]
+ *(https://proj.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 Instantiate a conversion based on the [Spherical Cross-Track Height]
+ *(https://proj.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 Instantiate a conversion based on the [Equal Earth]
+ * (https://proj.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));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the [Vertical Perspective]
+ * (https://proj.org/operations/projections/nsper.html) projection method.
+ *
+ * This method is defined as [EPSG:9838]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9838)
+ *
+ * The PROJ implementation of the EPSG Vertical Perspective has the current
+ * limitations with respect to the method described in EPSG:
+ * <ul>
+ * <li> it is a 2D-only method, ignoring the ellipsoidal height of the point to
+ * project.</li>
+ * <li> it has only a spherical development.</li>
+ * <li> the height of the topocentric origin is ignored, and thus assumed to be
+ * 0.</li>
+ * </ul>
+ *
+ * For completeness, PROJ adds the falseEasting and falseNorthing parameter,
+ * which are not described in EPSG. They should usually be set to 0.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param topoOriginLat Latitude of topocentric origin
+ * @param topoOriginLong Longitude of topocentric origin
+ * @param topoOriginHeight Ellipsoidal height of topocentric origin. Ignored by
+ * PROJ (that is assumed to be 0)
+ * @param viewPointHeight Viewpoint height with respect to the
+ * topocentric/mapping plane. In the case where topoOriginHeight = 0, this is
+ * the height above the ellipsoid surface at topoOriginLat, topoOriginLong.
+ * @param falseEasting See \ref false_easting . (not in EPSG)
+ * @param falseNorthing See \ref false_northing . (not in EPSG)
+ * @return a new Conversion.
+ *
+ * @since 6.3
+ */
+ConversionNNPtr Conversion::createVerticalPerspective(
+ const util::PropertyMap &properties, const common::Angle &topoOriginLat,
+ const common::Angle &topoOriginLong, const common::Length &topoOriginHeight,
+ const common::Length &viewPointHeight, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE,
+ createParams(topoOriginLat, topoOriginLong, topoOriginHeight,
+ viewPointHeight, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the Pole Rotation method, using
+ * the conventions of the GRIB 1 and GRIB 2 data formats.
+ *
+ * Those are mentioned in the Note 2 of
+ * https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_temp3-1.shtml
+ *
+ * Several conventions for the pole rotation method exists.
+ * The parameters provided in this method are remapped to the PROJ ob_tran
+ * operation with:
+ * <pre>
+ * +proj=ob_tran +o_proj=longlat +o_lon_p=-rotationAngle
+ * +o_lat_p=-southPoleLatInUnrotatedCRS
+ * +lon_0=southPoleLongInUnrotatedCRS
+ * </pre>
+ *
+ * Another implementation of that convention is also in the netcdf-java library:
+ * https://github.com/Unidata/netcdf-java/blob/3ce72c0cd167609ed8c69152bb4a004d1daa9273/cdm/core/src/main/java/ucar/unidata/geoloc/projection/RotatedLatLon.java
+ *
+ * The PROJ implementation of this method assumes a spherical ellipsoid.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param southPoleLatInUnrotatedCRS Latitude of the point from the unrotated
+ * CRS, expressed in the unrotated CRS, that will become the south pole of the
+ * rotated CRS.
+ * @param southPoleLongInUnrotatedCRS Longitude of the point from the unrotated
+ * CRS, expressed in the unrotated CRS, that will become the south pole of the
+ * rotated CRS.
+ * @param axisRotation The angle of rotation about the new polar
+ * axis (measured clockwise when looking from the southern to the northern pole)
+ * of the coordinate system, assuming the new axis to have been obtained by
+ * first rotating the sphere through southPoleLongInUnrotatedCRS degrees about
+ * the geographic polar axis and then rotating through
+ * (90 + southPoleLatInUnrotatedCRS) degrees so that the southern pole moved
+ * along the (previously rotated) Greenwich meridian.
+ * @return a new Conversion.
+ *
+ * @since 7.0
+ */
+ConversionNNPtr Conversion::createPoleRotationGRIBConvention(
+ const util::PropertyMap &properties,
+ const common::Angle &southPoleLatInUnrotatedCRS,
+ const common::Angle &southPoleLongInUnrotatedCRS,
+ const common::Angle &axisRotation) {
+ return create(properties,
+ PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION,
+ createParams(southPoleLatInUnrotatedCRS,
+ southPoleLongInUnrotatedCRS, axisRotation));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate 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 Instantiate a conversion based on the Height Depth Reversal
+ * method.
+ *
+ * This method is defined as [EPSG:1068]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1068)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @return a new Conversion.
+ * @since 6.3
+ */
+ConversionNNPtr
+Conversion::createHeightDepthReversal(const util::PropertyMap &properties) {
+ return create(properties, createMethodMapNameEPSGCode(
+ EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL),
+ {}, {});
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate 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_3D_NAME, 15499),
+ createMethodMapNameEPSGCode(
+ EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D),
+ {}, {});
+ } else {
+ return create(createMapNameEPSGCode(AXIS_ORDER_CHANGE_2D_NAME, 15498),
+ createMethodMapNameEPSGCode(
+ EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D),
+ {}, {});
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate 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
+
+ConversionNNPtr
+Conversion::createGeographicGeocentric(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS) {
+ auto properties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildOpName("Conversion", sourceCRS, targetCRS));
+ auto conv = createGeographicGeocentric(properties);
+ conv->setCRSs(sourceCRS, targetCRS, nullptr);
+ return conv;
+}
+
+// ---------------------------------------------------------------------------
+
+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;
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr InverseConversion::_shallowClone() const {
+ auto op = InverseConversion::nn_make_shared<InverseConversion>(
+ inverseAsConversion()->shallowClone());
+ op->assignSelf(op);
+ op->setCRSs(this, false);
+ return util::nn_static_pointer_cast<CoordinateOperation>(op);
+}
+
+//! @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;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
+
+ auto conv = createHeightDepthReversal(
+ createPropertiesForInverse(this, false, false));
+ conv->setCRSs(this, true);
+ return conv;
+ }
+
+ return InverseConversion::create(NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<Conversion>(shared_from_this())));
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static double msfn(double phi, double e2) {
+ const double sinphi = std::sin(phi);
+ const double cosphi = std::cos(phi);
+ return pj_msfn(sinphi, cosphi, e2);
+}
+
+// ---------------------------------------------------------------------------
+
+static double tsfn(double phi, double ec) {
+ const double sinphi = std::sin(phi);
+ return pj_tsfn(phi, sinphi, ec);
+}
+
+// ---------------------------------------------------------------------------
+
+// Function whose zeroes are the sin of the standard parallels of LCC_2SP
+static double lcc_1sp_to_2sp_f(double sinphi, double K, double ec, double n) {
+ const double x = sinphi;
+ const double ecx = ec * x;
+ return (1 - x * x) / (1 - ecx * ecx) -
+ K * K * std::pow((1.0 - x) / (1.0 + x) *
+ std::pow((1.0 + ecx) / (1.0 - ecx), ec),
+ n);
+}
+
+// ---------------------------------------------------------------------------
+
+// Find the sin of the standard parallels of LCC_2SP
+static double find_zero_lcc_1sp_to_2sp_f(double sinphi0, bool bNorth, double K,
+ double ec) {
+ double a, b;
+ double f_a;
+ if (bNorth) {
+ // Look for zero above phi0
+ a = sinphi0;
+ b = 1.0; // sin(North pole)
+ f_a = 1.0; // some positive value, but we only care about the sign
+ } else {
+ // Look for zero below phi0
+ a = -1.0; // sin(South pole)
+ b = sinphi0;
+ f_a = -1.0; // minus infinity in fact, but we only care about the sign
+ }
+ // We use dichotomy search. lcc_1sp_to_2sp_f() is positive at sinphi_init,
+ // has a zero in ]-1,sinphi0[ and ]sinphi0,1[ ranges
+ for (int N = 0; N < 100; N++) {
+ double c = (a + b) / 2;
+ double f_c = lcc_1sp_to_2sp_f(c, K, ec, sinphi0);
+ if (f_c == 0.0 || (b - a) < 1e-18) {
+ return c;
+ }
+ if ((f_c > 0 && f_a > 0) || (f_c < 0 && f_a < 0)) {
+ a = c;
+ f_a = f_c;
+ } else {
+ b = c;
+ }
+ }
+ return (a + b) / 2;
+}
+
+static inline double DegToRad(double x) { return x / 180.0 * M_PI; }
+static inline double RadToDeg(double x) { return x / M_PI * 180.0; }
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/**
+ * \brief Return an equivalent projection.
+ *
+ * Currently implemented:
+ * <ul>
+ * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP) to
+ * EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP)</li>
+ * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP) to
+ * EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP)</li>
+ * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP to
+ * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP</li>
+ * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP to
+ * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP</li>
+ * </ul>
+ *
+ * @param targetEPSGCode EPSG code of the target method.
+ * @return new conversion, or nullptr
+ */
+ConversionPtr Conversion::convertToOtherMethod(int targetEPSGCode) const {
+ const int current_epsg_code = method()->getEPSGCode();
+ if (current_epsg_code == targetEPSGCode) {
+ return util::nn_dynamic_pointer_cast<Conversion>(shared_from_this());
+ }
+
+ auto geogCRS = dynamic_cast<crs::GeodeticCRS *>(sourceCRS().get());
+ if (!geogCRS) {
+ return nullptr;
+ }
+
+ const double e2 = geogCRS->ellipsoid()->squaredEccentricity();
+ if (e2 < 0) {
+ return nullptr;
+ }
+
+ if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_A &&
+ targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
+ parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) {
+ const double k0 = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
+ if (!(k0 > 0 && k0 <= 1.0 + 1e-10))
+ return nullptr;
+ const double dfStdP1Lat =
+ (k0 >= 1.0)
+ ? 0.0
+ : std::acos(std::sqrt((1.0 - e2) / ((1.0 / (k0 * k0)) - e2)));
+ auto latitudeFirstParallel = common::Angle(
+ common::Angle(dfStdP1Lat, common::UnitOfMeasure::RADIAN)
+ .convertToUnit(common::UnitOfMeasure::DEGREE),
+ common::UnitOfMeasure::DEGREE);
+ auto conv = createMercatorVariantB(
+ util::PropertyMap(), latitudeFirstParallel,
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
+ conv->setCRSs(this, false);
+ return conv.as_nullable();
+ }
+
+ if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
+ targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
+ const double phi1 = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL);
+ if (!(std::fabs(phi1) < M_PI / 2))
+ return nullptr;
+ const double k0 = msfn(phi1, e2);
+ auto conv = createMercatorVariantA(
+ util::PropertyMap(),
+ common::Angle(0.0, common::UnitOfMeasure::DEGREE),
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ common::Scale(k0, common::UnitOfMeasure::SCALE_UNITY),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
+ conv->setCRSs(this, false);
+ return conv.as_nullable();
+ }
+
+ if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
+ targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
+ // Notations m0, t0, n, m1, t1, F are those of the EPSG guidance
+ // "1.3.1.1 Lambert Conic Conformal (2SP)" and
+ // "1.3.1.2 Lambert Conic Conformal (1SP)" and
+ // or Snyder pages 106-109
+ auto latitudeOfOrigin = common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN));
+ const double phi0 = latitudeOfOrigin.getSIValue();
+ const double k0 = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
+ if (!(std::fabs(phi0) < M_PI / 2))
+ return nullptr;
+ if (!(k0 > 0 && k0 <= 1.0 + 1e-10))
+ return nullptr;
+ const double ec = std::sqrt(e2);
+ const double m0 = msfn(phi0, e2);
+ const double t0 = tsfn(phi0, ec);
+ const double n = sin(phi0);
+ if (std::fabs(n) < 1e-10)
+ return nullptr;
+ if (fabs(k0 - 1.0) <= 1e-10) {
+ auto conv = createLambertConicConformal_2SP(
+ util::PropertyMap(), latitudeOfOrigin,
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ latitudeOfOrigin, latitudeOfOrigin,
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
+ conv->setCRSs(this, false);
+ return conv.as_nullable();
+ } else {
+ const double K = k0 * m0 / std::pow(t0, n);
+ const double phi1 =
+ std::asin(find_zero_lcc_1sp_to_2sp_f(n, true, K, ec));
+ const double phi2 =
+ std::asin(find_zero_lcc_1sp_to_2sp_f(n, false, K, ec));
+ double phi1Deg = RadToDeg(phi1);
+ double phi2Deg = RadToDeg(phi2);
+
+ // Try to round to hundreth of degree if very close to it
+ if (std::fabs(phi1Deg * 1000 - std::floor(phi1Deg * 1000 + 0.5)) <
+ 1e-8)
+ phi1Deg = floor(phi1Deg * 1000 + 0.5) / 1000;
+ if (std::fabs(phi2Deg * 1000 - std::floor(phi2Deg * 1000 + 0.5)) <
+ 1e-8)
+ phi2Deg = std::floor(phi2Deg * 1000 + 0.5) / 1000;
+
+ // The following improvement is too turn the LCC1SP equivalent of
+ // EPSG:2154 to the real LCC2SP
+ // If the computed latitude of origin is close to .0 or .5 degrees
+ // then check if rounding it to it will get a false northing
+ // close to an integer
+ const double FN =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
+ const double latitudeOfOriginDeg =
+ latitudeOfOrigin.convertToUnit(common::UnitOfMeasure::DEGREE);
+ if (std::fabs(latitudeOfOriginDeg * 2 -
+ std::floor(latitudeOfOriginDeg * 2 + 0.5)) < 0.2) {
+ const double dfRoundedLatOfOrig =
+ std::floor(latitudeOfOriginDeg * 2 + 0.5) / 2;
+ const double m1 = msfn(phi1, e2);
+ const double t1 = tsfn(phi1, ec);
+ const double F = m1 / (n * std::pow(t1, n));
+ const double a =
+ geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
+ const double tRoundedLatOfOrig =
+ tsfn(DegToRad(dfRoundedLatOfOrig), ec);
+ const double FN_correction =
+ a * F * (std::pow(tRoundedLatOfOrig, n) - std::pow(t0, n));
+ const double FN_corrected = FN - FN_correction;
+ const double FN_corrected_rounded =
+ std::floor(FN_corrected + 0.5);
+ if (std::fabs(FN_corrected - FN_corrected_rounded) < 1e-8) {
+ auto conv = createLambertConicConformal_2SP(
+ util::PropertyMap(),
+ common::Angle(dfRoundedLatOfOrig,
+ common::UnitOfMeasure::DEGREE),
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE),
+ common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE),
+ common::Length(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(FN_corrected_rounded));
+ conv->setCRSs(this, false);
+ return conv.as_nullable();
+ }
+ }
+
+ auto conv = createLambertConicConformal_2SP(
+ util::PropertyMap(), latitudeOfOrigin,
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE),
+ common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(FN));
+ conv->setCRSs(this, false);
+ return conv.as_nullable();
+ }
+ }
+
+ if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
+ targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP) {
+ // Notations m0, t0, m1, t1, m2, t2 n, F are those of the EPSG guidance
+ // "1.3.1.1 Lambert Conic Conformal (2SP)" and
+ // "1.3.1.2 Lambert Conic Conformal (1SP)" and
+ // or Snyder pages 106-109
+ const double phiF =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN)
+ .getSIValue();
+ const double phi1 =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL)
+ .getSIValue();
+ const double phi2 =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL)
+ .getSIValue();
+ if (!(std::fabs(phiF) < M_PI / 2))
+ return nullptr;
+ if (!(std::fabs(phi1) < M_PI / 2))
+ return nullptr;
+ if (!(std::fabs(phi2) < M_PI / 2))
+ return nullptr;
+ const double ec = std::sqrt(e2);
+ const double m1 = msfn(phi1, e2);
+ const double m2 = msfn(phi2, e2);
+ const double t1 = tsfn(phi1, ec);
+ const double t2 = tsfn(phi2, ec);
+ const double n_denom = std::log(t1) - std::log(t2);
+ const double n = (std::fabs(n_denom) < 1e-10)
+ ? std::sin(phi1)
+ : (std::log(m1) - std::log(m2)) / n_denom;
+ if (std::fabs(n) < 1e-10)
+ return nullptr;
+ const double F = m1 / (n * std::pow(t1, n));
+ const double phi0 = std::asin(n);
+ const double m0 = msfn(phi0, e2);
+ const double t0 = tsfn(phi0, ec);
+ const double F0 = m0 / (n * std::pow(t0, n));
+ const double k0 = F / F0;
+ const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
+ const double tF = tsfn(phiF, ec);
+ const double FN_correction =
+ a * F * (std::pow(tF, n) - std::pow(t0, n));
+
+ double phi0Deg = RadToDeg(phi0);
+ // Try to round to thousandth of degree if very close to it
+ if (std::fabs(phi0Deg * 1000 - std::floor(phi0Deg * 1000 + 0.5)) < 1e-8)
+ phi0Deg = std::floor(phi0Deg * 1000 + 0.5) / 1000;
+
+ auto conv = createLambertConicConformal_1SP(
+ util::PropertyMap(),
+ common::Angle(phi0Deg, common::UnitOfMeasure::DEGREE),
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN)),
+ common::Scale(k0), common::Length(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN)),
+ common::Length(
+ parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN) +
+ (std::fabs(FN_correction) > 1e-8 ? FN_correction : 0)));
+ conv->setCRSs(this, false);
+ return conv.as_nullable();
+ }
+
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static const ESRIMethodMapping *getESRIMapping(const std::string &wkt2_name,
+ int epsg_code) {
+ size_t nEsriMappings = 0;
+ const auto esriMappings = getEsriMappings(nEsriMappings);
+ for (size_t i = 0; i < nEsriMappings; ++i) {
+ const auto &mapping = esriMappings[i];
+ if ((epsg_code != 0 && mapping.epsg_code == epsg_code) ||
+ ci_equal(wkt2_name, mapping.wkt2_name)) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+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 (ci_find(conv->nameStr(), "Gauss Kruger") != std::string::npos ||
+ (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);
+ }
+
+#ifdef DEBUG_CONVERSION_ID
+ if (sourceCRS() && targetCRS()) {
+ formatter->startNode("SOURCECRS_ID", false);
+ sourceCRS()->formatID(formatter);
+ formatter->endNode();
+ formatter->startNode("TARGETCRS_ID", false);
+ targetCRS()->formatID(formatter);
+ formatter->endNode();
+ }
+#endif
+
+ 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;
+ }
+ }
+ }
+ }
+ // Same for false easting / false northing for Vertical Perspective
+ else if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE) {
+ auto opParamvalue =
+ dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto paramEPSGCode =
+ opParamvalue->parameter()->getEPSGCode();
+ if (paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_EASTING ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_NORTHING) {
+ 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
+void Conversion::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("Conversion", !identifiers().empty()));
+
+ writer->AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer->Add("unnamed");
+ } else {
+ writer->Add(l_name);
+ }
+
+ writer->AddObjKey("method");
+ formatter->setOmitTypeInImmediateChild();
+ formatter->setAllowIDInImmediateChild();
+ method()->_exportToJSON(formatter);
+
+ const auto &l_parameterValues = parameterValues();
+ if (!l_parameterValues.empty()) {
+ writer->AddObjKey("parameters");
+ {
+ auto parametersContext(writer->MakeArrayContext(false));
+ for (const auto &genOpParamvalue : l_parameterValues) {
+ formatter->setAllowIDInImmediateChild();
+ formatter->setOmitTypeInImmediateChild();
+ genOpParamvalue->_exportToJSON(formatter);
+ }
+ }
+ }
+
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+}
+
+//! @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;
+ }
+
+ std::string units("m");
+ auto targetCRS = conv->targetCRS();
+ auto targetProjCRS =
+ dynamic_cast<const crs::ProjectedCRS *>(targetCRS.get());
+ if (targetProjCRS) {
+ const auto &axisList = targetProjCRS->coordinateSystem()->axisList();
+ const auto &unit = axisList[0]->unit();
+ if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE,
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto projUnit = unit.exportToPROJString();
+ if (!projUnit.empty()) {
+ units = projUnit;
+ } else {
+ 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", units);
+ 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();
+ if (l_method->getPrivate()->projMethodOverride_ == "tmerc approx" ||
+ l_method->getPrivate()->projMethodOverride_ == "utm approx") {
+ auto projFormatter = io::PROJStringFormatter::create();
+ projFormatter->setCRSExport(true);
+ projFormatter->setUseApproxTMerc(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();
+ projFormatter->setCRSExport(true);
+ 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();
+ projFormatter->setCRSExport(true);
+ 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();
+ projFormatter->setCRSExport(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;
+ }
+ }
+ 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 isHeightDepthReversal =
+ methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL;
+ const bool applySourceCRSModifiers =
+ !isZUnitConversion && !isAffineParametric &&
+ !isAxisOrderReversal(methodEPSGCode) && !isGeographicGeocentric &&
+ !isHeightDepthReversal;
+ bool applyTargetCRSModifiers = applySourceCRSModifiers;
+
+ if (formatter->getCRSExport()) {
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TOPOCENTRIC ||
+ methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) {
+ throw io::FormattingException("Transformation cannot be exported "
+ "as a PROJ.4 string (but can be part "
+ "of a PROJ pipeline)");
+ }
+ }
+
+ auto l_sourceCRS = sourceCRS();
+ crs::GeographicCRSPtr srcGeogCRS;
+ if (!formatter->getCRSExport() && l_sourceCRS && applySourceCRSModifiers) {
+
+ crs::CRSPtr horiz = l_sourceCRS;
+ const auto compound =
+ dynamic_cast<const crs::CompoundCRS *>(l_sourceCRS.get());
+ if (compound) {
+ const auto &components = compound->componentReferenceSystems();
+ if (!components.empty()) {
+ horiz = components.front().as_nullable();
+ }
+ }
+
+ srcGeogCRS = std::dynamic_pointer_cast<crs::GeographicCRS>(horiz);
+ if (srcGeogCRS) {
+ formatter->setOmitProjLongLatIfPossible(true);
+ formatter->startInversion();
+ srcGeogCRS->_exportToPROJString(formatter);
+ formatter->stopInversion();
+ formatter->setOmitProjLongLatIfPossible(false);
+ }
+
+ auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(horiz.get());
+ if (projCRS) {
+ formatter->startInversion();
+ formatter->pushOmitZUnitConversion();
+ projCRS->addUnitConvertAndAxisSwap(formatter, false);
+ formatter->popOmitZUnitConversion();
+ formatter->stopInversion();
+ }
+ }
+
+ const auto &convName = nameStr();
+ bool bConversionDone = false;
+ bool bEllipsoidParametersDone = false;
+ bool useApprox = false;
+ if (methodEPSGCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
+ // Check for UTM
+ int zone = 0;
+ bool north = true;
+ useApprox =
+ formatter->getUseApproxTMerc() ||
+ l_method->getPrivate()->projMethodOverride_ == "tmerc approx" ||
+ l_method->getPrivate()->projMethodOverride_ == "utm approx";
+ if (isUTM(zone, north)) {
+ bConversionDone = true;
+ formatter->addStep("utm");
+ if (useApprox) {
+ formatter->addParam("approx");
+ }
+ 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);
+ }
+ } else if (methodEPSGCode ==
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
+ // We map TMSO to tmerc with axis=wsu. This only works if false easting
+ // and northings are zero, which is the case in practice for South
+ // African and Namibian EPSG CRS
+ const auto falseEasting = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_FALSE_EASTING, common::UnitOfMeasure::METRE);
+ if (falseEasting != 0) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_FALSE_EASTING);
+ }
+ const auto falseNorthing = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, common::UnitOfMeasure::METRE);
+ if (falseNorthing != 0) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_FALSE_NORTHING);
+ }
+ // PROJ.4 specific hack for webmercator
+ } else if (formatter->getCRSExport() &&
+ 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;
+ applyTargetCRSModifiers = false;
+ } else if (ci_equal(convName, "Popular Visualisation Mercator")) {
+ if (formatter->getCRSExport()) {
+ if (!createPROJ4WebMercator(this, formatter)) {
+ throw io::FormattingException(concat(
+ "Cannot export ", convName,
+ " as PROJ.4 string outside of a ProjectedCRS context"));
+ }
+ applyTargetCRSModifiers = false;
+ } 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 (ci_equal(methodName,
+ PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION)) {
+ double southPoleLat = parameterValueNumeric(
+ PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION,
+ common::UnitOfMeasure::DEGREE);
+ double southPoleLon = parameterValueNumeric(
+ PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION,
+ common::UnitOfMeasure::DEGREE);
+ double rotation = parameterValueNumeric(
+ PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION,
+ common::UnitOfMeasure::DEGREE);
+ formatter->addStep("ob_tran");
+ formatter->addParam("o_proj", "longlat");
+ formatter->addParam("o_lon_p", -rotation);
+ formatter->addParam("o_lat_p", -southPoleLat);
+ formatter->addParam("lon_0", southPoleLon);
+ bConversionDone = true;
+ } else if (ci_equal(methodName, "Adams_Square_II")) {
+ // Look for ESRI method and parameter names (to be opposed
+ // to the OGC WKT2 names we use elsewhere, because there's no mapping
+ // of those parameters to OGC WKT2)
+ // We also reject non-default values for a number of parameters,
+ // because they are not implemented on PROJ side. The subset we
+ // support can handle ESRI:54098 WGS_1984_Adams_Square_II, but not
+ // ESRI:54099 WGS_1984_Spilhaus_Ocean_Map_in_Square
+ const double falseEasting = parameterValueNumeric(
+ "False_Easting", common::UnitOfMeasure::METRE);
+ const double falseNorthing = parameterValueNumeric(
+ "False_Northing", common::UnitOfMeasure::METRE);
+ const double scaleFactor =
+ parameterValue("Scale_Factor", 0)
+ ? parameterValueNumeric("Scale_Factor",
+ common::UnitOfMeasure::SCALE_UNITY)
+ : 1.0;
+ const double azimuth =
+ parameterValueNumeric("Azimuth", common::UnitOfMeasure::DEGREE);
+ const double longitudeOfCenter = parameterValueNumeric(
+ "Longitude_Of_Center", common::UnitOfMeasure::DEGREE);
+ const double latitudeOfCenter = parameterValueNumeric(
+ "Latitude_Of_Center", common::UnitOfMeasure::DEGREE);
+ const double XYPlaneRotation = parameterValueNumeric(
+ "XY_Plane_Rotation", common::UnitOfMeasure::DEGREE);
+ if (scaleFactor != 1.0 || azimuth != 0.0 || latitudeOfCenter != 0.0 ||
+ XYPlaneRotation != 0.0) {
+ throw io::FormattingException("Unsupported value for one or "
+ "several parameters of "
+ "Adams_Square_II");
+ }
+ formatter->addStep("adams_ws2");
+ formatter->addParam("lon_0", longitudeOfCenter);
+ formatter->addParam("x_0", falseEasting);
+ formatter->addParam("y_0", falseNorthing);
+ bConversionDone = true;
+ } 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 =
+ convFactor == 0.0
+ ? std::string()
+ : common::UnitOfMeasure(std::string(), 1.0 / convFactor,
+ common::UnitOfMeasure::Type::LINEAR)
+ .exportToPROJString();
+ if (uom == "m") {
+ // do nothing
+ } else 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;
+ } else if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) {
+ if (!srcGeogCRS) {
+ throw io::FormattingException(
+ "Export of Geographic/Topocentric conversion to a PROJ string "
+ "requires an input geographic CRS");
+ }
+
+ formatter->addStep("cart");
+ srcGeogCRS->ellipsoid()->_exportToPROJString(formatter);
+
+ formatter->addStep("topocentric");
+ const auto latOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ const auto lonOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ const auto heightOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN,
+ common::UnitOfMeasure::METRE);
+ formatter->addParam("lat_0", latOrigin);
+ formatter->addParam("lon_0", lonOrigin);
+ formatter->addParam("h_0", heightOrigin);
+ bConversionDone = true;
+ }
+
+ auto l_targetCRS = targetCRS();
+
+ bool bAxisSpecFound = false;
+ if (!bConversionDone) {
+ const MethodMapping *mapping = getMapping(l_method.get());
+ if (mapping && mapping->proj_name_main) {
+ formatter->addStep(mapping->proj_name_main);
+ if (useApprox) {
+ formatter->addParam("approx");
+ }
+ if (mapping->proj_name_aux) {
+ bool addAux = true;
+ if (internal::starts_with(mapping->proj_name_aux, "axis=")) {
+ if (mapping->epsg_code == EPSG_CODE_METHOD_KROVAK) {
+ auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(
+ l_targetCRS.get());
+ if (projCRS) {
+ const auto &axisList =
+ projCRS->coordinateSystem()->axisList();
+ if (axisList[0]->direction() ==
+ cs::AxisDirection::WEST &&
+ axisList[1]->direction() ==
+ cs::AxisDirection::SOUTH) {
+ formatter->addParam("czech");
+ addAux = false;
+ }
+ }
+ }
+ bAxisSpecFound = true;
+ }
+
+ // No need to add explicit f=0 if the ellipsoid is a sphere
+ if (strcmp(mapping->proj_name_aux, "f=0") == 0) {
+ crs::CRS *horiz = l_sourceCRS.get();
+ const auto compound =
+ dynamic_cast<const crs::CompoundCRS *>(horiz);
+ if (compound) {
+ const auto &components =
+ compound->componentReferenceSystems();
+ if (!components.empty()) {
+ horiz = components.front().get();
+ }
+ }
+
+ auto geogCRS =
+ dynamic_cast<const crs::GeographicCRS *>(horiz);
+ if (geogCRS && geogCRS->ellipsoid()->isSphere()) {
+ addAux = false;
+ }
+ }
+
+ if (addAux) {
+ 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;
+ }
+ const auto &value =
+ parameterValueMeasure(param->wkt2_name, param->epsg_code);
+ double valueConverted = 0;
+ if (value == nullMeasure) {
+ // Deal with missing values. In an ideal world, this would
+ // not happen
+ if (param->epsg_code ==
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) {
+ valueConverted = 1.0;
+ }
+ } else if (param->unit_type ==
+ common::UnitOfMeasure::Type::ANGULAR) {
+ valueConverted =
+ value.convertToUnit(common::UnitOfMeasure::DEGREE);
+ } else {
+ valueConverted = value.getSIValue();
+ }
+
+ if (mapping->epsg_code ==
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
+ strcmp(param->proj_name, "lat_1") == 0) {
+ formatter->addParam(param->proj_name, valueConverted);
+ formatter->addParam("lat_0", valueConverted);
+ } else {
+ formatter->addParam(param->proj_name, valueConverted);
+ }
+ }
+
+ } else {
+ if (!exportToPROJStringGeneric(formatter)) {
+ throw io::FormattingException(
+ concat("Unsupported conversion method: ", methodName));
+ }
+ }
+ }
+
+ if (l_targetCRS && applyTargetCRSModifiers) {
+ crs::CRS *horiz = l_targetCRS.get();
+ const auto compound = dynamic_cast<const crs::CompoundCRS *>(horiz);
+ if (compound) {
+ const auto &components = compound->componentReferenceSystems();
+ if (!components.empty()) {
+ horiz = components.front().get();
+ }
+ }
+
+ if (!bEllipsoidParametersDone) {
+ auto targetGeodCRS = horiz->extractGeodeticCRS();
+ auto targetGeogCRS =
+ std::dynamic_pointer_cast<crs::GeographicCRS>(targetGeodCRS);
+ if (targetGeogCRS) {
+ if (formatter->getCRSExport()) {
+ targetGeogCRS->addDatumInfoToPROJString(formatter);
+ } else {
+ targetGeogCRS->ellipsoid()->_exportToPROJString(formatter);
+ targetGeogCRS->primeMeridian()->_exportToPROJString(
+ formatter);
+ }
+ } else if (targetGeodCRS) {
+ targetGeodCRS->ellipsoid()->_exportToPROJString(formatter);
+ }
+ }
+
+ auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(horiz);
+ if (projCRS) {
+ formatter->pushOmitZUnitConversion();
+ projCRS->addUnitConvertAndAxisSwap(formatter, bAxisSpecFound);
+ formatter->popOmitZUnitConversion();
+ }
+
+ auto derivedGeographicCRS =
+ dynamic_cast<const crs::DerivedGeographicCRS *>(horiz);
+ if (!formatter->getCRSExport() && derivedGeographicCRS) {
+ formatter->setOmitProjLongLatIfPossible(true);
+ derivedGeographicCRS->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->setOmitProjLongLatIfPossible(false);
+ }
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether a conversion is a [Universal Transverse Mercator]
+ * (https://proj.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;
+}
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
diff --git a/src/iso19111/operation/coordinateoperation_internal.hpp b/src/iso19111/operation/coordinateoperation_internal.hpp
new file mode 100644
index 00000000..d890f710
--- /dev/null
+++ b/src/iso19111/operation/coordinateoperation_internal.hpp
@@ -0,0 +1,274 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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
+#error This file should only be included from a PROJ cpp file
+#endif
+
+#ifndef COORDINATEOPERATION_INTERNAL_HH_INCLUDED
+#define COORDINATEOPERATION_INTERNAL_HH_INCLUDED
+
+#include "proj/coordinateoperation.hpp"
+
+#include <vector>
+
+//! @cond Doxygen_Suppress
+
+NS_PROJ_START
+
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+bool isAxisOrderReversal(int methodEPSGCode);
+
+// ---------------------------------------------------------------------------
+
+class InverseCoordinateOperation;
+/** Shared pointer of InverseCoordinateOperation */
+using InverseCoordinateOperationPtr =
+ std::shared_ptr<InverseCoordinateOperation>;
+/** Non-null shared pointer of InverseCoordinateOperation */
+using InverseCoordinateOperationNNPtr = util::nn<InverseCoordinateOperationPtr>;
+
+/** \brief Inverse operation of a CoordinateOperation.
+ *
+ * This is used when there is no straightforward way of building another
+ * subclass of CoordinateOperation that models the inverse operation.
+ */
+class InverseCoordinateOperation : virtual public CoordinateOperation {
+ public:
+ InverseCoordinateOperation(
+ const CoordinateOperationNNPtr &forwardOperationIn,
+ bool wktSupportsInversion);
+
+ ~InverseCoordinateOperation() override;
+
+ void _exportToPROJString(io::PROJStringFormatter *formatter)
+ const override; // throw(FormattingException)
+
+ bool _isEquivalentTo(
+ const util::IComparable *other,
+ util::IComparable::Criterion criterion =
+ util::IComparable::Criterion::STRICT,
+ const io::DatabaseContextPtr &dbContext = nullptr) const override;
+
+ CoordinateOperationNNPtr inverse() const override;
+
+ const CoordinateOperationNNPtr &forwardOperation() const {
+ return forwardOperation_;
+ }
+
+ protected:
+ CoordinateOperationNNPtr forwardOperation_;
+ bool wktSupportsInversion_;
+
+ void setPropertiesFromForward();
+};
+
+// ---------------------------------------------------------------------------
+
+/** \brief Inverse of a conversion. */
+class InverseConversion : public Conversion, public InverseCoordinateOperation {
+ public:
+ explicit InverseConversion(const ConversionNNPtr &forward);
+
+ ~InverseConversion() override;
+
+ void _exportToWKT(io::WKTFormatter *formatter) const override {
+ Conversion::_exportToWKT(formatter);
+ }
+
+ void _exportToJSON(io::JSONFormatter *formatter) const override {
+ Conversion::_exportToJSON(formatter);
+ }
+
+ void
+ _exportToPROJString(io::PROJStringFormatter *formatter) const override {
+ InverseCoordinateOperation::_exportToPROJString(formatter);
+ }
+
+ bool _isEquivalentTo(
+ const util::IComparable *other,
+ util::IComparable::Criterion criterion =
+ util::IComparable::Criterion::STRICT,
+ const io::DatabaseContextPtr &dbContext = nullptr) const override {
+ return InverseCoordinateOperation::_isEquivalentTo(other, criterion,
+ dbContext);
+ }
+
+ CoordinateOperationNNPtr inverse() const override {
+ return InverseCoordinateOperation::inverse();
+ }
+
+ ConversionNNPtr inverseAsConversion() const;
+
+#ifdef _MSC_VER
+ // To avoid a warning C4250: 'osgeo::proj::operation::InverseConversion':
+ // inherits
+ // 'osgeo::proj::operation::SingleOperation::osgeo::proj::operation::SingleOperation::gridsNeeded'
+ // via dominance
+ std::set<GridDescription>
+ gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const override {
+ return SingleOperation::gridsNeeded(databaseContext,
+ considerKnownGridsAsAvailable);
+ }
+#endif
+
+ static CoordinateOperationNNPtr create(const ConversionNNPtr &forward);
+
+ CoordinateOperationNNPtr _shallowClone() const override;
+};
+
+// ---------------------------------------------------------------------------
+
+/** \brief Inverse of a transformation. */
+class InverseTransformation : public Transformation,
+ public InverseCoordinateOperation {
+ public:
+ explicit InverseTransformation(const TransformationNNPtr &forward);
+
+ ~InverseTransformation() override;
+
+ void _exportToWKT(io::WKTFormatter *formatter) const override;
+
+ void
+ _exportToPROJString(io::PROJStringFormatter *formatter) const override {
+ return InverseCoordinateOperation::_exportToPROJString(formatter);
+ }
+
+ void _exportToJSON(io::JSONFormatter *formatter) const override {
+ Transformation::_exportToJSON(formatter);
+ }
+
+ bool _isEquivalentTo(
+ const util::IComparable *other,
+ util::IComparable::Criterion criterion =
+ util::IComparable::Criterion::STRICT,
+ const io::DatabaseContextPtr &dbContext = nullptr) const override {
+ return InverseCoordinateOperation::_isEquivalentTo(other, criterion,
+ dbContext);
+ }
+
+ CoordinateOperationNNPtr inverse() const override {
+ return InverseCoordinateOperation::inverse();
+ }
+
+ TransformationNNPtr inverseAsTransformation() const;
+
+#ifdef _MSC_VER
+ // To avoid a warning C4250:
+ // 'osgeo::proj::operation::InverseTransformation': inherits
+ // 'osgeo::proj::operation::SingleOperation::osgeo::proj::operation::SingleOperation::gridsNeeded'
+ // via dominance
+ std::set<GridDescription>
+ gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const override {
+ return SingleOperation::gridsNeeded(databaseContext,
+ considerKnownGridsAsAvailable);
+ }
+#endif
+
+ static TransformationNNPtr create(const TransformationNNPtr &forward);
+
+ CoordinateOperationNNPtr _shallowClone() const override;
+};
+
+// ---------------------------------------------------------------------------
+
+class PROJBasedOperation;
+/** Shared pointer of PROJBasedOperation */
+using PROJBasedOperationPtr = std::shared_ptr<PROJBasedOperation>;
+/** Non-null shared pointer of PROJBasedOperation */
+using PROJBasedOperationNNPtr = util::nn<PROJBasedOperationPtr>;
+
+/** \brief A PROJ-string based coordinate operation.
+ */
+class PROJBasedOperation : public SingleOperation {
+ public:
+ ~PROJBasedOperation() override;
+
+ void _exportToWKT(io::WKTFormatter *formatter)
+ const override; // throw(io::FormattingException)
+
+ CoordinateOperationNNPtr inverse() const override;
+
+ static PROJBasedOperationNNPtr
+ create(const util::PropertyMap &properties, const std::string &PROJString,
+ const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies);
+
+ static PROJBasedOperationNNPtr
+ create(const util::PropertyMap &properties,
+ const io::IPROJStringExportableNNPtr &projExportable, bool inverse,
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::CRSPtr &interpolationCRS,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies,
+ bool hasRoughTransformation);
+
+ std::set<GridDescription>
+ gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const override;
+
+ protected:
+ PROJBasedOperation(const PROJBasedOperation &) = default;
+ explicit PROJBasedOperation(const OperationMethodNNPtr &methodIn);
+
+ void _exportToPROJString(io::PROJStringFormatter *formatter)
+ const override; // throw(FormattingException)
+
+ void _exportToJSON(io::JSONFormatter *formatter)
+ const override; // throw(FormattingException)
+
+ CoordinateOperationNNPtr _shallowClone() const override;
+
+ INLINED_MAKE_SHARED
+
+ private:
+ std::string projString_{};
+ io::IPROJStringExportablePtr projStringExportable_{};
+ bool inverse_ = false;
+};
+
+// ---------------------------------------------------------------------------
+
+class InvalidOperationEmptyIntersection : public InvalidOperation {
+ public:
+ explicit InvalidOperationEmptyIntersection(const std::string &message);
+ InvalidOperationEmptyIntersection(
+ const InvalidOperationEmptyIntersection &other);
+ ~InvalidOperationEmptyIntersection() override;
+};
+} // namespace operation
+
+NS_PROJ_END
+
+//! @endcond
+
+#endif // COORDINATEOPERATION_INTERNAL_HH_INCLUDED
diff --git a/src/iso19111/operation/coordinateoperation_private.hpp b/src/iso19111/operation/coordinateoperation_private.hpp
new file mode 100644
index 00000000..42bedd4b
--- /dev/null
+++ b/src/iso19111/operation/coordinateoperation_private.hpp
@@ -0,0 +1,88 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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 COORDINATEROPERATION_PRIVATE_HPP
+#define COORDINATEROPERATION_PRIVATE_HPP
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/util.hpp"
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @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_{};
+ bool hasBallparkTransformation_ = false;
+ bool use3DHelmert_ = false;
+
+ // 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_),
+ hasBallparkTransformation_(other.hasBallparkTransformation_),
+ strongRef_(other.strongRef_ ? internal::make_unique<CRSStrongRef>(
+ *(other.strongRef_))
+ : nullptr) {}
+
+ Private &operator=(const Private &) = delete;
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
+
+#endif // COORDINATEROPERATION_PRIVATE_HPP
diff --git a/src/iso19111/operation/coordinateoperationfactory.cpp b/src/iso19111/operation/coordinateoperationfactory.cpp
new file mode 100644
index 00000000..1e919416
--- /dev/null
+++ b/src/iso19111/operation/coordinateoperationfactory.cpp
@@ -0,0 +1,5257 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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/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 "proj/internal/tracing.hpp"
+
+#include "coordinateoperation_internal.hpp"
+#include "coordinateoperation_private.hpp"
+#include "oputils.hpp"
+
+// PROJ include order is sensitive
+// clang-format off
+#include "proj.h"
+#include "proj_internal.h" // M_PI
+// clang-format on
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+// #define TRACE_CREATE_OPERATIONS
+// #define DEBUG_SORT
+// #define DEBUG_CONCATENATED_OPERATION
+#if defined(DEBUG_SORT) || defined(DEBUG_CONCATENATED_OPERATION)
+#include <iostream>
+
+void dumpWKT(const NS_PROJ::crs::CRS *crs);
+void dumpWKT(const NS_PROJ::crs::CRS *crs) {
+ auto f(NS_PROJ::io::WKTFormatter::create(
+ NS_PROJ::io::WKTFormatter::Convention::WKT2_2019));
+ std::cerr << crs->exportToWKT(f.get()) << std::endl;
+}
+
+void dumpWKT(const NS_PROJ::crs::CRSPtr &crs);
+void dumpWKT(const NS_PROJ::crs::CRSPtr &crs) { dumpWKT(crs.get()); }
+
+void dumpWKT(const NS_PROJ::crs::CRSNNPtr &crs);
+void dumpWKT(const NS_PROJ::crs::CRSNNPtr &crs) {
+ dumpWKT(crs.as_nullable().get());
+}
+
+void dumpWKT(const NS_PROJ::crs::GeographicCRSPtr &crs);
+void dumpWKT(const NS_PROJ::crs::GeographicCRSPtr &crs) { dumpWKT(crs.get()); }
+
+void dumpWKT(const NS_PROJ::crs::GeographicCRSNNPtr &crs);
+void dumpWKT(const NS_PROJ::crs::GeographicCRSNNPtr &crs) {
+ dumpWKT(crs.as_nullable().get());
+}
+
+#endif
+
+using namespace NS_PROJ::internal;
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+#ifdef TRACE_CREATE_OPERATIONS
+
+//! @cond Doxygen_Suppress
+
+static std::string objectAsStr(const common::IdentifiedObject *obj) {
+ std::string ret(obj->nameStr());
+ const auto &ids = obj->identifiers();
+ if (!ids.empty()) {
+ ret += " (";
+ ret += (*ids[0]->codeSpace()) + ":" + ids[0]->code();
+ ret += ")";
+ }
+ return ret;
+}
+//! @endcond
+
+#endif
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+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 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;
+ IntermediateCRSUse allowUseIntermediateCRS_ = CoordinateOperationContext::
+ IntermediateCRSUse::IF_NO_DIRECT_TRANSFORMATION;
+ std::vector<std::pair<std::string, std::string>>
+ intermediateCRSAuthCodes_{};
+ bool discardSuperseded_ = true;
+ bool allowBallpark_ = 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 Return whether ballpark transformations are allowed */
+bool CoordinateOperationContext::getAllowBallparkTransformations() const {
+ return d->allowBallpark_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether ballpark transformations are allowed */
+void CoordinateOperationContext::setAllowBallparkTransformations(bool allow) {
+ d->allowBallpark_ = allow;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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
+ * ALWAYS/IF_NO_DIRECT_TRANSFORMATION, allow chaining both operations.
+ *
+ * The current implementation is limited to researching one intermediate
+ * step.
+ *
+ * By default, with the IF_NO_DIRECT_TRANSFORMATION stratgey, all potential
+ * C candidates will be used if there is no direct transformation.
+ */
+void CoordinateOperationContext::setAllowUseIntermediateCRS(
+ IntermediateCRSUse 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
+ * ALWAYS/IF_NO_DIRECT_TRANSFORMATION, allow chaining both operations.
+ *
+ * The default is IF_NO_DIRECT_TRANSFORMATION.
+ */
+CoordinateOperationContext::IntermediateCRSUse
+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 extent of 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 metadata::ExtentPtr &extent1;
+ const metadata::ExtentPtr &extent2;
+ const CoordinateOperationContextNNPtr &context;
+ bool inCreateOperationsWithDatumPivotAntiRecursion = false;
+ bool inCreateOperationsGeogToVertWithAlternativeGeog = false;
+ bool inCreateOperationsGeogToVertWithIntermediateVert = false;
+ bool skipHorizontalTransformation = false;
+ std::map<std::pair<io::AuthorityFactory::ObjectType, std::string>,
+ std::list<std::pair<std::string, std::string>>>
+ cacheNameToCRS{};
+
+ Context(const metadata::ExtentPtr &extent1In,
+ const metadata::ExtentPtr &extent2In,
+ const CoordinateOperationContextNNPtr &contextIn)
+ : extent1(extent1In), extent2(extent2In), context(contextIn) {}
+ };
+
+ static std::vector<CoordinateOperationNNPtr>
+ createOperations(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS, Context &context);
+
+ private:
+ static constexpr bool disallowEmptyIntersection = true;
+
+ static void
+ buildCRSIds(const crs::CRSNNPtr &crs, Private::Context &context,
+ std::list<std::pair<std::string, std::string>> &ids);
+
+ static std::vector<CoordinateOperationNNPtr> findOpsInRegistryDirect(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, bool &resNonEmptyBeforeFiltering);
+
+ static std::vector<CoordinateOperationNNPtr>
+ findOpsInRegistryDirectTo(const crs::CRSNNPtr &targetCRS,
+ Private::Context &context);
+
+ static std::vector<CoordinateOperationNNPtr>
+ findsOpsInRegistryWithIntermediate(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context,
+ bool useCreateBetweenGeodeticCRSWithDatumBasedIntermediates);
+
+ static void createOperationsFromProj4Ext(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::BoundCRS *boundSrc, const crs::BoundCRS *boundDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static bool createOperationsFromDatabase(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeodeticCRS *geodSrc,
+ const crs::GeodeticCRS *geodDst, const crs::GeographicCRS *geogSrc,
+ const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static std::vector<CoordinateOperationNNPtr>
+ createOperationsGeogToVertFromGeoid(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS,
+ const crs::VerticalCRS *vertDst,
+ Context &context);
+
+ static std::vector<CoordinateOperationNNPtr>
+ createOperationsGeogToVertWithIntermediateVert(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::VerticalCRS *vertDst, Context &context);
+
+ static std::vector<CoordinateOperationNNPtr>
+ createOperationsGeogToVertWithAlternativeGeog(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Context &context);
+
+ static void createOperationsFromDatabaseWithVertCRS(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeographicCRS *geogSrc,
+ const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsGeodToGeod(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeodeticCRS *geodSrc,
+ const crs::GeodeticCRS *geodDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsDerivedTo(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::DerivedCRS *derivedSrc,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsBoundToGeog(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsBoundToVert(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsVertToVert(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::VerticalCRS *vertSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsVertToGeog(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::VerticalCRS *vertSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsVertToGeogBallpark(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::VerticalCRS *vertSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsBoundToBound(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::BoundCRS *boundDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsCompoundToGeog(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::CompoundCRS *compoundSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsToGeod(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeodeticCRS *geodDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsCompoundToCompound(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::CompoundCRS *compoundSrc,
+ const crs::CompoundCRS *compoundDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static void createOperationsBoundToCompound(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::CompoundCRS *compoundDst,
+ std::vector<CoordinateOperationNNPtr> &res);
+
+ static std::vector<CoordinateOperationNNPtr> createOperationsGeogToGeog(
+ std::vector<CoordinateOperationNNPtr> &res,
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, 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 void setCRSs(CoordinateOperation *co, 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 isPROJExportable_ = false;
+ bool hasGrids_ = false;
+ bool gridsAvailable_ = false;
+ bool gridsKnown_ = false;
+ size_t stepCount_ = 0;
+ bool isApprox_ = false;
+ bool hasBallparkVertical_ = false;
+ bool isNullTransformation_ = false;
+
+ PrecomputedOpCharacteristics() = default;
+ PrecomputedOpCharacteristics(double area, double accuracy,
+ bool isPROJExportable, bool hasGrids,
+ bool gridsAvailable, bool gridsKnown,
+ size_t stepCount, bool isApprox,
+ bool hasBallparkVertical,
+ bool isNullTransformation)
+ : area_(area), accuracy_(accuracy), isPROJExportable_(isPROJExportable),
+ hasGrids_(hasGrids), gridsAvailable_(gridsAvailable),
+ gridsKnown_(gridsKnown), stepCount_(stepCount), isApprox_(isApprox),
+ hasBallparkVertical_(hasBallparkVertical),
+ isNullTransformation_(isNullTransformation) {}
+};
+
+// ---------------------------------------------------------------------------
+
+// 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 compare(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.isPROJExportable_ &&
+ !iterB->second.isPROJExportable_) {
+ return true;
+ }
+ if (!iterA->second.isPROJExportable_ &&
+ iterB->second.isPROJExportable_) {
+ return false;
+ }
+
+ if (!iterA->second.isApprox_ && iterB->second.isApprox_) {
+ return true;
+ }
+ if (iterA->second.isApprox_ && !iterB->second.isApprox_) {
+ return false;
+ }
+
+ if (!iterA->second.hasBallparkVertical_ &&
+ iterB->second.hasBallparkVertical_) {
+ return true;
+ }
+ if (iterA->second.hasBallparkVertical_ &&
+ !iterB->second.hasBallparkVertical_) {
+ return false;
+ }
+
+ if (!iterA->second.isNullTransformation_ &&
+ iterB->second.isNullTransformation_) {
+ return true;
+ }
+ if (iterA->second.isNullTransformation_ &&
+ !iterB->second.isNullTransformation_) {
+ return false;
+ }
+
+ // 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;
+ }
+
+ 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;
+ }
+ }
+
+ // 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;
+ }
+ }
+
+ // 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. We actually return the greater element
+ // first, so that "Amersfoort to WGS 84 (4)" is presented before
+ // "Amersfoort to WGS 84 (3)", which is probably a better guess.
+
+ // Except for French NTF (Paris) to NTF, where the (1) conversion
+ // should be preferred because in the remarks of (2), it is mentioned
+ // OGP prefers value from IGN Paris (code 1467)...
+ if (a_name.find("NTF (Paris) to NTF (1)") != std::string::npos &&
+ b_name.find("NTF (Paris) to NTF (2)") != std::string::npos) {
+ return true;
+ }
+ if (a_name.find("NTF (Paris) to NTF (2)") != std::string::npos &&
+ b_name.find("NTF (Paris) to NTF (1)") != std::string::npos) {
+ return false;
+ }
+ if (a_name.find("NTF (Paris) to RGF93 (1)") != std::string::npos &&
+ b_name.find("NTF (Paris) to RGF93 (2)") != std::string::npos) {
+ return true;
+ }
+ if (a_name.find("NTF (Paris) to RGF93 (2)") != std::string::npos &&
+ b_name.find("NTF (Paris) to RGF93 (1)") != std::string::npos) {
+ return false;
+ }
+
+ return a_name > b_name;
+ }
+
+ bool operator()(const CoordinateOperationNNPtr &a,
+ const CoordinateOperationNNPtr &b) const {
+ const bool ret = compare(a, b);
+#if 0
+ std::cerr << a->nameStr() << " < " << b->nameStr() << " : " << ret << std::endl;
+#endif
+ return ret;
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+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;
+}
+
+// ---------------------------------------------------------------------------
+
+// Return number of steps that are transformations (and not conversions)
+static size_t getTransformationStepCount(const CoordinateOperationNNPtr &op) {
+ auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get());
+ size_t stepCount = 1;
+ if (concat) {
+ stepCount = 0;
+ for (const auto &subOp : concat->operations()) {
+ if (dynamic_cast<const Conversion *>(subOp.get()) == nullptr) {
+ stepCount++;
+ }
+ }
+ }
+ return stepCount;
+}
+
+// ---------------------------------------------------------------------------
+
+static bool isNullTransformation(const std::string &name) {
+ if (name.find(" + ") != std::string::npos)
+ return false;
+ return starts_with(name, BALLPARK_GEOCENTRIC_TRANSLATION) ||
+ starts_with(name, BALLPARK_GEOGRAPHIC_OFFSET) ||
+ starts_with(name, NULL_GEOGRAPHIC_OFFSET) ||
+ starts_with(name, NULL_GEOCENTRIC_TRANSLATION);
+}
+
+// ---------------------------------------------------------------------------
+
+struct FilterResults {
+
+ FilterResults(const std::vector<CoordinateOperationNNPtr> &sourceListIn,
+ const CoordinateOperationContextNNPtr &contextIn,
+ const metadata::ExtentPtr &extent1In,
+ const metadata::ExtentPtr &extent2In,
+ bool forceStrictContainmentTest)
+ : sourceList(sourceListIn), context(contextIn), extent1(extent1In),
+ extent2(extent2In), areaOfInterest(context->getAreaOfInterest()),
+ desiredAccuracy(context->getDesiredAccuracy()),
+ sourceAndTargetCRSExtentUse(
+ context->getSourceAndTargetCRSExtentUse()) {
+
+ computeAreaOfInterest();
+ 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 metadata::ExtentPtr &extent1;
+ const metadata::ExtentPtr &extent2;
+ metadata::ExtentPtr areaOfInterest;
+ const double desiredAccuracy = context->getDesiredAccuracy();
+ const CoordinateOperationContext::SourceTargetCRSExtentUse
+ sourceAndTargetCRSExtentUse;
+
+ bool hasOpThatContainsAreaOfInterestAndNoGrid = false;
+ std::vector<CoordinateOperationNNPtr> res{};
+
+ // ----------------------------------------------------------------------
+ void computeAreaOfInterest() {
+
+ // Compute an area of interest from the CRS extent if the user did
+ // not specify one
+ if (!areaOfInterest) {
+ if (sourceAndTargetCRSExtentUse ==
+ CoordinateOperationContext::SourceTargetCRSExtentUse::
+ INTERSECTION) {
+ if (extent1 && extent2) {
+ areaOfInterest =
+ extent1->intersection(NN_NO_CHECK(extent2));
+ }
+ } else if (sourceAndTargetCRSExtentUse ==
+ CoordinateOperationContext::SourceTargetCRSExtentUse::
+ SMALLEST) {
+ if (extent1 && extent2) {
+ if (getPseudoArea(extent1) < getPseudoArea(extent2)) {
+ areaOfInterest = extent1;
+ } else {
+ areaOfInterest = extent2;
+ }
+ } else if (extent1) {
+ areaOfInterest = extent1;
+ } else {
+ areaOfInterest = extent2;
+ }
+ }
+ }
+ }
+
+ // ---------------------------------------------------------------------------
+
+ 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();
+ bool hasOnlyBallpark = true;
+ bool hasNonBallparkWithoutExtent = false;
+ bool hasNonBallparkOpWithExtent = false;
+ const bool allowBallpark = context->getAllowBallparkTransformations();
+ for (const auto &op : sourceList) {
+ if (desiredAccuracy != 0) {
+ const double accuracy = getAccuracy(op);
+ if (accuracy < 0 || accuracy > desiredAccuracy) {
+ continue;
+ }
+ }
+ if (!allowBallpark && op->hasBallparkTransformation()) {
+ continue;
+ }
+ if (areaOfInterest) {
+ bool emptyIntersection = false;
+ auto extent = getExtent(op, true, emptyIntersection);
+ if (!extent) {
+ if (!op->hasBallparkTransformation()) {
+ hasNonBallparkWithoutExtent = true;
+ }
+ continue;
+ }
+ if (!op->hasBallparkTransformation()) {
+ hasNonBallparkOpWithExtent = true;
+ }
+ bool extentContains =
+ extent->contains(NN_NO_CHECK(areaOfInterest));
+ if (!hasOpThatContainsAreaOfInterestAndNoGrid &&
+ extentContains) {
+ if (!op->hasBallparkTransformation() &&
+ op->gridsNeeded(nullptr, true).empty()) {
+ hasOpThatContainsAreaOfInterestAndNoGrid = 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) {
+ if (!op->hasBallparkTransformation()) {
+ hasNonBallparkWithoutExtent = true;
+ }
+ continue;
+ }
+ if (!op->hasBallparkTransformation()) {
+ hasNonBallparkOpWithExtent = true;
+ }
+ bool extentContainsExtent1 =
+ !extent1 || extent->contains(NN_NO_CHECK(extent1));
+ bool extentContainsExtent2 =
+ !extent2 || extent->contains(NN_NO_CHECK(extent2));
+ if (!hasOpThatContainsAreaOfInterestAndNoGrid &&
+ extentContainsExtent1 && extentContainsExtent2) {
+ if (!op->hasBallparkTransformation() &&
+ op->gridsNeeded(nullptr, true).empty()) {
+ hasOpThatContainsAreaOfInterestAndNoGrid = true;
+ }
+ }
+ if (spatialCriterion ==
+ CoordinateOperationContext::SpatialCriterion::
+ STRICT_CONTAINMENT) {
+ if (!extentContainsExtent1 || !extentContainsExtent2) {
+ continue;
+ }
+ } else if (spatialCriterion ==
+ CoordinateOperationContext::SpatialCriterion::
+ PARTIAL_INTERSECTION) {
+ bool extentIntersectsExtent1 =
+ !extent1 || extent->intersects(NN_NO_CHECK(extent1));
+ bool extentIntersectsExtent2 =
+ extent2 && extent->intersects(NN_NO_CHECK(extent2));
+ if (!extentIntersectsExtent1 || !extentIntersectsExtent2) {
+ continue;
+ }
+ }
+ }
+ if (!op->hasBallparkTransformation()) {
+ hasOnlyBallpark = false;
+ }
+ res.emplace_back(op);
+ }
+
+ // In case no operation has an extent and no result is found,
+ // retain all initial operations that match accuracy criterion.
+ if ((res.empty() && !hasNonBallparkOpWithExtent) ||
+ (hasOnlyBallpark && hasNonBallparkWithoutExtent)) {
+ for (const auto &op : sourceList) {
+ if (desiredAccuracy != 0) {
+ const double accuracy = getAccuracy(op);
+ if (accuracy < 0 || accuracy > desiredAccuracy) {
+ continue;
+ }
+ }
+ if (!allowBallpark && op->hasBallparkTransformation()) {
+ 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 (extent1 && extent2) {
+ auto x = extentOp->intersection(NN_NO_CHECK(extent1));
+ auto y = extentOp->intersection(NN_NO_CHECK(extent2));
+ area = getPseudoArea(x) + getPseudoArea(y) -
+ ((x && y)
+ ? getPseudoArea(x->intersection(NN_NO_CHECK(y)))
+ : 0.0);
+ } else if (extent1) {
+ area = getPseudoArea(
+ extentOp->intersection(NN_NO_CHECK(extent1)));
+ } else if (extent2) {
+ area = getPseudoArea(
+ extentOp->intersection(NN_NO_CHECK(extent2)));
+ } else {
+ area = getPseudoArea(extentOp);
+ }
+ }
+
+ bool hasGrids = false;
+ bool gridsAvailable = true;
+ bool gridsKnown = true;
+ if (context->getAuthorityFactory()) {
+ const auto gridsNeeded = op->gridsNeeded(
+ context->getAuthorityFactory()->databaseContext(),
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE);
+ for (const auto &gridDesc : gridsNeeded) {
+ hasGrids = true;
+ if (gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ USE_FOR_SORTING &&
+ !gridDesc.available) {
+ gridsAvailable = false;
+ }
+ if (gridDesc.packageName.empty() &&
+ !(!gridDesc.url.empty() && gridDesc.openLicense) &&
+ !gridDesc.available) {
+ gridsKnown = false;
+ }
+ }
+ }
+
+ const auto stepCount = getStepCount(op);
+
+ bool isPROJExportable = false;
+ auto formatter = io::PROJStringFormatter::create();
+ try {
+ op->exportToPROJString(formatter.get());
+ // Grids might be missing, but at least this is something
+ // PROJ could potentially process
+ isPROJExportable = true;
+ } catch (const std::exception &) {
+ }
+
+#if 0
+ std::cerr << op->nameStr() << " ";
+ std::cerr << area << " ";
+ std::cerr << getAccuracy(op) << " ";
+ std::cerr << isPROJExportable << " ";
+ std::cerr << hasGrids << " ";
+ std::cerr << gridsAvailable << " ";
+ std::cerr << gridsKnown << " ";
+ std::cerr << stepCount << " ";
+ std::cerr << op->hasBallparkTransformation() << " ";
+ std::cerr << isNullTransformation(op->nameStr()) << " ";
+ std::cerr << std::endl;
+#endif
+ map[op.get()] = PrecomputedOpCharacteristics(
+ area, getAccuracy(op), isPROJExportable, hasGrids,
+ gridsAvailable, gridsKnown, stepCount,
+ op->hasBallparkTransformation(),
+ op->nameStr().find("ballpark vertical transformation") !=
+ std::string::npos,
+ isNullTransformation(op->nameStr()));
+ }
+
+ // Sort !
+ SortFunction sortFunc(map);
+ std::sort(res.begin(), res.end(), sortFunc);
+
+// Debug code to check consistency of the sort function
+#ifdef DEBUG_SORT
+ constexpr bool debugSort = true;
+#elif !defined(NDEBUG)
+ const bool debugSort = getenv("PROJ_DEBUG_SORT_FUNCT") != nullptr;
+#endif
+#if defined(DEBUG_SORT) || !defined(NDEBUG)
+ if (debugSort) {
+ const bool assertIfIssue =
+ !(getenv("PROJ_DEBUG_SORT_FUNCT_ASSERT") != nullptr);
+ for (size_t i = 0; i < res.size(); ++i) {
+ for (size_t j = i + 1; j < res.size(); ++j) {
+ if (sortFunc(res[j], res[i])) {
+#ifdef DEBUG_SORT
+ std::cerr << "Sorting issue with entry " << i << "("
+ << res[i]->nameStr() << ") and " << j << "("
+ << res[j]->nameStr() << ")" << std::endl;
+#endif
+ if (assertIfIssue) {
+ assert(false);
+ }
+ }
+ }
+ }
+ }
+#endif
+ }
+
+ // ----------------------------------------------------------------------
+
+ void removeSyntheticNullTransforms() {
+
+ // If we have more than one result, and than the last result is the
+ // default "Ballpark geographic offset" or "Ballpark geocentric
+ // translation" operations we have synthetized, and that at least one
+ // operation has the desired area of interest and does not require the
+ // use of grids, remove it as all previous results are necessarily
+ // better
+ if (hasOpThatContainsAreaOfInterestAndNoGrid && res.size() > 1) {
+ const auto &opLast = res.back();
+ if (opLast->hasBallparkTransformation() ||
+ isNullTransformation(opLast->nameStr())) {
+ 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 similar or worse accuracy, but
+ // involve more (non conversion) steps
+ std::vector<CoordinateOperationNNPtr> resTemp;
+ metadata::ExtentPtr lastExtent;
+ double lastAccuracy = -1;
+ 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);
+ const auto curStepCount = getTransformationStepCount(op);
+
+ if (first) {
+ resTemp.emplace_back(op);
+ 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 && curStepCount > lastStepCount) {
+ continue;
+ }
+
+ resTemp.emplace_back(op);
+ }
+
+ lastOp = op.as_nullable();
+ lastStepCount = curStepCount;
+ lastExtent = curExtent;
+ lastAccuracy = curAccuracy;
+ }
+ 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 metadata::ExtentPtr &extent1,
+ const metadata::ExtentPtr &extent2) {
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_FUNCTION();
+ logTrace("number of results before filter and sort: " +
+ toString(static_cast<int>(sourceList.size())));
+#endif
+ auto resFiltered =
+ FilterResults(sourceList, context, extent1, extent2, false)
+ .andSort()
+ .getRes();
+#ifdef TRACE_CREATE_OPERATIONS
+ logTrace("number of results after filter and sort: " +
+ toString(static_cast<int>(resFiltered.size())));
+#endif
+ return resFiltered;
+}
+//! @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) {
+#ifdef DEBUG
+ auto opNew = op->inverse();
+ assert(opNew->targetCRS()->isEquivalentTo(op->sourceCRS().get()));
+ assert(opNew->sourceCRS()->isEquivalentTo(op->targetCRS().get()));
+ op = opNew;
+#else
+ op = op->inverse();
+#endif
+ }
+ return res;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+void CoordinateOperationFactory::Private::buildCRSIds(
+ const crs::CRSNNPtr &crs, Private::Context &context,
+ std::list<std::pair<std::string, std::string>> &ids) {
+ const auto &authFactory = context.context->getAuthorityFactory();
+ assert(authFactory);
+ for (const auto &id : crs->identifiers()) {
+ const auto &authName = *(id->codeSpace());
+ const auto &code = id->code();
+ if (!authName.empty()) {
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(), authName);
+ try {
+ // Consistency check for the ID attached to the object.
+ // See https://github.com/OSGeo/PROJ/issues/1982 where EPSG:4656
+ // is attached to a GeographicCRS whereas it is a ProjectedCRS
+ if (tmpAuthFactory->createCoordinateReferenceSystem(code)
+ ->_isEquivalentTo(
+ crs.get(),
+ util::IComparable::Criterion::
+ EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)) {
+ ids.emplace_back(authName, code);
+ } else {
+ // TODO? log this inconsistency
+ }
+ } catch (const std::exception &) {
+ // TODO? log this inconsistency
+ }
+ }
+ }
+ if (ids.empty()) {
+ std::vector<io::AuthorityFactory::ObjectType> allowedObjects;
+ auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(crs.get());
+ if (geogCRS) {
+ allowedObjects.push_back(
+ geogCRS->coordinateSystem()->axisList().size() == 2
+ ? io::AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS
+ : io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS);
+ } else if (dynamic_cast<crs::ProjectedCRS *>(crs.get())) {
+ allowedObjects.push_back(
+ io::AuthorityFactory::ObjectType::PROJECTED_CRS);
+ } else if (dynamic_cast<crs::VerticalCRS *>(crs.get())) {
+ allowedObjects.push_back(
+ io::AuthorityFactory::ObjectType::VERTICAL_CRS);
+ }
+ if (!allowedObjects.empty()) {
+
+ const std::pair<io::AuthorityFactory::ObjectType, std::string> key(
+ allowedObjects[0], crs->nameStr());
+ auto iter = context.cacheNameToCRS.find(key);
+ if (iter != context.cacheNameToCRS.end()) {
+ ids = iter->second;
+ return;
+ }
+
+ const auto &authFactoryName = authFactory->getAuthority();
+ try {
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(),
+ (authFactoryName.empty() || authFactoryName == "any")
+ ? std::string()
+ : authFactoryName);
+
+ auto matches = tmpAuthFactory->createObjectsFromName(
+ crs->nameStr(), allowedObjects, false, 2);
+ if (matches.size() == 1 &&
+ crs->_isEquivalentTo(
+ matches.front().get(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ !matches.front()->identifiers().empty()) {
+ const auto &tmpIds = matches.front()->identifiers();
+ ids.emplace_back(*(tmpIds[0]->codeSpace()),
+ tmpIds[0]->code());
+ }
+ } catch (const std::exception &) {
+ }
+ context.cacheNameToCRS[key] = ids;
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static std::vector<std::string>
+getCandidateAuthorities(const io::AuthorityFactoryPtr &authFactory,
+ const std::string &srcAuthName,
+ const std::string &targetAuthName) {
+ const auto &authFactoryName = authFactory->getAuthority();
+ 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);
+ }
+ return authorities;
+}
+
+// ---------------------------------------------------------------------------
+
+// Look in the authority registry for operations from sourceCRS to targetCRS
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::Private::findOpsInRegistryDirect(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, bool &resNonEmptyBeforeFiltering) {
+ const auto &authFactory = context.context->getAuthorityFactory();
+ assert(authFactory);
+
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("findOpsInRegistryDirect(" + objectAsStr(sourceCRS.get()) +
+ " --> " + objectAsStr(targetCRS.get()) + ")");
+#endif
+
+ resNonEmptyBeforeFiltering = false;
+ std::list<std::pair<std::string, std::string>> sourceIds;
+ std::list<std::pair<std::string, std::string>> targetIds;
+ buildCRSIds(sourceCRS, context, sourceIds);
+ buildCRSIds(targetCRS, context, targetIds);
+
+ const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
+ for (const auto &idSrc : sourceIds) {
+ const auto &srcAuthName = idSrc.first;
+ const auto &srcCode = idSrc.second;
+ for (const auto &idTarget : targetIds) {
+ const auto &targetAuthName = idTarget.first;
+ const auto &targetCode = idTarget.second;
+
+ const auto authorities(getCandidateAuthorities(
+ authFactory, srcAuthName, targetAuthName));
+ std::vector<CoordinateOperationNNPtr> res;
+ for (const auto &authority : authorities) {
+ const auto authName =
+ authority == "any" ? std::string() : authority;
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(), authName);
+ auto resTmp =
+ tmpAuthFactory->createFromCoordinateReferenceSystemCodes(
+ srcAuthName, srcCode, targetAuthName, targetCode,
+ context.context->getUsePROJAlternativeGridNames(),
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE,
+ context.context->getDiscardSuperseded(), true, false,
+ context.extent1, context.extent2);
+ res.insert(res.end(), resTmp.begin(), resTmp.end());
+ if (authName == "PROJ") {
+ continue;
+ }
+ if (!res.empty()) {
+ resNonEmptyBeforeFiltering = true;
+ auto resFiltered =
+ FilterResults(res, context.context, context.extent1,
+ context.extent2, false)
+ .getRes();
+#ifdef TRACE_CREATE_OPERATIONS
+ logTrace("filtering reduced from " +
+ toString(static_cast<int>(res.size())) + " to " +
+ toString(static_cast<int>(resFiltered.size())));
+#endif
+ return resFiltered;
+ }
+ }
+ }
+ }
+ return std::vector<CoordinateOperationNNPtr>();
+}
+
+// ---------------------------------------------------------------------------
+
+// Look in the authority registry for operations to targetCRS
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::Private::findOpsInRegistryDirectTo(
+ const crs::CRSNNPtr &targetCRS, Private::Context &context) {
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("findOpsInRegistryDirectTo({any} -->" +
+ objectAsStr(targetCRS.get()) + ")");
+#endif
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ assert(authFactory);
+
+ std::list<std::pair<std::string, std::string>> ids;
+ buildCRSIds(targetCRS, context, ids);
+
+ const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
+ for (const auto &id : ids) {
+ const auto &targetAuthName = id.first;
+ const auto &targetCode = id.second;
+
+ const auto authorities(getCandidateAuthorities(
+ authFactory, targetAuthName, targetAuthName));
+ for (const auto &authority : authorities) {
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(),
+ authority == "any" ? std::string() : authority);
+ auto res = tmpAuthFactory->createFromCoordinateReferenceSystemCodes(
+ std::string(), std::string(), targetAuthName, targetCode,
+ context.context->getUsePROJAlternativeGridNames(),
+
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE,
+ gridAvailabilityUse == CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
+ context.context->getDiscardSuperseded(), true, true,
+ context.extent1, context.extent2);
+ if (!res.empty()) {
+ auto resFiltered =
+ FilterResults(res, context.context, context.extent1,
+ context.extent2, false)
+ .getRes();
+#ifdef TRACE_CREATE_OPERATIONS
+ logTrace("filtering reduced from " +
+ toString(static_cast<int>(res.size())) + " to " +
+ toString(static_cast<int>(resFiltered.size())));
+#endif
+ return resFiltered;
+ }
+ }
+ }
+ return std::vector<CoordinateOperationNNPtr>();
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// Look in the authority registry for operations from sourceCRS to targetCRS
+// using an intermediate pivot
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context,
+ bool useCreateBetweenGeodeticCRSWithDatumBasedIntermediates) {
+
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("findsOpsInRegistryWithIntermediate(" +
+ objectAsStr(sourceCRS.get()) + " --> " +
+ objectAsStr(targetCRS.get()) + ")");
+#endif
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ assert(authFactory);
+
+ std::list<std::pair<std::string, std::string>> sourceIds;
+ std::list<std::pair<std::string, std::string>> targetIds;
+ buildCRSIds(sourceCRS, context, sourceIds);
+ buildCRSIds(targetCRS, context, targetIds);
+
+ const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
+ for (const auto &idSrc : sourceIds) {
+ const auto &srcAuthName = idSrc.first;
+ const auto &srcCode = idSrc.second;
+ for (const auto &idTarget : targetIds) {
+ const auto &targetAuthName = idTarget.first;
+ const auto &targetCode = idTarget.second;
+
+ const auto authorities(getCandidateAuthorities(
+ authFactory, srcAuthName, targetAuthName));
+ assert(!authorities.empty());
+
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(),
+ (authFactory->getAuthority() == "any" || authorities.size() > 1)
+ ? std::string()
+ : authorities.front());
+
+ std::vector<CoordinateOperationNNPtr> res;
+ if (useCreateBetweenGeodeticCRSWithDatumBasedIntermediates) {
+ res =
+ tmpAuthFactory
+ ->createBetweenGeodeticCRSWithDatumBasedIntermediates(
+ sourceCRS, srcAuthName, srcCode, targetCRS,
+ targetAuthName, targetCode,
+ context.context->getUsePROJAlternativeGridNames(),
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
+ context.context->getDiscardSuperseded(),
+ authFactory->getAuthority() != "any" &&
+ authorities.size() > 1
+ ? authorities
+ : std::vector<std::string>(),
+ context.extent1, context.extent2);
+ } else {
+ io::AuthorityFactory::ObjectType intermediateObjectType =
+ io::AuthorityFactory::ObjectType::CRS;
+
+ // If doing GeogCRS --> GeogCRS, only use GeogCRS as
+ // intermediate CRS
+ // Avoid weird behavior when doing NAD83 -> NAD83(2011)
+ // that would go through NAVD88 otherwise.
+ if (context.context->getIntermediateCRS().empty() &&
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()) &&
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS.get())) {
+ intermediateObjectType =
+ io::AuthorityFactory::ObjectType::GEOGRAPHIC_CRS;
+ }
+ res = tmpAuthFactory->createFromCRSCodesWithIntermediates(
+ srcAuthName, srcCode, targetAuthName, targetCode,
+ context.context->getUsePROJAlternativeGridNames(),
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE,
+ context.context->getDiscardSuperseded(),
+ context.context->getIntermediateCRS(),
+ intermediateObjectType,
+ authFactory->getAuthority() != "any" &&
+ authorities.size() > 1
+ ? authorities
+ : std::vector<std::string>(),
+ context.extent1, context.extent2);
+ }
+ if (!res.empty()) {
+
+ auto resFiltered =
+ FilterResults(res, context.context, context.extent1,
+ context.extent2, false)
+ .getRes();
+#ifdef TRACE_CREATE_OPERATIONS
+ logTrace("filtering reduced from " +
+ toString(static_cast<int>(res.size())) + " to " +
+ toString(static_cast<int>(resFiltered.size())));
+#endif
+ return resFiltered;
+ }
+ }
+ }
+ return std::vector<CoordinateOperationNNPtr>();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static TransformationNNPtr
+createBallparkGeographicOffset(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS,
+ const io::DatabaseContextPtr &dbContext) {
+
+ const crs::GeographicCRS *geogSrc =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
+ const crs::GeographicCRS *geogDst =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
+ const bool isSameDatum = geogSrc && geogDst &&
+ geogSrc->datumNonNull(dbContext)->_isEquivalentTo(
+ geogDst->datumNonNull(dbContext).get(),
+ util::IComparable::Criterion::EQUIVALENT);
+
+ auto name = buildOpName(isSameDatum ? NULL_GEOGRAPHIC_OFFSET
+ : BALLPARK_GEOGRAPHIC_OFFSET,
+ sourceCRS, targetCRS);
+
+ 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);
+
+ std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
+ if (isSameDatum) {
+ accuracies.emplace_back(metadata::PositionalAccuracy::create("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),
+ accuracies);
+ } else {
+ return Transformation::createGeographic2DOffsets(
+ map, sourceCRS, targetCRS, angle0, angle0, accuracies);
+ }
+}
+//! @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->pushOmitZUnitConversion();
+
+ horizTransform->_exportToPROJString(formatter);
+
+ formatter->startInversion();
+ geogDst->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ formatter->popOmitZUnitConversion();
+
+ formatter->pushOmitHorizontalConversionInVertTransformation();
+ verticalTransform->_exportToPROJString(formatter);
+ formatter->popOmitHorizontalConversionInVertTransformation();
+
+ formatter->pushOmitZUnitConversion();
+ geogDst->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->popOmitZUnitConversion();
+ }
+};
+
+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->pushOmitZUnitConversion();
+
+ opSrcCRSToGeogCRS->_exportToPROJString(formatter);
+
+ formatter->startInversion();
+ interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ formatter->popOmitZUnitConversion();
+
+ formatter->pushOmitHorizontalConversionInVertTransformation();
+ verticalTransform->_exportToPROJString(formatter);
+ formatter->popOmitHorizontalConversionInVertTransformation();
+
+ formatter->pushOmitZUnitConversion();
+
+ interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter);
+
+ opGeogCRStoDstCRS->_exportToPROJString(formatter);
+
+ formatter->popOmitZUnitConversion();
+ }
+};
+
+MyPROJStringExportableHorizVerticalHorizPROJBased::
+ ~MyPROJStringExportableHorizVerticalHorizPROJBased() = default;
+
+//! @endcond
+
+} // namespace operation
+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 {
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+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 SingleOperationNNPtr createPROJBased(
+ const util::PropertyMap &properties,
+ const io::IPROJStringExportableNNPtr &projExportable,
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::CRSPtr &interpolationCRS,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies,
+ bool hasBallparkTransformation) {
+ return util::nn_static_pointer_cast<SingleOperation>(
+ PROJBasedOperation::create(properties, projExportable, false, sourceCRS,
+ targetCRS, interpolationCRS, accuracies,
+ hasBallparkTransformation));
+}
+
+// ---------------------------------------------------------------------------
+
+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, nullptr,
+ {}, false);
+}
+
+// ---------------------------------------------------------------------------
+
+static std::string
+getRemarks(const std::vector<operation::CoordinateOperationNNPtr> &ops) {
+ std::string remarks;
+ for (const auto &op : ops) {
+ const auto &opRemarks = op->remarks();
+ if (!opRemarks.empty()) {
+ if (!remarks.empty()) {
+ remarks += '\n';
+ }
+
+ std::string opName(op->nameStr());
+ if (starts_with(opName, INVERSE_OF)) {
+ opName = opName.substr(INVERSE_OF.size());
+ }
+
+ remarks += "For ";
+ remarks += opName;
+
+ const auto &ids = op->identifiers();
+ if (!ids.empty()) {
+ std::string authority(*ids.front()->codeSpace());
+ if (starts_with(authority, "INVERSE(") &&
+ authority.back() == ')') {
+ authority = authority.substr(strlen("INVERSE("),
+ authority.size() - 1 -
+ strlen("INVERSE("));
+ }
+ if (starts_with(authority, "DERIVED_FROM(") &&
+ authority.back() == ')') {
+ authority = authority.substr(strlen("DERIVED_FROM("),
+ authority.size() - 1 -
+ strlen("DERIVED_FROM("));
+ }
+
+ remarks += " (";
+ remarks += authority;
+ remarks += ':';
+ remarks += ids.front()->code();
+ remarks += ')';
+ }
+ remarks += ": ";
+ remarks += opRemarks;
+ }
+ }
+ return remarks;
+}
+
+// ---------------------------------------------------------------------------
+
+static CoordinateOperationNNPtr createHorizVerticalPROJBased(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const operation::CoordinateOperationNNPtr &horizTransform,
+ const operation::CoordinateOperationNNPtr &verticalTransform,
+ bool checkExtent) {
+
+ auto geogDst = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(targetCRS);
+ assert(geogDst);
+
+ auto exportable = util::nn_make_shared<MyPROJStringExportableHorizVertical>(
+ horizTransform, verticalTransform, geogDst);
+
+ const bool horizTransformIsNoOp =
+ starts_with(horizTransform->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ horizTransform->nameStr().find(" + ") == std::string::npos;
+ if (horizTransformIsNoOp) {
+ auto properties = util::PropertyMap();
+ properties.set(common::IdentifiedObject::NAME_KEY,
+ verticalTransform->nameStr());
+ bool dummy = false;
+ auto extent = getExtent(verticalTransform, true, dummy);
+ if (extent) {
+ properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ NN_NO_CHECK(extent));
+ }
+ const auto &remarks = verticalTransform->remarks();
+ if (!remarks.empty()) {
+ properties.set(common::IdentifiedObject::REMARKS_KEY, remarks);
+ }
+ return createPROJBased(
+ properties, exportable, sourceCRS, targetCRS, nullptr,
+ verticalTransform->coordinateOperationAccuracies(),
+ verticalTransform->hasBallparkTransformation());
+ } else {
+ bool emptyIntersection = false;
+ auto ops = std::vector<CoordinateOperationNNPtr>{horizTransform,
+ verticalTransform};
+ auto extent = getExtent(ops, true, emptyIntersection);
+ if (checkExtent && emptyIntersection) {
+ std::string msg(
+ "empty intersection of area of validity of concatenated "
+ "operations");
+ throw InvalidOperationEmptyIntersection(msg);
+ }
+ 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));
+ }
+
+ const auto remarks = getRemarks(ops);
+ if (!remarks.empty()) {
+ properties.set(common::IdentifiedObject::REMARKS_KEY, remarks);
+ }
+
+ 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, nullptr, accuracies,
+ horizTransform->hasBallparkTransformation() ||
+ verticalTransform->hasBallparkTransformation());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+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, bool checkExtent) {
+
+ auto exportable =
+ util::nn_make_shared<MyPROJStringExportableHorizVerticalHorizPROJBased>(
+ opSrcCRSToGeogCRS, verticalTransform, opGeogCRStoDstCRS,
+ interpolationGeogCRS);
+
+ std::vector<CoordinateOperationNNPtr> ops;
+ if (!(starts_with(opSrcCRSToGeogCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ opSrcCRSToGeogCRS->nameStr().find(" + ") == std::string::npos)) {
+ ops.emplace_back(opSrcCRSToGeogCRS);
+ }
+ ops.emplace_back(verticalTransform);
+ if (!(starts_with(opGeogCRStoDstCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ opGeogCRStoDstCRS->nameStr().find(" + ") == std::string::npos)) {
+ ops.emplace_back(opGeogCRStoDstCRS);
+ }
+
+ bool hasBallparkTransformation = false;
+ for (const auto &op : ops) {
+ hasBallparkTransformation |= op->hasBallparkTransformation();
+ }
+ bool emptyIntersection = false;
+ auto extent = getExtent(ops, false, emptyIntersection);
+ if (checkExtent && emptyIntersection) {
+ std::string msg(
+ "empty intersection of area of validity of concatenated "
+ "operations");
+ throw InvalidOperationEmptyIntersection(msg);
+ }
+ 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));
+ }
+
+ const auto remarks = getRemarks(ops);
+ if (!remarks.empty()) {
+ properties.set(common::IdentifiedObject::REMARKS_KEY, remarks);
+ }
+
+ 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,
+ nullptr, accuracies, hasBallparkTransformation);
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::Private::createOperationsGeogToGeog(
+ std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS, Private::Context &context,
+ const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst) {
+
+ assert(sourceCRS.get() == geogSrc);
+ assert(targetCRS.get() == geogDst);
+
+ 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()));
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable() : nullptr;
+
+ const bool sameDatum = geogSrc->datumNonNull(dbContext)->_isEquivalentTo(
+ geogDst->datumNonNull(dbContext).get(),
+ util::IComparable::Criterion::EQUIVALENT);
+
+ // Do the CRS differ by their axis order ?
+ bool axisReversal2D = false;
+ bool axisReversal3D = false;
+ if (!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 ||
+ srcOrder == cs::EllipsoidalCS::AxisOrder::
+ LAT_NORTH_LONG_EAST_HEIGHT_UP) &&
+ (dstOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ||
+ dstOrder == cs::EllipsoidalCS::AxisOrder::
+ LONG_EAST_LAT_NORTH_HEIGHT_UP)) ||
+ ((srcOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ||
+ srcOrder == cs::EllipsoidalCS::AxisOrder::
+ LONG_EAST_LAT_NORTH_HEIGHT_UP) &&
+ (dstOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST ||
+ dstOrder == cs::EllipsoidalCS::AxisOrder::
+ LAT_NORTH_LONG_EAST_HEIGHT_UP))) {
+ if (srcAxisList.size() == 3 || dstAxisList.size() == 3)
+ axisReversal3D = true;
+ else
+ axisReversal2D = true;
+ }
+ }
+
+ // Do they differ by vertical units ?
+ if (vconvSrc != vconvDst &&
+ geogSrc->ellipsoid()->_isEquivalentTo(
+ geogDst->ellipsoid().get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ if (offset_pm.value() == 0 && !axisReversal2D && !axisReversal3D) {
+ // 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);
+ conv->setHasBallparkTransformation(!sameDatum);
+ res.push_back(conv);
+ return res;
+ } else {
+ auto op = createGeodToGeodPROJBased(sourceCRS, targetCRS);
+ op->setHasBallparkTransformation(!sameDatum);
+ res.emplace_back(op);
+ return res;
+ }
+ }
+
+ // Do the CRS differ only by their axis order ?
+ if (sameDatum && (axisReversal2D || axisReversal3D)) {
+ auto conv = Conversion::createAxisOrderReversal(axisReversal3D);
+ 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(
+ createBallparkGeographicOffset(sourceCRS, interm_crs, dbContext));
+
+ 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(createBallparkGeographicOffset(
+ interm_crs, targetCRS, dbContext));
+ } else {
+ steps.emplace_back(createBallparkGeographicOffset(
+ sourceCRS, targetCRS, dbContext));
+ }
+ }
+
+ auto op = ConcatenatedOperation::createComputeMetadata(
+ steps, disallowEmptyIntersection);
+ op->setHasBallparkTransformation(!sameDatum);
+ res.emplace_back(op);
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+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;
+}
+
+// ---------------------------------------------------------------------------
+
+static std::vector<crs::CRSNNPtr>
+findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory,
+ const crs::GeodeticCRS *crs,
+ const datum::GeodeticReferenceFrame *datum) {
+ std::vector<crs::CRSNNPtr> candidates;
+ assert(datum);
+ const auto &ids = datum->identifiers();
+ const auto &datumName = datum->nameStr();
+ if (!ids.empty()) {
+ for (const auto &id : ids) {
+ const auto &authName = *(id->codeSpace());
+ const auto &code = id->code();
+ if (!authName.empty()) {
+ const auto crsIds = crs->identifiers();
+ const auto tmpFactory =
+ (crsIds.size() == 1 &&
+ *(crsIds.front()->codeSpace()) == authName)
+ ? io::AuthorityFactory::create(
+ authFactory->databaseContext(), authName)
+ .as_nullable()
+ : authFactory;
+ auto l_candidates = tmpFactory->createGeodeticCRSFromDatum(
+ authName, code, std::string());
+ for (const auto &candidate : l_candidates) {
+ candidates.emplace_back(candidate);
+ }
+ }
+ }
+ } else if (datumName != "unknown" && datumName != "unnamed") {
+ auto matches = authFactory->createObjectsFromName(
+ datumName,
+ {io::AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, false,
+ 2);
+ if (matches.size() == 1) {
+ const auto &match = matches.front();
+ if (datum->_isEquivalentTo(
+ match.get(), util::IComparable::Criterion::EQUIVALENT) &&
+ !match->identifiers().empty()) {
+ return findCandidateGeodCRSForDatum(
+ authFactory, crs,
+ dynamic_cast<const datum::GeodeticReferenceFrame *>(
+ match.get()));
+ }
+ }
+ }
+ return candidates;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::setCRSs(
+ CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS) {
+ co->setCRSs(sourceCRS, targetCRS, nullptr);
+
+ auto invCO = dynamic_cast<InverseCoordinateOperation *>(co);
+ if (invCO) {
+ invCO->forwardOperation()->setCRSs(targetCRS, sourceCRS, nullptr);
+ }
+
+ auto transf = dynamic_cast<Transformation *>(co);
+ if (transf) {
+ transf->inverseAsTransformation()->setCRSs(targetCRS, sourceCRS,
+ nullptr);
+ }
+
+ auto concat = dynamic_cast<ConcatenatedOperation *>(co);
+ if (concat) {
+ auto first = concat->operations().front().get();
+ auto &firstTarget(first->targetCRS());
+ if (firstTarget) {
+ setCRSs(first, sourceCRS, NN_NO_CHECK(firstTarget));
+ }
+ auto last = concat->operations().back().get();
+ auto &lastSource(last->sourceCRS());
+ if (lastSource) {
+ setCRSs(last, NN_NO_CHECK(lastSource), targetCRS);
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static bool hasResultSetOnlyResultsWithPROJStep(
+ const std::vector<CoordinateOperationNNPtr> &res) {
+ for (const auto &op : res) {
+ auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get());
+ if (concat) {
+ bool hasPROJStep = false;
+ const auto &steps = concat->operations();
+ for (const auto &step : steps) {
+ const auto &ids = step->identifiers();
+ if (!ids.empty()) {
+ const auto &opAuthority = *(ids.front()->codeSpace());
+ if (opAuthority == "PROJ" ||
+ opAuthority == "INVERSE(PROJ)" ||
+ opAuthority == "DERIVED_FROM(PROJ)") {
+ hasPROJStep = true;
+ break;
+ }
+ }
+ }
+ if (!hasPROJStep) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+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) {
+
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("createOperationsWithDatumPivot(" +
+ objectAsStr(sourceCRS.get()) + "," +
+ objectAsStr(targetCRS.get()) + ")");
+#endif
+
+ 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 &dbContext = authFactory->databaseContext();
+
+ const auto candidatesSrcGeod(findCandidateGeodCRSForDatum(
+ authFactory, geodSrc,
+ geodSrc->datumNonNull(dbContext.as_nullable()).get()));
+ const auto candidatesDstGeod(findCandidateGeodCRSForDatum(
+ authFactory, geodDst,
+ geodDst->datumNonNull(dbContext.as_nullable()).get()));
+
+ const bool sourceAndTargetAre3D =
+ geodSrc->coordinateSystem()->axisList().size() == 3 &&
+ geodDst->coordinateSystem()->axisList().size() == 3;
+
+ 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;
+ const bool isNullThird =
+ isNullTransformation(opsThird[0]->nameStr());
+ CoordinateOperationNNPtr opSecondCloned(
+ (isNullFirst || isNullThird || sourceAndTargetAre3D)
+ ? opSecond->shallowClone()
+ : opSecond);
+ if (isNullFirst || isNullThird) {
+ if (opSecondCloned->identifiers().size() == 1 &&
+ (*opSecondCloned->identifiers()[0]->codeSpace())
+ .find("DERIVED_FROM") == std::string::npos) {
+ {
+ util::PropertyMap map;
+ addModifiedIdentifier(map, opSecondCloned.get(), false,
+ true);
+ opSecondCloned->setProperties(map);
+ }
+ auto invCO = dynamic_cast<InverseCoordinateOperation *>(
+ opSecondCloned.get());
+ if (invCO) {
+ auto invCOForward = invCO->forwardOperation().get();
+ if (invCOForward->identifiers().size() == 1 &&
+ (*invCOForward->identifiers()[0]->codeSpace())
+ .find("DERIVED_FROM") ==
+ std::string::npos) {
+ util::PropertyMap map;
+ addModifiedIdentifier(map, invCOForward, false,
+ true);
+ invCOForward->setProperties(map);
+ }
+ }
+ }
+ }
+ if (sourceAndTargetAre3D) {
+ opSecondCloned->getPrivate()->use3DHelmert_ = true;
+ auto invCO = dynamic_cast<InverseCoordinateOperation *>(
+ opSecondCloned.get());
+ if (invCO) {
+ auto invCOForward = invCO->forwardOperation().get();
+ invCOForward->getPrivate()->use3DHelmert_ = true;
+ }
+ }
+ if (isNullFirst) {
+ auto oldTarget(NN_CHECK_ASSERT(opSecondCloned->targetCRS()));
+ setCRSs(opSecondCloned.get(), sourceCRS, oldTarget);
+ } else {
+ subOps.emplace_back(opFirst);
+ }
+ if (isNullThird) {
+ auto oldSource(NN_CHECK_ASSERT(opSecondCloned->sourceCRS()));
+ setCRSs(opSecondCloned.get(), oldSource, targetCRS);
+ subOps.emplace_back(opSecondCloned);
+ } else {
+ subOps.emplace_back(opSecondCloned);
+ subOps.emplace_back(opsThird[0]);
+ }
+#ifdef TRACE_CREATE_OPERATIONS
+ std::string debugStr;
+ for (const auto &op : subOps) {
+ if (!debugStr.empty()) {
+ debugStr += " + ";
+ }
+ debugStr += objectAsStr(op.get());
+ debugStr += " (";
+ debugStr += objectAsStr(op->sourceCRS().get());
+ debugStr += "->";
+ debugStr += objectAsStr(op->targetCRS().get());
+ debugStr += ")";
+ }
+ logTrace("transformation " + debugStr);
+#endif
+ try {
+ res.emplace_back(ConcatenatedOperation::createComputeMetadata(
+ subOps, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ };
+
+ // Start in priority with candidates that have exactly the same name as
+ // the sourcCRS and targetCRS. Typically for the case of init=IGNF:XXXX
+
+ // Transformation from IGNF:NTFP to IGNF:RGF93G,
+ // using
+ // NTF geographiques Paris (gr) vers NTF GEOGRAPHIQUES GREENWICH (DMS) +
+ // NOUVELLE TRIANGULATION DE LA FRANCE (NTF) vers RGF93 (ETRS89)
+ // that is using ntf_r93.gsb, is horribly dependent
+ // of IGNF:RGF93G being returned before IGNF:RGF93GEO in candidatesDstGeod.
+ // If RGF93GEO is returned before then we go through WGS84 and use
+ // instead a Helmert transformation.
+ // The below logic is thus quite fragile, and attempts at changing it
+ // result in degraded results for other use cases...
+
+ for (const auto &candidateSrcGeod : candidatesSrcGeod) {
+ if (candidateSrcGeod->nameStr() == sourceCRS->nameStr()) {
+ for (const auto &candidateDstGeod : candidatesDstGeod) {
+ if (candidateDstGeod->nameStr() == targetCRS->nameStr()) {
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("try " + objectAsStr(sourceCRS.get()) + "->" +
+ objectAsStr(candidateSrcGeod.get()) + "->" +
+ objectAsStr(candidateDstGeod.get()) + "->" +
+ objectAsStr(targetCRS.get()) + ")");
+#endif
+ 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()) {
+ if (hasResultSetOnlyResultsWithPROJStep(res)) {
+ continue;
+ }
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ for (const auto &candidateSrcGeod : candidatesSrcGeod) {
+ const bool bSameSrcName =
+ candidateSrcGeod->nameStr() == sourceCRS->nameStr();
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("");
+#endif
+ const auto opsFirst =
+ createOperations(sourceCRS, candidateSrcGeod, context);
+ assert(!opsFirst.empty());
+ const bool isNullFirst = isNullTransformation(opsFirst[0]->nameStr());
+
+ for (const auto &candidateDstGeod : candidatesDstGeod) {
+ if (bSameSrcName &&
+ candidateDstGeod->nameStr() == targetCRS->nameStr()) {
+ continue;
+ }
+
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("try " + objectAsStr(sourceCRS.get()) + "->" +
+ objectAsStr(candidateSrcGeod.get()) + "->" +
+ objectAsStr(candidateDstGeod.get()) + "->" +
+ objectAsStr(targetCRS.get()) + ")");
+#endif
+ createTransformations(candidateSrcGeod, candidateDstGeod,
+ opsFirst[0], isNullFirst);
+ if (!res.empty() && !hasResultSetOnlyResultsWithPROJStep(res)) {
+ return;
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static CoordinateOperationNNPtr
+createBallparkGeocentricTranslation(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS) {
+ std::string name(BALLPARK_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, {}));
+}
+
+// ---------------------------------------------------------------------------
+
+bool CoordinateOperationFactory::Private::hasPerfectAccuracyResult(
+ const std::vector<CoordinateOperationNNPtr> &res, const Context &context) {
+ auto resTmp = FilterResults(res, context.context, context.extent1,
+ context.extent2, true)
+ .getRes();
+ for (const auto &op : resTmp) {
+ const double acc = getAccuracy(op);
+ if (acc == 0.0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::Private::createOperations(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context) {
+
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("createOperations(" + objectAsStr(sourceCRS.get()) + " --> " +
+ objectAsStr(targetCRS.get()) + ")");
+#endif
+
+ std::vector<CoordinateOperationNNPtr> res;
+
+ auto boundSrc = dynamic_cast<const crs::BoundCRS *>(sourceCRS.get());
+ auto boundDst = dynamic_cast<const crs::BoundCRS *>(targetCRS.get());
+
+ const auto &sourceProj4Ext = boundSrc
+ ? boundSrc->baseCRS()->getExtensionProj4()
+ : sourceCRS->getExtensionProj4();
+ const auto &targetProj4Ext = boundDst
+ ? boundDst->baseCRS()->getExtensionProj4()
+ : targetCRS->getExtensionProj4();
+ if (!sourceProj4Ext.empty() || !targetProj4Ext.empty()) {
+ createOperationsFromProj4Ext(sourceCRS, targetCRS, boundSrc, boundDst,
+ res);
+ return res;
+ }
+
+ auto geodSrc = dynamic_cast<const crs::GeodeticCRS *>(sourceCRS.get());
+ auto geodDst = dynamic_cast<const crs::GeodeticCRS *>(targetCRS.get());
+ auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
+ auto geogDst = dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
+ auto vertSrc = dynamic_cast<const crs::VerticalCRS *>(sourceCRS.get());
+ auto vertDst = dynamic_cast<const crs::VerticalCRS *>(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());
+ const auto &authFactory = context.context->getAuthorityFactory();
+ if (authFactory &&
+ (derivedSrc == nullptr ||
+ !derivedSrc->baseCRS()->_isEquivalentTo(
+ targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) &&
+ (derivedDst == nullptr ||
+ !derivedDst->baseCRS()->_isEquivalentTo(
+ sourceCRS.get(), util::IComparable::Criterion::EQUIVALENT))) {
+
+ if (createOperationsFromDatabase(sourceCRS, targetCRS, context, geodSrc,
+ geodDst, geogSrc, geogDst, vertSrc,
+ vertDst, res)) {
+ return res;
+ }
+ }
+
+ // Special case if both CRS are geodetic
+ if (geodSrc && geodDst && !derivedSrc && !derivedDst) {
+ createOperationsGeodToGeod(sourceCRS, targetCRS, context, geodSrc,
+ geodDst, res);
+ 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) {
+ createOperationsDerivedTo(sourceCRS, targetCRS, context, derivedSrc,
+ res);
+ return res;
+ }
+
+ // reverse of previous case
+ if (derivedDst) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ // Order of comparison between the geogDst vs geodDst is impotant
+ if (boundSrc && geogDst) {
+ createOperationsBoundToGeog(sourceCRS, targetCRS, context, boundSrc,
+ geogDst, res);
+ return res;
+ } else if (boundSrc && geodDst) {
+ createOperationsToGeod(sourceCRS, targetCRS, context, geodDst, res);
+ return res;
+ }
+
+ // reverse of previous case
+ if (geodSrc && boundDst) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ // vertCRS (as boundCRS with transformation to target vertCRS) to
+ // vertCRS
+ if (boundSrc && vertDst) {
+ createOperationsBoundToVert(sourceCRS, targetCRS, context, boundSrc,
+ vertDst, res);
+ return res;
+ }
+
+ // reverse of previous case
+ if (boundDst && vertSrc) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ if (vertSrc && vertDst) {
+ createOperationsVertToVert(sourceCRS, targetCRS, context, vertSrc,
+ vertDst, res);
+ 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) {
+ createOperationsVertToGeog(sourceCRS, targetCRS, context, vertSrc,
+ geogDst, res);
+ return res;
+ }
+
+ // reverse of previous case
+ if (vertDst && geogSrc) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ // boundCRS to boundCRS
+ if (boundSrc && boundDst) {
+ createOperationsBoundToBound(sourceCRS, targetCRS, context, boundSrc,
+ boundDst, res);
+ return res;
+ }
+
+ auto compoundSrc = dynamic_cast<crs::CompoundCRS *>(sourceCRS.get());
+ // Order of comparison between the geogDst vs geodDst is impotant
+ if (compoundSrc && geogDst) {
+ createOperationsCompoundToGeog(sourceCRS, targetCRS, context,
+ compoundSrc, geogDst, res);
+ return res;
+ } else if (compoundSrc && geodDst) {
+ createOperationsToGeod(sourceCRS, targetCRS, context, geodDst, res);
+ return res;
+ }
+
+ // reverse of previous cases
+ auto compoundDst = dynamic_cast<const crs::CompoundCRS *>(targetCRS.get());
+ if (geodSrc && compoundDst) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ if (compoundSrc && compoundDst) {
+ createOperationsCompoundToCompound(sourceCRS, targetCRS, context,
+ compoundSrc, compoundDst, res);
+ return res;
+ }
+
+ // '+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +type=crs' to
+ // '+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@bar.gtx
+ // +type=crs'
+ if (boundSrc && compoundDst) {
+ createOperationsBoundToCompound(sourceCRS, targetCRS, context, boundSrc,
+ compoundDst, res);
+ return res;
+ }
+
+ // reverse of previous case
+ if (boundDst && compoundSrc) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsFromProj4Ext(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::BoundCRS *boundSrc, const crs::BoundCRS *boundDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ auto sourceProjExportable = dynamic_cast<const io::IPROJStringExportable *>(
+ boundSrc ? boundSrc : sourceCRS.get());
+ auto targetProjExportable = dynamic_cast<const io::IPROJStringExportable *>(
+ boundDst ? boundDst : 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();
+ projFormatter->setCRSExport(true);
+ projFormatter->setLegacyCRSToCRSContext(true);
+ projFormatter->startInversion();
+ sourceProjExportable->_exportToPROJString(projFormatter.get());
+ auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
+ if (geogSrc) {
+ auto tmpFormatter = io::PROJStringFormatter::create();
+ geogSrc->addAngularUnitConvertAndAxisSwap(tmpFormatter.get());
+ projFormatter->ingestPROJString(tmpFormatter->toString());
+ }
+
+ projFormatter->stopInversion();
+
+ targetProjExportable->_exportToPROJString(projFormatter.get());
+ auto geogDst = dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
+ if (geogDst) {
+ auto tmpFormatter = io::PROJStringFormatter::create();
+ geogDst->addAngularUnitConvertAndAxisSwap(tmpFormatter.get());
+ projFormatter->ingestPROJString(tmpFormatter->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, {}));
+}
+
+// ---------------------------------------------------------------------------
+
+bool CoordinateOperationFactory::Private::createOperationsFromDatabase(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeodeticCRS *geodSrc,
+ const crs::GeodeticCRS *geodDst, const crs::GeographicCRS *geogSrc,
+ const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ if (geogSrc && vertDst) {
+ createOperationsFromDatabase(targetCRS, sourceCRS, context, geodDst,
+ geodSrc, geogDst, geogSrc, vertDst,
+ vertSrc, res);
+ res = applyInverse(res);
+ } else if (geogDst && vertSrc) {
+ res = applyInverse(createOperationsGeogToVertFromGeoid(
+ targetCRS, sourceCRS, vertSrc, context));
+ if (!res.empty()) {
+ createOperationsVertToGeogBallpark(sourceCRS, targetCRS, context,
+ vertSrc, geogDst, res);
+ }
+ }
+
+ if (!res.empty()) {
+ return true;
+ }
+
+ bool resFindDirectNonEmptyBeforeFiltering = false;
+ res = findOpsInRegistryDirect(sourceCRS, targetCRS, context,
+ resFindDirectNonEmptyBeforeFiltering);
+
+ // If we get at least a result with perfect accuracy, do not
+ // bother generating synthetic transforms.
+ if (hasPerfectAccuracyResult(res, context)) {
+ return true;
+ }
+
+ bool doFilterAndCheckPerfectOp = false;
+
+ bool sameGeodeticDatum = false;
+
+ if (vertSrc || vertDst) {
+ if (res.empty()) {
+ if (geogSrc &&
+ geogSrc->coordinateSystem()->axisList().size() == 2 &&
+ vertDst) {
+ auto dbContext =
+ context.context->getAuthorityFactory()->databaseContext();
+ auto resTmp = findOpsInRegistryDirect(
+ sourceCRS->promoteTo3D(std::string(), dbContext), targetCRS,
+ context, resFindDirectNonEmptyBeforeFiltering);
+ for (auto &op : resTmp) {
+ auto newOp = op->shallowClone();
+ setCRSs(newOp.get(), sourceCRS, targetCRS);
+ res.emplace_back(newOp);
+ }
+ } else if (geogDst &&
+ geogDst->coordinateSystem()->axisList().size() == 2 &&
+ vertSrc) {
+ auto dbContext =
+ context.context->getAuthorityFactory()->databaseContext();
+ auto resTmp = findOpsInRegistryDirect(
+ sourceCRS, targetCRS->promoteTo3D(std::string(), dbContext),
+ context, resFindDirectNonEmptyBeforeFiltering);
+ for (auto &op : resTmp) {
+ auto newOp = op->shallowClone();
+ setCRSs(newOp.get(), sourceCRS, targetCRS);
+ res.emplace_back(newOp);
+ }
+ }
+ }
+ if (res.empty()) {
+ createOperationsFromDatabaseWithVertCRS(sourceCRS, targetCRS,
+ context, geogSrc, geogDst,
+ vertSrc, vertDst, res);
+ }
+ } else if (geodSrc && geodDst) {
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext = authFactory->databaseContext().as_nullable();
+
+ const auto srcDatum = geodSrc->datumNonNull(dbContext);
+ const auto dstDatum = geodDst->datumNonNull(dbContext);
+ sameGeodeticDatum = srcDatum->_isEquivalentTo(
+ dstDatum.get(), util::IComparable::Criterion::EQUIVALENT);
+
+ if (res.empty() && !sameGeodeticDatum &&
+ !context.inCreateOperationsWithDatumPivotAntiRecursion) {
+ // 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
+ // GeodeticCRSs
+ // 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.
+ createOperationsWithDatumPivot(res, sourceCRS, targetCRS, geodSrc,
+ geodDst, context);
+ doFilterAndCheckPerfectOp = !res.empty();
+ }
+ }
+
+ bool foundInstantiableOp = false;
+ // FIXME: the limitation to .size() == 1 is just for the
+ // -s EPSG:4959+5759 -t "EPSG:4959+7839" case
+ // finding EPSG:7860 'NZVD2016 height to Auckland 1946
+ // height (1)', which uses the EPSG:1071 'Vertical Offset by Grid
+ // Interpolation (NZLVD)' method which is not currently implemented by PROJ
+ // (cannot deal with .csv files)
+ // Initially the test was written to iterate over for all operations of a
+ // non-empty res, but this causes failures in the test suite when no grids
+ // are installed at all. Ideally we should tweak the test suite to be
+ // robust to that, or skip some tests.
+ if (res.size() == 1) {
+ try {
+ res.front()->exportToPROJString(
+ io::PROJStringFormatter::create().get());
+ foundInstantiableOp = true;
+ } catch (const std::exception &) {
+ }
+ if (!foundInstantiableOp) {
+ resFindDirectNonEmptyBeforeFiltering = false;
+ }
+ } else if (res.size() > 1) {
+ foundInstantiableOp = true;
+ }
+
+ // NAD27 to NAD83 has tens of results already. No need to look
+ // for a pivot
+ if (!sameGeodeticDatum &&
+ (((res.empty() || !foundInstantiableOp) &&
+ !resFindDirectNonEmptyBeforeFiltering &&
+ context.context->getAllowUseIntermediateCRS() ==
+ CoordinateOperationContext::IntermediateCRSUse::
+ IF_NO_DIRECT_TRANSFORMATION) ||
+ context.context->getAllowUseIntermediateCRS() ==
+ CoordinateOperationContext::IntermediateCRSUse::ALWAYS ||
+ getenv("PROJ_FORCE_SEARCH_PIVOT"))) {
+ auto resWithIntermediate = findsOpsInRegistryWithIntermediate(
+ sourceCRS, targetCRS, context, false);
+ res.insert(res.end(), resWithIntermediate.begin(),
+ resWithIntermediate.end());
+ doFilterAndCheckPerfectOp = !res.empty();
+
+ } else if (!context.inCreateOperationsWithDatumPivotAntiRecursion &&
+ !resFindDirectNonEmptyBeforeFiltering && geodSrc && geodDst &&
+ !sameGeodeticDatum &&
+ context.context->getIntermediateCRS().empty() &&
+ context.context->getAllowUseIntermediateCRS() !=
+ CoordinateOperationContext::IntermediateCRSUse::NEVER) {
+
+ bool tryWithGeodeticDatumIntermediate = res.empty();
+ if (!tryWithGeodeticDatumIntermediate) {
+ // This is in particular for the GDA94 to WGS 84 (G1762) case
+ // As we have a WGS 84 -> WGS 84 (G1762) null-transformation in the
+ // PROJ authority, previous steps might have use that WGS 84
+ // intermediate directly. They might also have generated a path
+ // through ITRF2008, as there is a path
+ // GDA94 (geoc.) -> ITRF2008 (geoc.) -> WGS84 (G1762) (geoc.)
+ // But there's a better path using
+ // GDA94 (geog.) --> GDA2020 (geog.) and
+ // GDA2020 (geoc.) -> WGS84 (G1762) (geoc.) that requires to
+ // explore intermediates through their datum, and not directly
+ // trough the CRS code.
+ // Do that only if the number of results we got through other
+ // algorithms is small, or if all results we have go through an
+ // operation in the PROJ authority.
+ constexpr size_t ARBITRARY_SMALL_NUMBER = 5U;
+ tryWithGeodeticDatumIntermediate =
+ res.size() < ARBITRARY_SMALL_NUMBER ||
+ hasResultSetOnlyResultsWithPROJStep(res);
+ }
+ if (tryWithGeodeticDatumIntermediate) {
+ auto resWithIntermediate = findsOpsInRegistryWithIntermediate(
+ sourceCRS, targetCRS, context, true);
+ res.insert(res.end(), resWithIntermediate.begin(),
+ resWithIntermediate.end());
+ 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 true;
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+static std::vector<crs::CRSNNPtr>
+findCandidateVertCRSForDatum(const io::AuthorityFactoryPtr &authFactory,
+ const datum::VerticalReferenceFrame *datum) {
+ std::vector<crs::CRSNNPtr> candidates;
+ assert(datum);
+ const auto &ids = datum->identifiers();
+ const auto &datumName = datum->nameStr();
+ if (!ids.empty()) {
+ for (const auto &id : ids) {
+ const auto &authName = *(id->codeSpace());
+ const auto &code = id->code();
+ if (!authName.empty()) {
+ auto l_candidates =
+ authFactory->createVerticalCRSFromDatum(authName, code);
+ for (const auto &candidate : l_candidates) {
+ candidates.emplace_back(candidate);
+ }
+ }
+ }
+ } else if (datumName != "unknown" && datumName != "unnamed") {
+ auto matches = authFactory->createObjectsFromName(
+ datumName,
+ {io::AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME}, false,
+ 2);
+ if (matches.size() == 1) {
+ const auto &match = matches.front();
+ if (datum->_isEquivalentTo(
+ match.get(), util::IComparable::Criterion::EQUIVALENT) &&
+ !match->identifiers().empty()) {
+ return findCandidateVertCRSForDatum(
+ authFactory,
+ dynamic_cast<const datum::VerticalReferenceFrame *>(
+ match.get()));
+ }
+ }
+ }
+ return candidates;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::Private::createOperationsGeogToVertFromGeoid(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::VerticalCRS *vertDst, Private::Context &context) {
+
+ ENTER_FUNCTION();
+
+ const auto useTransf = [&targetCRS, &context,
+ vertDst](const CoordinateOperationNNPtr &op) {
+ const auto targetOp =
+ dynamic_cast<const crs::VerticalCRS *>(op->targetCRS().get());
+ assert(targetOp);
+ if (targetOp->_isEquivalentTo(
+ vertDst, util::IComparable::Criterion::EQUIVALENT)) {
+ return op;
+ }
+ std::vector<CoordinateOperationNNPtr> tmp;
+ createOperationsVertToVert(NN_NO_CHECK(op->targetCRS()), targetCRS,
+ context, targetOp, vertDst, tmp);
+ assert(!tmp.empty());
+ auto ret = ConcatenatedOperation::createComputeMetadata(
+ {op, tmp.front()}, disallowEmptyIntersection);
+ return ret;
+ };
+
+ const auto getProjGeoidTransformation = [&sourceCRS, &targetCRS, &vertDst,
+ &context](
+ const CoordinateOperationNNPtr &model,
+ const std::string &projFilename) {
+
+ const auto getNameVertCRSMetre = [](const std::string &name) {
+ if (name.empty())
+ return std::string("unnamed");
+ auto ret(name);
+ bool haveOriginalUnit = false;
+ if (name.back() == ')') {
+ const auto pos = ret.rfind(" (");
+ if (pos != std::string::npos) {
+ haveOriginalUnit = true;
+ ret = ret.substr(0, pos);
+ }
+ }
+ const auto pos = ret.rfind(" depth");
+ if (pos != std::string::npos) {
+ ret = ret.substr(0, pos) + " height";
+ }
+ if (!haveOriginalUnit) {
+ ret += " (metre)";
+ }
+ return ret;
+ };
+
+ const auto &axis = vertDst->coordinateSystem()->axisList()[0];
+ const auto geogSrcCRS =
+ dynamic_cast<crs::GeographicCRS *>(model->interpolationCRS().get())
+ ? NN_NO_CHECK(model->interpolationCRS())
+ : sourceCRS;
+ const auto vertCRSMetre =
+ axis->unit() == common::UnitOfMeasure::METRE &&
+ axis->direction() == cs::AxisDirection::UP
+ ? targetCRS
+ : util::nn_static_pointer_cast<crs::CRS>(
+ crs::VerticalCRS::create(
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ getNameVertCRSMetre(targetCRS->nameStr())),
+ vertDst->datum(), vertDst->datumEnsemble(),
+ cs::VerticalCS::createGravityRelatedHeight(
+ common::UnitOfMeasure::METRE)));
+ const auto properties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildOpName("Transformation", vertCRSMetre, geogSrcCRS));
+
+ // Try to find a representative value for the accuracy of this grid
+ // from the registered transformations.
+ std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
+ const auto &modelAccuracies = model->coordinateOperationAccuracies();
+ if (modelAccuracies.empty()) {
+ const auto &authFactory = context.context->getAuthorityFactory();
+ if (authFactory) {
+ const auto transformationsForGrid =
+ io::DatabaseContext::getTransformationsForGridName(
+ authFactory->databaseContext(), projFilename);
+ double accuracy = -1;
+ for (const auto &transf : transformationsForGrid) {
+ accuracy = std::max(accuracy, getAccuracy(transf));
+ }
+ if (accuracy >= 0) {
+ accuracies.emplace_back(
+ metadata::PositionalAccuracy::create(
+ toString(accuracy)));
+ }
+ }
+ }
+
+ return Transformation::createGravityRelatedHeightToGeographic3D(
+ properties, vertCRSMetre, geogSrcCRS, nullptr, projFilename,
+ !modelAccuracies.empty() ? modelAccuracies : accuracies);
+ };
+
+ std::vector<CoordinateOperationNNPtr> res;
+ const auto &authFactory = context.context->getAuthorityFactory();
+ if (authFactory) {
+ const auto &models = vertDst->geoidModel();
+ for (const auto &model : models) {
+ const auto &modelName = model->nameStr();
+ const auto transformations =
+ starts_with(modelName, "PROJ ")
+ ? std::vector<
+ CoordinateOperationNNPtr>{getProjGeoidTransformation(
+ model, modelName.substr(strlen("PROJ ")))}
+ : authFactory->getTransformationsForGeoid(
+ modelName,
+ context.context->getUsePROJAlternativeGridNames());
+ for (const auto &transf : transformations) {
+ if (dynamic_cast<crs::GeographicCRS *>(
+ transf->sourceCRS().get()) &&
+ dynamic_cast<crs::VerticalCRS *>(
+ transf->targetCRS().get())) {
+ res.push_back(useTransf(transf));
+ } else if (dynamic_cast<crs::GeographicCRS *>(
+ transf->targetCRS().get()) &&
+ dynamic_cast<crs::VerticalCRS *>(
+ transf->sourceCRS().get())) {
+ res.push_back(useTransf(transf->inverse()));
+ }
+ }
+ }
+ }
+
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private::
+ createOperationsGeogToVertWithIntermediateVert(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::VerticalCRS *vertDst, Private::Context &context) {
+
+ ENTER_FUNCTION();
+
+ std::vector<CoordinateOperationNNPtr> res;
+
+ struct AntiRecursionGuard {
+ Context &context;
+
+ explicit AntiRecursionGuard(Context &contextIn) : context(contextIn) {
+ assert(!context.inCreateOperationsGeogToVertWithIntermediateVert);
+ context.inCreateOperationsGeogToVertWithIntermediateVert = true;
+ }
+
+ ~AntiRecursionGuard() {
+ context.inCreateOperationsGeogToVertWithIntermediateVert = false;
+ }
+ };
+ AntiRecursionGuard guard(context);
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext = authFactory->databaseContext().as_nullable();
+
+ auto candidatesVert = findCandidateVertCRSForDatum(
+ authFactory, vertDst->datumNonNull(dbContext).get());
+ for (const auto &candidateVert : candidatesVert) {
+ auto resTmp = createOperations(sourceCRS, candidateVert, context);
+ if (!resTmp.empty()) {
+ const auto opsSecond =
+ createOperations(candidateVert, targetCRS, context);
+ if (!opsSecond.empty()) {
+ // The transformation from candidateVert to targetCRS should
+ // be just a unit change typically, so take only the first one,
+ // which is likely/hopefully the only one.
+ for (const auto &opFirst : resTmp) {
+ if (hasIdentifiers(opFirst)) {
+ if (candidateVert->_isEquivalentTo(
+ targetCRS.get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ res.emplace_back(opFirst);
+ } else {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {opFirst, opsSecond.front()},
+ disallowEmptyIntersection));
+ }
+ }
+ }
+ if (!res.empty())
+ break;
+ }
+ }
+ }
+
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private::
+ createOperationsGeogToVertWithAlternativeGeog(
+ const crs::CRSNNPtr & /*sourceCRS*/, // geographic CRS
+ const crs::CRSNNPtr &targetCRS, // vertical CRS
+ Private::Context &context) {
+
+ ENTER_FUNCTION();
+
+ std::vector<CoordinateOperationNNPtr> res;
+
+ struct AntiRecursionGuard {
+ Context &context;
+
+ explicit AntiRecursionGuard(Context &contextIn) : context(contextIn) {
+ assert(!context.inCreateOperationsGeogToVertWithAlternativeGeog);
+ context.inCreateOperationsGeogToVertWithAlternativeGeog = true;
+ }
+
+ ~AntiRecursionGuard() {
+ context.inCreateOperationsGeogToVertWithAlternativeGeog = false;
+ }
+ };
+ AntiRecursionGuard guard(context);
+
+ // Generally EPSG has operations from GeogCrs to VertCRS
+ auto ops = findOpsInRegistryDirectTo(targetCRS, context);
+
+ for (const auto &op : ops) {
+ const auto tmpCRS = op->sourceCRS();
+ if (tmpCRS && dynamic_cast<const crs::GeographicCRS *>(tmpCRS.get())) {
+ res.emplace_back(op);
+ }
+ }
+
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::
+ createOperationsFromDatabaseWithVertCRS(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeographicCRS *geogSrc,
+ const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ // Typically to transform from "NAVD88 height (ftUS)" to a geog CRS
+ // by using transformations of "NAVD88 height" (metre) to that geog CRS
+ if (res.empty() &&
+ !context.inCreateOperationsGeogToVertWithIntermediateVert && geogSrc &&
+ vertDst) {
+ res = createOperationsGeogToVertWithIntermediateVert(
+ sourceCRS, targetCRS, vertDst, context);
+ } else if (res.empty() &&
+ !context.inCreateOperationsGeogToVertWithIntermediateVert &&
+ geogDst && vertSrc) {
+ res = applyInverse(createOperationsGeogToVertWithIntermediateVert(
+ targetCRS, sourceCRS, vertSrc, context));
+ }
+
+ // NAD83 only exists in 2D version in EPSG, so if it has been
+ // promoted to 3D, when researching a vertical to geog
+ // transformation, try to down cast to 2D.
+ const auto geog3DToVertTryThroughGeog2D = [&res, &context](
+ const crs::GeographicCRS *geogSrcIn, const crs::VerticalCRS *vertDstIn,
+ const crs::CRSNNPtr &targetCRSIn) {
+ if (res.empty() && geogSrcIn && vertDstIn &&
+ geogSrcIn->coordinateSystem()->axisList().size() == 3) {
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable()
+ : nullptr;
+ const auto candidatesSrcGeod(findCandidateGeodCRSForDatum(
+ authFactory, geogSrcIn,
+ geogSrcIn->datumNonNull(dbContext).get()));
+ for (const auto &candidate : candidatesSrcGeod) {
+ auto geogCandidate =
+ util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ candidate);
+ if (geogCandidate &&
+ geogCandidate->coordinateSystem()->axisList().size() == 2) {
+ bool ignored;
+ res =
+ findOpsInRegistryDirect(NN_NO_CHECK(geogCandidate),
+ targetCRSIn, context, ignored);
+ break;
+ }
+ }
+ return true;
+ }
+ return false;
+ };
+
+ if (geog3DToVertTryThroughGeog2D(geogSrc, vertDst, targetCRS)) {
+ // do nothing
+ } else if (geog3DToVertTryThroughGeog2D(geogDst, vertSrc, sourceCRS)) {
+ res = applyInverse(res);
+ }
+
+ // There's no direct transformation from NAVD88 height to WGS84,
+ // so try to research all transformations from NAVD88 to another
+ // intermediate GeographicCRS.
+ if (res.empty() &&
+ !context.inCreateOperationsGeogToVertWithAlternativeGeog && geogSrc &&
+ vertDst) {
+ res = createOperationsGeogToVertWithAlternativeGeog(sourceCRS,
+ targetCRS, context);
+ } else if (res.empty() &&
+ !context.inCreateOperationsGeogToVertWithAlternativeGeog &&
+ geogDst && vertSrc) {
+ res = applyInverse(createOperationsGeogToVertWithAlternativeGeog(
+ targetCRS, sourceCRS, context));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsGeodToGeod(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeodeticCRS *geodSrc,
+ const crs::GeodeticCRS *geodDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ 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 *>(geodSrc);
+ auto geogDst = dynamic_cast<const crs::GeographicCRS *>(geodDst);
+
+ if (geogSrc && geogDst) {
+ createOperationsGeogToGeog(res, sourceCRS, targetCRS, context, geogSrc,
+ geogDst);
+ return;
+ }
+
+ const bool isSrcGeocentric = geodSrc->isGeocentric();
+ const bool isSrcGeographic = geogSrc != nullptr;
+ const bool isTargetGeocentric = geodDst->isGeocentric();
+ const bool isTargetGeographic = geogDst != nullptr;
+
+ const auto IsSameDatum = [&context, &geodSrc, &geodDst]() {
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable()
+ : nullptr;
+
+ return geodSrc->datumNonNull(dbContext)->_isEquivalentTo(
+ geodDst->datumNonNull(dbContext).get(),
+ util::IComparable::Criterion::EQUIVALENT);
+ };
+
+ if (((isSrcGeocentric && isTargetGeographic) ||
+ (isSrcGeographic && isTargetGeocentric))) {
+
+ // Same datum ?
+ if (IsSameDatum()) {
+ res.emplace_back(
+ Conversion::createGeographicGeocentric(sourceCRS, targetCRS));
+ } else if (isSrcGeocentric && geogDst) {
+ 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),
+ geogDst->datum(), geogDst->datumEnsemble(),
+ NN_CHECK_ASSERT(
+ util::nn_dynamic_pointer_cast<cs::CartesianCS>(
+ geodSrc->coordinateSystem()))));
+ auto opFirst =
+ createBallparkGeocentricTranslation(sourceCRS, interm_crs);
+ auto opSecond =
+ Conversion::createGeographicGeocentric(interm_crs, targetCRS);
+ res.emplace_back(ConcatenatedOperation::createComputeMetadata(
+ {opFirst, opSecond}, disallowEmptyIntersection));
+ } else {
+ // Apply previous case in reverse way
+ std::vector<CoordinateOperationNNPtr> resTmp;
+ createOperationsGeodToGeod(targetCRS, sourceCRS, context, geodDst,
+ geodSrc, resTmp);
+ assert(resTmp.size() == 1);
+ res.emplace_back(resTmp.front()->inverse());
+ }
+
+ return;
+ }
+
+ if (isSrcGeocentric && isTargetGeocentric) {
+ if (sourceCRS->_isEquivalentTo(
+ targetCRS.get(), util::IComparable::Criterion::EQUIVALENT) ||
+ IsSameDatum()) {
+ std::string name(NULL_GEOCENTRIC_TRANSLATION);
+ name += " from ";
+ name += sourceCRS->nameStr();
+ name += " to ";
+ name += targetCRS->nameStr();
+ res.emplace_back(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,
+ {metadata::PositionalAccuracy::create("0")}));
+ } else {
+ res.emplace_back(
+ createBallparkGeocentricTranslation(sourceCRS, targetCRS));
+ }
+ return;
+ }
+
+ // Transformation between two geodetic systems of unknown type
+ // This should normally not be triggered with "standard" CRS
+ res.emplace_back(createGeodToGeodPROJBased(sourceCRS, targetCRS));
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsDerivedTo(
+ const crs::CRSNNPtr & /*sourceCRS*/, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::DerivedCRS *derivedSrc,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ 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;
+ }
+ auto opsSecond =
+ createOperations(derivedSrc->baseCRS(), targetCRS, context);
+ for (const auto &opSecond : opsSecond) {
+ try {
+ res.emplace_back(ConcatenatedOperation::createComputeMetadata(
+ {opFirst, opSecond}, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsBoundToGeog(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ const auto &hubSrc = boundSrc->hubCRS();
+ auto hubSrcGeog = dynamic_cast<const crs::GeographicCRS *>(hubSrc.get());
+ auto geogCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractGeographicCRS();
+ {
+ // If geogCRSOfBaseOfBoundSrc is a DerivedGeographicCRS, use its base
+ // instead (if it is a GeographicCRS)
+ auto derivedGeogCRS =
+ std::dynamic_pointer_cast<crs::DerivedGeographicCRS>(
+ geogCRSOfBaseOfBoundSrc);
+ if (derivedGeogCRS) {
+ auto baseCRS = std::dynamic_pointer_cast<crs::GeographicCRS>(
+ derivedGeogCRS->baseCRS().as_nullable());
+ if (baseCRS) {
+ geogCRSOfBaseOfBoundSrc = baseCRS;
+ }
+ }
+ }
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable() : nullptr;
+
+ const auto geogDstDatum = geogDst->datumNonNull(dbContext);
+
+ // If the underlying datum of the source is the same as the target, do
+ // not consider the boundCRS at all, but just its base
+ if (geogCRSOfBaseOfBoundSrc) {
+ auto geogCRSOfBaseOfBoundSrcDatum =
+ geogCRSOfBaseOfBoundSrc->datumNonNull(dbContext);
+ if (geogCRSOfBaseOfBoundSrcDatum->_isEquivalentTo(
+ geogDstDatum.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ res = createOperations(boundSrc->baseCRS(), targetCRS, context);
+ return;
+ }
+ }
+
+ bool triedBoundCrsToGeogCRSSameAsHubCRS = false;
+ // Is it: boundCRS to a geogCRS that is the same as the hubCRS ?
+ if (hubSrcGeog && geogCRSOfBaseOfBoundSrc &&
+ (hubSrcGeog->_isEquivalentTo(
+ geogDst, util::IComparable::Criterion::EQUIVALENT) ||
+ hubSrcGeog->is2DPartOf3D(NN_NO_CHECK(geogDst), dbContext))) {
+ triedBoundCrsToGeogCRSSameAsHubCRS = true;
+
+ CoordinateOperationPtr opIntermediate;
+ if (!geogCRSOfBaseOfBoundSrc->_isEquivalentTo(
+ boundSrc->transformation()->sourceCRS().get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto opsIntermediate = createOperations(
+ NN_NO_CHECK(geogCRSOfBaseOfBoundSrc),
+ boundSrc->transformation()->sourceCRS(), context);
+ assert(!opsIntermediate.empty());
+ opIntermediate = opsIntermediate.front();
+ }
+
+ if (boundSrc->baseCRS() == geogCRSOfBaseOfBoundSrc) {
+ if (opIntermediate) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {NN_NO_CHECK(opIntermediate),
+ boundSrc->transformation()},
+ disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ } else {
+ // Optimization to avoid creating a useless concatenated
+ // operation
+ res.emplace_back(boundSrc->transformation());
+ }
+ return;
+ }
+ auto opsFirst = createOperations(
+ boundSrc->baseCRS(), NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context);
+ if (!opsFirst.empty()) {
+ for (const auto &opFirst : opsFirst) {
+ try {
+ std::vector<CoordinateOperationNNPtr> subops;
+ subops.emplace_back(opFirst);
+ if (opIntermediate) {
+ subops.emplace_back(NN_NO_CHECK(opIntermediate));
+ }
+ subops.emplace_back(boundSrc->transformation());
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ subops, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+ // If the datum are equivalent, this is also fine
+ } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog &&
+ hubSrcGeog->datumNonNull(dbContext)->_isEquivalentTo(
+ geogDstDatum.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()) {
+ CoordinateOperationPtr opIntermediate;
+ if (!geogCRSOfBaseOfBoundSrc->_isEquivalentTo(
+ boundSrc->transformation()->sourceCRS().get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto opsIntermediate = createOperations(
+ NN_NO_CHECK(geogCRSOfBaseOfBoundSrc),
+ boundSrc->transformation()->sourceCRS(), context);
+ assert(!opsIntermediate.empty());
+ opIntermediate = opsIntermediate.front();
+ }
+ for (const auto &opFirst : opsFirst) {
+ for (const auto &opLast : opsLast) {
+ try {
+ std::vector<CoordinateOperationNNPtr> subops;
+ subops.emplace_back(opFirst);
+ if (opIntermediate) {
+ subops.emplace_back(NN_NO_CHECK(opIntermediate));
+ }
+ subops.emplace_back(boundSrc->transformation());
+ subops.emplace_back(opLast);
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ subops, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+ // 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 &&
+ geogCRSOfBaseOfBoundSrc->ellipsoid()->_isEquivalentTo(
+ datum::Ellipsoid::CLARKE_1866.get(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ hubSrcGeog->datumNonNull(dbContext)->_isEquivalentTo(
+ datum::GeodeticReferenceFrame::EPSG_6326.get(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ geogDstDatum->_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;
+ } 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}, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+ }
+ }
+
+ if (hubSrcGeog &&
+ hubSrcGeog->_isEquivalentTo(geogDst,
+ util::IComparable::Criterion::EQUIVALENT) &&
+ dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get())) {
+ auto transfSrc = boundSrc->transformation()->sourceCRS();
+ if (dynamic_cast<const crs::VerticalCRS *>(transfSrc.get()) &&
+ !boundSrc->baseCRS()->_isEquivalentTo(
+ transfSrc.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ auto opsFirst =
+ createOperations(boundSrc->baseCRS(), transfSrc, context);
+ for (const auto &opFirst : opsFirst) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {opFirst, boundSrc->transformation()},
+ disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ return;
+ }
+
+ res.emplace_back(boundSrc->transformation());
+ return;
+ }
+
+ if (!triedBoundCrsToGeogCRSSameAsHubCRS && hubSrcGeog &&
+ geogCRSOfBaseOfBoundSrc) {
+ // This one should go to the above 'Is it: boundCRS to a geogCRS
+ // that is the same as the hubCRS ?' case
+ auto opsFirst = createOperations(sourceCRS, hubSrc, context);
+ auto opsLast = createOperations(hubSrc, targetCRS, context);
+ if (!opsFirst.empty() && !opsLast.empty()) {
+ for (const auto &opFirst : opsFirst) {
+ for (const auto &opLast : opsLast) {
+ // Exclude artificial transformations from the hub
+ // to the target CRS, if it is the only one.
+ if (opsLast.size() > 1 ||
+ !opLast->hasBallparkTransformation()) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {opFirst, opLast},
+ disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ } else {
+ // std::cerr << "excluded " << opLast->nameStr() <<
+ // std::endl;
+ }
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+ }
+
+ auto vertCRSOfBaseOfBoundSrc =
+ dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get());
+ if (vertCRSOfBaseOfBoundSrc && hubSrcGeog) {
+ auto opsFirst = createOperations(sourceCRS, hubSrc, context);
+ if (context.skipHorizontalTransformation) {
+ if (!opsFirst.empty()) {
+ const auto &hubAxisList =
+ hubSrcGeog->coordinateSystem()->axisList();
+ const auto &targetAxisList =
+ geogDst->coordinateSystem()->axisList();
+ if (hubAxisList.size() == 3 && targetAxisList.size() == 3 &&
+ !hubAxisList[2]->_isEquivalentTo(
+ targetAxisList[2].get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+
+ const auto &srcAxis = hubAxisList[2];
+ const double convSrc = srcAxis->unit().conversionToSI();
+ const auto &dstAxis = targetAxisList[2];
+ const double convDst = dstAxis->unit().conversionToSI();
+ const bool srcIsUp =
+ srcAxis->direction() == cs::AxisDirection::UP;
+ const bool srcIsDown =
+ srcAxis->direction() == cs::AxisDirection::DOWN;
+ const bool dstIsUp =
+ dstAxis->direction() == cs::AxisDirection::UP;
+ const bool dstIsDown =
+ dstAxis->direction() == cs::AxisDirection::DOWN;
+ const bool heightDepthReversal =
+ ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp));
+
+ const double factor = convSrc / convDst;
+ auto conv = Conversion::createChangeVerticalUnit(
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ "Change of vertical unit"),
+ common::Scale(heightDepthReversal ? -factor : factor));
+
+ conv->setCRSs(
+ hubSrc,
+ hubSrc->demoteTo2D(std::string(), dbContext)
+ ->promoteTo3D(std::string(), dbContext, dstAxis),
+ nullptr);
+
+ for (const auto &op : opsFirst) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {op, conv}, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ } else {
+ res = opsFirst;
+ }
+ }
+ return;
+ } else {
+ auto opsSecond = createOperations(hubSrc, targetCRS, context);
+ if (!opsFirst.empty() && !opsSecond.empty()) {
+ for (const auto &opFirst : opsFirst) {
+ for (const auto &opLast : opsSecond) {
+ // Exclude artificial transformations from the hub
+ // to the target CRS
+ if (!opLast->hasBallparkTransformation()) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::
+ createComputeMetadata(
+ {opFirst, opLast},
+ disallowEmptyIntersection));
+ } catch (
+ const InvalidOperationEmptyIntersection &) {
+ }
+ } else {
+ // std::cerr << "excluded " << opLast->nameStr() <<
+ // std::endl;
+ }
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+ }
+ }
+
+ res = createOperations(boundSrc->baseCRS(), targetCRS, context);
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsBoundToVert(
+ const crs::CRSNNPtr & /*sourceCRS*/, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ 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 = createOperations(boundSrc->baseCRS(), targetCRS, context);
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsVertToVert(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::VerticalCRS *vertSrc,
+ const crs::VerticalCRS *vertDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable() : nullptr;
+
+ const auto srcDatum = vertSrc->datumNonNull(dbContext);
+ const auto dstDatum = vertDst->datumNonNull(dbContext);
+ const bool equivalentVDatum = srcDatum->_isEquivalentTo(
+ dstDatum.get(), util::IComparable::Criterion::EQUIVALENT);
+
+ const auto &srcAxis = vertSrc->coordinateSystem()->axisList()[0];
+ const double convSrc = srcAxis->unit().conversionToSI();
+ const auto &dstAxis = vertDst->coordinateSystem()->axisList()[0];
+ const double convDst = dstAxis->unit().conversionToSI();
+ const bool srcIsUp = srcAxis->direction() == cs::AxisDirection::UP;
+ const bool srcIsDown = srcAxis->direction() == cs::AxisDirection::DOWN;
+ const bool dstIsUp = dstAxis->direction() == cs::AxisDirection::UP;
+ const bool dstIsDown = dstAxis->direction() == cs::AxisDirection::DOWN;
+ const bool heightDepthReversal =
+ ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp));
+
+ const double factor = convSrc / convDst;
+ auto name = buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr());
+ if (!equivalentVDatum) {
+ name += BALLPARK_VERTICAL_TRANSFORMATION;
+ auto conv = Transformation::createChangeVerticalUnit(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name),
+ sourceCRS, targetCRS,
+ // In case of a height depth reversal, we should probably have
+ // 2 steps instead of putting a negative factor...
+ common::Scale(heightDepthReversal ? -factor : factor), {});
+ conv->setHasBallparkTransformation(true);
+ res.push_back(conv);
+ } else if (convSrc != convDst || !heightDepthReversal) {
+ auto conv = Conversion::createChangeVerticalUnit(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name),
+ // In case of a height depth reversal, we should probably have
+ // 2 steps instead of putting a negative factor...
+ common::Scale(heightDepthReversal ? -factor : factor));
+ conv->setCRSs(sourceCRS, targetCRS, nullptr);
+ res.push_back(conv);
+ } else {
+ auto conv = Conversion::createHeightDepthReversal(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name));
+ conv->setCRSs(sourceCRS, targetCRS, nullptr);
+ res.push_back(conv);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsVertToGeog(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::VerticalCRS *vertSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ if (vertSrc->identifiers().empty()) {
+ const auto &vertSrcName = vertSrc->nameStr();
+ const auto &authFactory = context.context->getAuthorityFactory();
+ if (authFactory != nullptr && vertSrcName != "unnamed" &&
+ vertSrcName != "unknown") {
+ auto matches = authFactory->createObjectsFromName(
+ vertSrcName, {io::AuthorityFactory::ObjectType::VERTICAL_CRS},
+ false, 2);
+ if (matches.size() == 1) {
+ const auto &match = matches.front();
+ if (vertSrc->_isEquivalentTo(
+ match.get(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ !match->identifiers().empty()) {
+ auto resTmp = createOperations(
+ NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<crs::VerticalCRS>(
+ match)),
+ targetCRS, context);
+ res.insert(res.end(), resTmp.begin(), resTmp.end());
+ return;
+ }
+ }
+ }
+ }
+
+ createOperationsVertToGeogBallpark(sourceCRS, targetCRS, context, vertSrc,
+ geogDst, res);
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsVertToGeogBallpark(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &, const crs::VerticalCRS *vertSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ const auto &srcAxis = vertSrc->coordinateSystem()->axisList()[0];
+ const double convSrc = srcAxis->unit().conversionToSI();
+ double convDst = 1.0;
+ const auto &geogAxis = geogDst->coordinateSystem()->axisList();
+ bool dstIsUp = true;
+ bool dstIsDown = false;
+ if (geogAxis.size() == 3) {
+ const auto &dstAxis = geogAxis[2];
+ convDst = dstAxis->unit().conversionToSI();
+ dstIsUp = dstAxis->direction() == cs::AxisDirection::UP;
+ dstIsDown = dstAxis->direction() == cs::AxisDirection::DOWN;
+ }
+ const bool srcIsUp = srcAxis->direction() == cs::AxisDirection::UP;
+ const bool srcIsDown = srcAxis->direction() == cs::AxisDirection::DOWN;
+ const bool heightDepthReversal =
+ ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp));
+
+ const double factor = convSrc / convDst;
+
+ 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,
+ buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr()) +
+ BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT)
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ sameExtent ? NN_NO_CHECK(sourceCRSExtent)
+ : metadata::Extent::WORLD);
+
+ auto conv = Transformation::createChangeVerticalUnit(
+ map, sourceCRS, targetCRS,
+ common::Scale(heightDepthReversal ? -factor : factor), {});
+ conv->setHasBallparkTransformation(true);
+ res.push_back(conv);
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsBoundToBound(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::BoundCRS *boundDst, std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ // BoundCRS to BoundCRS of horizontal CRS using the same (geographic) hub
+ 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, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+ }
+
+ // BoundCRS to BoundCRS of vertical CRS using the same vertical datum
+ // ==> ignore the bound transformation
+ auto baseOfBoundSrcAsVertCRS =
+ dynamic_cast<crs::VerticalCRS *>(boundSrc->baseCRS().get());
+ auto baseOfBoundDstAsVertCRS =
+ dynamic_cast<crs::VerticalCRS *>(boundDst->baseCRS().get());
+ if (baseOfBoundSrcAsVertCRS && baseOfBoundDstAsVertCRS) {
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable()
+ : nullptr;
+
+ const auto datumSrc = baseOfBoundSrcAsVertCRS->datumNonNull(dbContext);
+ const auto datumDst = baseOfBoundDstAsVertCRS->datumNonNull(dbContext);
+ if (datumSrc->nameStr() == datumDst->nameStr() &&
+ (datumSrc->nameStr() != "unknown" ||
+ boundSrc->transformation()->_isEquivalentTo(
+ boundDst->transformation().get(),
+ util::IComparable::Criterion::EQUIVALENT))) {
+ res = createOperations(boundSrc->baseCRS(), boundDst->baseCRS(),
+ context);
+ return;
+ }
+ }
+
+ // BoundCRS to BoundCRS of vertical CRS
+ auto vertCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractVerticalCRS();
+ auto vertCRSOfBaseOfBoundDst = boundDst->baseCRS()->extractVerticalCRS();
+ if (hubSrcGeog && hubDstGeog &&
+ hubSrcGeog->_isEquivalentTo(hubDstGeog,
+ util::IComparable::Criterion::EQUIVALENT) &&
+ vertCRSOfBaseOfBoundSrc && vertCRSOfBaseOfBoundDst) {
+ auto opsFirst = createOperations(sourceCRS, hubSrc, 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, opLast}, disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+ }
+
+ res = createOperations(boundSrc->baseCRS(), boundDst->baseCRS(), context);
+}
+
+// ---------------------------------------------------------------------------
+
+static std::vector<CoordinateOperationNNPtr>
+getOps(const CoordinateOperationNNPtr &op) {
+ auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get());
+ if (concatenated)
+ return concatenated->operations();
+ return {op};
+}
+
+// ---------------------------------------------------------------------------
+
+static bool useDifferentTransformationsForSameSourceTarget(
+ const CoordinateOperationNNPtr &opA, const CoordinateOperationNNPtr &opB) {
+ auto subOpsA = getOps(opA);
+ auto subOpsB = getOps(opB);
+ for (const auto &subOpA : subOpsA) {
+ if (!dynamic_cast<const Transformation *>(subOpA.get()))
+ continue;
+ if (subOpA->sourceCRS()->nameStr() == "unknown" ||
+ subOpA->targetCRS()->nameStr() == "unknown")
+ continue;
+ for (const auto &subOpB : subOpsB) {
+ if (!dynamic_cast<const Transformation *>(subOpB.get()))
+ continue;
+ if (subOpB->sourceCRS()->nameStr() == "unknown" ||
+ subOpB->targetCRS()->nameStr() == "unknown")
+ continue;
+
+ if (subOpA->sourceCRS()->nameStr() ==
+ subOpB->sourceCRS()->nameStr() &&
+ subOpA->targetCRS()->nameStr() ==
+ subOpB->targetCRS()->nameStr()) {
+ if (starts_with(subOpA->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ starts_with(subOpB->nameStr(), NULL_GEOGRAPHIC_OFFSET)) {
+ continue;
+ }
+
+ if (!subOpA->isEquivalentTo(subOpB.get())) {
+ return true;
+ }
+ } else if (subOpA->sourceCRS()->nameStr() ==
+ subOpB->targetCRS()->nameStr() &&
+ subOpA->targetCRS()->nameStr() ==
+ subOpB->sourceCRS()->nameStr()) {
+ if (starts_with(subOpA->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ starts_with(subOpB->nameStr(), NULL_GEOGRAPHIC_OFFSET)) {
+ continue;
+ }
+
+ if (!subOpA->isEquivalentTo(subOpB->inverse().get())) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+static crs::GeographicCRSPtr
+getInterpolationGeogCRS(const CoordinateOperationNNPtr &verticalTransform,
+ const io::DatabaseContextPtr &dbContext) {
+ crs::GeographicCRSPtr interpolationGeogCRS;
+ auto transformationVerticalTransform =
+ dynamic_cast<const Transformation *>(verticalTransform.get());
+ if (transformationVerticalTransform == nullptr) {
+ const auto concat = dynamic_cast<const ConcatenatedOperation *>(
+ verticalTransform.get());
+ if (concat) {
+ const auto &steps = concat->operations();
+ // Is this change of unit and/or height depth reversal +
+ // transformation ?
+ for (const auto &step : steps) {
+ const auto transf =
+ dynamic_cast<const Transformation *>(step.get());
+ if (transf) {
+ // Only support a single Transformation in the steps
+ if (transformationVerticalTransform != nullptr) {
+ transformationVerticalTransform = nullptr;
+ break;
+ }
+ transformationVerticalTransform = transf;
+ }
+ }
+ }
+ }
+ if (transformationVerticalTransform &&
+ !transformationVerticalTransform->hasBallparkTransformation()) {
+ auto interpTransformCRS =
+ transformationVerticalTransform->interpolationCRS();
+ if (interpTransformCRS) {
+ interpolationGeogCRS =
+ std::dynamic_pointer_cast<crs::GeographicCRS>(
+ interpTransformCRS);
+ } else {
+ // If no explicit interpolation CRS, then
+ // this will be the geographic CRS of the
+ // vertical to geog transformation
+ interpolationGeogCRS =
+ std::dynamic_pointer_cast<crs::GeographicCRS>(
+ transformationVerticalTransform->targetCRS().as_nullable());
+ }
+ }
+
+ if (interpolationGeogCRS) {
+ if (interpolationGeogCRS->coordinateSystem()->axisList().size() == 3) {
+ // We need to force the interpolation CRS, which
+ // will
+ // frequently be 3D, to 2D to avoid transformations
+ // between source CRS and interpolation CRS to have
+ // 3D terms.
+ interpolationGeogCRS =
+ interpolationGeogCRS->demoteTo2D(std::string(), dbContext)
+ .as_nullable();
+ }
+ }
+
+ return interpolationGeogCRS;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::CompoundCRS *compoundSrc,
+ const crs::GeographicCRS *geogDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ ENTER_FUNCTION();
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto &componentsSrc = compoundSrc->componentReferenceSystems();
+ if (!componentsSrc.empty()) {
+
+ if (componentsSrc.size() == 2) {
+ auto derivedHSrc =
+ dynamic_cast<const crs::DerivedCRS *>(componentsSrc[0].get());
+ if (derivedHSrc) {
+ std::vector<crs::CRSNNPtr> intermComponents{
+ derivedHSrc->baseCRS(), componentsSrc[1]};
+ auto properties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ intermComponents[0]->nameStr() + " + " +
+ intermComponents[1]->nameStr());
+ auto intermCompound =
+ crs::CompoundCRS::create(properties, intermComponents);
+ auto opsFirst =
+ createOperations(sourceCRS, intermCompound, context);
+ assert(!opsFirst.empty());
+ auto opsLast =
+ createOperations(intermCompound, targetCRS, context);
+ for (const auto &opLast : opsLast) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {opsFirst.front(), opLast},
+ disallowEmptyIntersection));
+ } catch (const std::exception &) {
+ }
+ }
+ return;
+ }
+ }
+
+ std::vector<CoordinateOperationNNPtr> horizTransforms;
+ auto srcGeogCRS = componentsSrc[0]->extractGeographicCRS();
+ if (srcGeogCRS) {
+ horizTransforms =
+ createOperations(componentsSrc[0], targetCRS, context);
+ }
+ std::vector<CoordinateOperationNNPtr> verticalTransforms;
+
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable()
+ : nullptr;
+ if (componentsSrc.size() >= 2 &&
+ componentsSrc[1]->extractVerticalCRS()) {
+
+ struct SetSkipHorizontalTransform {
+ Context &context;
+
+ explicit SetSkipHorizontalTransform(Context &contextIn)
+ : context(contextIn) {
+ assert(!context.skipHorizontalTransformation);
+ context.skipHorizontalTransformation = true;
+ }
+
+ ~SetSkipHorizontalTransform() {
+ context.skipHorizontalTransformation = false;
+ }
+ };
+ SetSkipHorizontalTransform setSkipHorizontalTransform(context);
+
+ verticalTransforms = createOperations(
+ componentsSrc[1],
+ targetCRS->promoteTo3D(std::string(), dbContext), context);
+ bool foundRegisteredTransformWithAllGridsAvailable = false;
+ const auto gridAvailabilityUse =
+ context.context->getGridAvailabilityUse();
+ const bool ignoreMissingGrids =
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY;
+ for (const auto &op : verticalTransforms) {
+ if (hasIdentifiers(op) && dbContext) {
+ bool missingGrid = false;
+ if (!ignoreMissingGrids) {
+ const auto gridsNeeded = op->gridsNeeded(
+ dbContext,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE);
+ for (const auto &gridDesc : gridsNeeded) {
+ if (!gridDesc.available) {
+ missingGrid = true;
+ break;
+ }
+ }
+ }
+ if (!missingGrid) {
+ foundRegisteredTransformWithAllGridsAvailable = true;
+ break;
+ }
+ }
+ }
+ if (!foundRegisteredTransformWithAllGridsAvailable && srcGeogCRS &&
+ !srcGeogCRS->_isEquivalentTo(
+ geogDst, util::IComparable::Criterion::EQUIVALENT) &&
+ !srcGeogCRS->is2DPartOf3D(NN_NO_CHECK(geogDst), dbContext)) {
+ auto verticalTransformsTmp = createOperations(
+ componentsSrc[1],
+ NN_NO_CHECK(srcGeogCRS)
+ ->promoteTo3D(std::string(), dbContext),
+ context);
+ bool foundRegisteredTransform = false;
+ foundRegisteredTransformWithAllGridsAvailable = false;
+ for (const auto &op : verticalTransformsTmp) {
+ if (hasIdentifiers(op) && dbContext) {
+ bool missingGrid = false;
+ if (!ignoreMissingGrids) {
+ const auto gridsNeeded = op->gridsNeeded(
+ dbContext,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE);
+ for (const auto &gridDesc : gridsNeeded) {
+ if (!gridDesc.available) {
+ missingGrid = true;
+ break;
+ }
+ }
+ }
+ foundRegisteredTransform = true;
+ if (!missingGrid) {
+ foundRegisteredTransformWithAllGridsAvailable =
+ true;
+ break;
+ }
+ }
+ }
+ if (foundRegisteredTransformWithAllGridsAvailable) {
+ verticalTransforms = verticalTransformsTmp;
+ } else if (foundRegisteredTransform) {
+ verticalTransforms.insert(verticalTransforms.end(),
+ verticalTransformsTmp.begin(),
+ verticalTransformsTmp.end());
+ }
+ }
+ }
+
+ if (horizTransforms.empty() || verticalTransforms.empty()) {
+ res = horizTransforms;
+ return;
+ }
+
+ typedef std::pair<std::vector<CoordinateOperationNNPtr>,
+ std::vector<CoordinateOperationNNPtr>>
+ PairOfTransforms;
+ std::map<std::string, PairOfTransforms>
+ cacheHorizToInterpAndInterpToTarget;
+
+ for (const auto &verticalTransform : verticalTransforms) {
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("Considering vertical transform " +
+ objectAsStr(verticalTransform.get()));
+#endif
+ crs::GeographicCRSPtr interpolationGeogCRS =
+ getInterpolationGeogCRS(verticalTransform, dbContext);
+ if (interpolationGeogCRS) {
+#ifdef TRACE_CREATE_OPERATIONS
+ logTrace("Using " + objectAsStr(interpolationGeogCRS.get()) +
+ " as interpolation CRS");
+#endif
+ std::vector<CoordinateOperationNNPtr> srcToInterpOps;
+ std::vector<CoordinateOperationNNPtr> interpToTargetOps;
+
+ std::string key;
+ const auto &ids = interpolationGeogCRS->identifiers();
+ if (!ids.empty()) {
+ key =
+ (*ids.front()->codeSpace()) + ':' + ids.front()->code();
+ }
+
+ const auto computeOpsToInterp =
+ [&srcToInterpOps, &interpToTargetOps, &componentsSrc,
+ &interpolationGeogCRS, &targetCRS, &dbContext,
+ &context]() {
+ srcToInterpOps = createOperations(
+ componentsSrc[0], NN_NO_CHECK(interpolationGeogCRS),
+ context);
+ auto target2D =
+ targetCRS->demoteTo2D(std::string(), dbContext);
+ if (!componentsSrc[0]->isEquivalentTo(
+ target2D.get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ // We do the transformation from the
+ // interpolationCRS
+ // to the target one in 3D (see #2225)
+ // But we don't do that between sourceCRS and
+ // interpolationCRS, as this would mess with an
+ // orthometric elevation.
+ auto interp3D = interpolationGeogCRS->promoteTo3D(
+ std::string(), dbContext);
+ interpToTargetOps =
+ createOperations(interp3D, targetCRS, context);
+ }
+ };
+
+ if (!key.empty()) {
+ auto iter = cacheHorizToInterpAndInterpToTarget.find(key);
+ if (iter == cacheHorizToInterpAndInterpToTarget.end()) {
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("looking for horizontal transformation "
+ "from source to interpCRS and interpCRS to "
+ "target");
+#endif
+ computeOpsToInterp();
+ cacheHorizToInterpAndInterpToTarget[key] =
+ PairOfTransforms(srcToInterpOps, interpToTargetOps);
+ } else {
+ srcToInterpOps = iter->second.first;
+ interpToTargetOps = iter->second.second;
+ }
+ } else {
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("looking for horizontal transformation "
+ "from source to interpCRS and interpCRS to "
+ "target");
+#endif
+ computeOpsToInterp();
+ }
+
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_BLOCK("creating HorizVerticalHorizPROJBased operations");
+#endif
+ for (const auto &srcToInterp : srcToInterpOps) {
+ if (interpToTargetOps.empty()) {
+ try {
+ auto op = createHorizVerticalHorizPROJBased(
+ sourceCRS, targetCRS, srcToInterp,
+ verticalTransform, srcToInterp->inverse(),
+ interpolationGeogCRS, true);
+ res.emplace_back(op);
+ } catch (const std::exception &) {
+ }
+ } else {
+ for (const auto &interpToTarget : interpToTargetOps) {
+
+ if (useDifferentTransformationsForSameSourceTarget(
+ srcToInterp, interpToTarget)) {
+ continue;
+ }
+
+ try {
+ auto op = createHorizVerticalHorizPROJBased(
+ sourceCRS, targetCRS, srcToInterp,
+ verticalTransform, interpToTarget,
+ interpolationGeogCRS, true);
+ res.emplace_back(op);
+ } catch (const std::exception &) {
+ }
+ }
+ }
+ }
+ } else {
+ // This case is probably only correct if
+ // verticalTransform and horizTransform are independent
+ // and in particular that verticalTransform does not
+ // involve a grid, because of the rather arbitrary order
+ // horizontal then vertical applied
+ for (const auto &horizTransform : horizTransforms) {
+ try {
+ auto op = createHorizVerticalPROJBased(
+ sourceCRS, targetCRS, horizTransform,
+ verticalTransform, disallowEmptyIntersection);
+ res.emplace_back(op);
+ } catch (const std::exception &) {
+ }
+ }
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsToGeod(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::GeodeticCRS *geodDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ auto cs = cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight(
+ common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE);
+ auto intermGeog3DCRS =
+ util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, geodDst->nameStr())
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ geodDst->datum(), geodDst->datumEnsemble(), cs));
+ auto sourceToGeog3DOps =
+ createOperations(sourceCRS, intermGeog3DCRS, context);
+ auto geog3DToTargetOps =
+ createOperations(intermGeog3DCRS, targetCRS, context);
+ if (!geog3DToTargetOps.empty()) {
+ for (const auto &op : sourceToGeog3DOps) {
+ auto newOp = op->shallowClone();
+ setCRSs(newOp.get(), sourceCRS, intermGeog3DCRS);
+ try {
+ res.emplace_back(ConcatenatedOperation::createComputeMetadata(
+ {newOp, geog3DToTargetOps.front()},
+ disallowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsCompoundToCompound(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::CompoundCRS *compoundSrc,
+ const crs::CompoundCRS *compoundDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ const auto &componentsSrc = compoundSrc->componentReferenceSystems();
+ const auto &componentsDst = compoundDst->componentReferenceSystems();
+ if (componentsSrc.empty() || componentsSrc.size() != componentsDst.size()) {
+ return;
+ }
+ const auto srcGeog = componentsSrc[0]->extractGeographicCRS();
+ const auto dstGeog = componentsDst[0]->extractGeographicCRS();
+ if (srcGeog == nullptr || dstGeog == nullptr) {
+ return;
+ }
+
+ std::vector<CoordinateOperationNNPtr> verticalTransforms;
+ if (componentsSrc.size() >= 2 && componentsSrc[1]->extractVerticalCRS() &&
+ componentsDst[1]->extractVerticalCRS()) {
+ if (!componentsSrc[1]->_isEquivalentTo(componentsDst[1].get())) {
+ verticalTransforms =
+ createOperations(componentsSrc[1], componentsDst[1], context);
+ }
+ }
+
+ // If we didn't find a non-ballpark transformation between
+ // the 2 vertical CRS, then try through intermediate geographic CRS
+ // For example
+ // WGS 84 + EGM96 --> ETRS89 + Belfast height where
+ // there is a geoid model for EGM96 referenced to WGS 84
+ // and a geoid model for Belfast height referenced to ETRS89
+ if (verticalTransforms.size() == 1 &&
+ verticalTransforms.front()->hasBallparkTransformation()) {
+ auto dbContext =
+ context.context->getAuthorityFactory()->databaseContext();
+ const auto intermGeogSrc =
+ srcGeog->promoteTo3D(std::string(), dbContext);
+ const bool intermGeogSrcIsSameAsIntermGeogDst =
+ srcGeog->_isEquivalentTo(dstGeog.get());
+ const auto intermGeogDst =
+ intermGeogSrcIsSameAsIntermGeogDst
+ ? intermGeogSrc
+ : dstGeog->promoteTo3D(std::string(), dbContext);
+ const auto opsSrcToGeog =
+ createOperations(sourceCRS, intermGeogSrc, context);
+ const auto opsGeogToTarget =
+ createOperations(intermGeogDst, targetCRS, context);
+ const bool hasNonTrivalSrcTransf =
+ !opsSrcToGeog.empty() &&
+ !opsSrcToGeog.front()->hasBallparkTransformation();
+ const bool hasNonTrivialTargetTransf =
+ !opsGeogToTarget.empty() &&
+ !opsGeogToTarget.front()->hasBallparkTransformation();
+ if (hasNonTrivalSrcTransf && hasNonTrivialTargetTransf) {
+ const auto opsGeogSrcToGeogDst =
+ createOperations(intermGeogSrc, intermGeogDst, context);
+ for (const auto &op1 : opsSrcToGeog) {
+ if (op1->hasBallparkTransformation()) {
+ // std::cerr << "excluded " << op1->nameStr() << std::endl;
+ continue;
+ }
+ for (const auto &op2 : opsGeogSrcToGeogDst) {
+ for (const auto &op3 : opsGeogToTarget) {
+ if (op3->hasBallparkTransformation()) {
+ // std::cerr << "excluded " << op3->nameStr() <<
+ // std::endl;
+ continue;
+ }
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ intermGeogSrcIsSameAsIntermGeogDst
+ ? std::vector<
+ CoordinateOperationNNPtr>{op1,
+ op3}
+ : std::vector<
+ CoordinateOperationNNPtr>{op1,
+ op2,
+ op3},
+ disallowEmptyIntersection));
+ } catch (const std::exception &) {
+ }
+ }
+ }
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+
+ for (const auto &verticalTransform : verticalTransforms) {
+ auto interpolationGeogCRS = NN_NO_CHECK(srcGeog);
+ auto interpTransformCRS = verticalTransform->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));
+ }
+ } else {
+ auto compSrc0BoundCrs =
+ dynamic_cast<crs::BoundCRS *>(componentsSrc[0].get());
+ auto compDst0BoundCrs =
+ dynamic_cast<crs::BoundCRS *>(componentsDst[0].get());
+ if (compSrc0BoundCrs && compDst0BoundCrs &&
+ dynamic_cast<crs::GeographicCRS *>(
+ compSrc0BoundCrs->hubCRS().get()) &&
+ compSrc0BoundCrs->hubCRS()->_isEquivalentTo(
+ compDst0BoundCrs->hubCRS().get())) {
+ interpolationGeogCRS = NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ compSrc0BoundCrs->hubCRS()));
+ }
+ }
+ auto opSrcCRSToGeogCRS =
+ createOperations(componentsSrc[0], interpolationGeogCRS, context);
+ auto opGeogCRStoDstCRS =
+ createOperations(interpolationGeogCRS, componentsDst[0], context);
+ for (const auto &opSrc : opSrcCRSToGeogCRS) {
+ for (const auto &opDst : opGeogCRStoDstCRS) {
+
+ try {
+ auto op = createHorizVerticalHorizPROJBased(
+ sourceCRS, targetCRS, opSrc, verticalTransform, opDst,
+ interpolationGeogCRS, true);
+ res.emplace_back(op);
+ } catch (const InvalidOperationEmptyIntersection &) {
+ } catch (const io::FormattingException &) {
+ }
+ }
+ }
+ }
+
+ if (verticalTransforms.empty()) {
+ auto resTmp =
+ createOperations(componentsSrc[0], componentsDst[0], context);
+ for (const auto &op : resTmp) {
+ auto opClone = op->shallowClone();
+ setCRSs(opClone.get(), sourceCRS, targetCRS);
+ res.emplace_back(opClone);
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsBoundToCompound(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context, const crs::BoundCRS *boundSrc,
+ const crs::CompoundCRS *compoundDst,
+ std::vector<CoordinateOperationNNPtr> &res) {
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto dbContext =
+ authFactory ? authFactory->databaseContext().as_nullable() : nullptr;
+
+ const auto &componentsDst = compoundDst->componentReferenceSystems();
+ if (!componentsDst.empty()) {
+ auto compDst0BoundCrs =
+ dynamic_cast<crs::BoundCRS *>(componentsDst[0].get());
+ if (compDst0BoundCrs) {
+ auto boundSrcHubAsGeogCRS =
+ dynamic_cast<crs::GeographicCRS *>(boundSrc->hubCRS().get());
+ auto compDst0BoundCrsHubAsGeogCRS =
+ dynamic_cast<crs::GeographicCRS *>(
+ compDst0BoundCrs->hubCRS().get());
+ if (boundSrcHubAsGeogCRS && compDst0BoundCrsHubAsGeogCRS) {
+ const auto boundSrcHubAsGeogCRSDatum =
+ boundSrcHubAsGeogCRS->datumNonNull(dbContext);
+ const auto compDst0BoundCrsHubAsGeogCRSDatum =
+ compDst0BoundCrsHubAsGeogCRS->datumNonNull(dbContext);
+ if (boundSrcHubAsGeogCRSDatum->_isEquivalentTo(
+ compDst0BoundCrsHubAsGeogCRSDatum.get())) {
+ auto cs = cs::EllipsoidalCS::
+ createLatitudeLongitudeEllipsoidalHeight(
+ common::UnitOfMeasure::DEGREE,
+ common::UnitOfMeasure::METRE);
+ auto intermGeog3DCRS = util::nn_static_pointer_cast<
+ crs::CRS>(crs::GeographicCRS::create(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY,
+ boundSrcHubAsGeogCRS->nameStr())
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ boundSrcHubAsGeogCRS->datum(),
+ boundSrcHubAsGeogCRS->datumEnsemble(), cs));
+ auto sourceToGeog3DOps =
+ createOperations(sourceCRS, intermGeog3DCRS, context);
+ auto geog3DToTargetOps =
+ createOperations(intermGeog3DCRS, targetCRS, context);
+ for (const auto &opSrc : sourceToGeog3DOps) {
+ for (const auto &opDst : geog3DToTargetOps) {
+ if (opSrc->targetCRS() && opDst->sourceCRS() &&
+ !opSrc->targetCRS()->_isEquivalentTo(
+ opDst->sourceCRS().get())) {
+ // Shouldn't happen normally, but typically
+ // one of them can be 2D and the other 3D
+ // due to above createOperations() not
+ // exactly setting the expected source and
+ // target CRS.
+ // So create an adapter operation...
+ auto intermOps = createOperations(
+ NN_NO_CHECK(opSrc->targetCRS()),
+ NN_NO_CHECK(opDst->sourceCRS()), context);
+ if (!intermOps.empty()) {
+ res.emplace_back(
+ ConcatenatedOperation::
+ createComputeMetadata(
+ {opSrc, intermOps.front(),
+ opDst},
+ disallowEmptyIntersection));
+ }
+ } else {
+ res.emplace_back(
+ ConcatenatedOperation::
+ createComputeMetadata(
+ {opSrc, opDst},
+ disallowEmptyIntersection));
+ }
+ }
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ // There might be better things to do, but for now just ignore the
+ // transformation of the bound CRS
+ res = createOperations(boundSrc->baseCRS(), targetCRS, context);
+}
+//! @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.
+ *
+ * When one of the source or target CRS has a vertical component but not the
+ * other one, the one that has no vertical component is automatically promoted
+ * to a 3D version, where its vertical axis is the ellipsoidal height in metres,
+ * using the ellipsoid of the base geodetic CRS.
+ *
+ * @param sourceCRS source CRS.
+ * @param targetCRS target 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 {
+
+#ifdef TRACE_CREATE_OPERATIONS
+ ENTER_FUNCTION();
+#endif
+ // 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;
+ const auto &authFactory = context->getAuthorityFactory();
+
+ metadata::ExtentPtr sourceCRSExtent;
+ auto l_resolvedSourceCRS =
+ crs::CRS::getResolvedCRS(l_sourceCRS, authFactory, sourceCRSExtent);
+ metadata::ExtentPtr targetCRSExtent;
+ auto l_resolvedTargetCRS =
+ crs::CRS::getResolvedCRS(l_targetCRS, authFactory, targetCRSExtent);
+ Private::Context contextPrivate(sourceCRSExtent, targetCRSExtent, context);
+
+ if (context->getSourceAndTargetCRSExtentUse() ==
+ CoordinateOperationContext::SourceTargetCRSExtentUse::INTERSECTION) {
+ if (sourceCRSExtent && targetCRSExtent &&
+ !sourceCRSExtent->intersects(NN_NO_CHECK(targetCRSExtent))) {
+ return std::vector<CoordinateOperationNNPtr>();
+ }
+ }
+
+ return filterAndSort(Private::createOperations(l_resolvedSourceCRS,
+ l_resolvedTargetCRS,
+ contextPrivate),
+ context, sourceCRSExtent, targetCRSExtent);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a CoordinateOperationFactory.
+ */
+CoordinateOperationFactoryNNPtr CoordinateOperationFactory::create() {
+ return NN_NO_CHECK(
+ CoordinateOperationFactory::make_unique<CoordinateOperationFactory>());
+}
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+
+namespace crs {
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+crs::CRSNNPtr CRS::getResolvedCRS(const crs::CRSNNPtr &crs,
+ const io::AuthorityFactoryPtr &authFactory,
+ metadata::ExtentPtr &extentOut) {
+ const auto &ids = crs->identifiers();
+ const auto &name = crs->nameStr();
+
+ bool approxExtent;
+ extentOut = operation::getExtentPossiblySynthetized(crs, approxExtent);
+
+ // We try to "identify" the provided CRS with the ones of the database,
+ // but in a more restricted way that what identify() does.
+ // If we get a match from id in priority, and from name as a fallback, and
+ // that they are equivalent to the input CRS, then use the identified CRS.
+ // Even if they aren't equivalent, we update extentOut with the one of the
+ // identified CRS if our input one is absent/not reliable.
+
+ const auto tryToIdentifyByName = [&crs, &name, &authFactory, approxExtent,
+ &extentOut](
+ io::AuthorityFactory::ObjectType objectType) {
+ if (name != "unknown" && name != "unnamed") {
+ auto matches = authFactory->createObjectsFromName(
+ name, {objectType}, false, 2);
+ if (matches.size() == 1) {
+ const auto match =
+ util::nn_static_pointer_cast<crs::CRS>(matches.front());
+ if (approxExtent || !extentOut) {
+ extentOut = operation::getExtent(match);
+ }
+ if (match->isEquivalentTo(
+ crs.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ return match;
+ }
+ }
+ }
+ return crs;
+ };
+
+ auto geogCRS = dynamic_cast<crs::GeographicCRS *>(crs.get());
+ if (geogCRS && authFactory) {
+ if (!ids.empty()) {
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(), *ids.front()->codeSpace());
+ try {
+ auto resolvedCrs(
+ tmpAuthFactory->createGeographicCRS(ids.front()->code()));
+ if (approxExtent || !extentOut) {
+ extentOut = operation::getExtent(resolvedCrs);
+ }
+ if (resolvedCrs->isEquivalentTo(
+ crs.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs);
+ }
+ } catch (const std::exception &) {
+ }
+ } else {
+ return tryToIdentifyByName(
+ geogCRS->coordinateSystem()->axisList().size() == 2
+ ? io::AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS
+ : io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS);
+ }
+ }
+
+ auto projectedCrs = dynamic_cast<crs::ProjectedCRS *>(crs.get());
+ if (projectedCrs && authFactory) {
+ if (!ids.empty()) {
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(), *ids.front()->codeSpace());
+ try {
+ auto resolvedCrs(
+ tmpAuthFactory->createProjectedCRS(ids.front()->code()));
+ if (approxExtent || !extentOut) {
+ extentOut = operation::getExtent(resolvedCrs);
+ }
+ if (resolvedCrs->isEquivalentTo(
+ crs.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs);
+ }
+ } catch (const std::exception &) {
+ }
+ } else {
+ return tryToIdentifyByName(
+ io::AuthorityFactory::ObjectType::PROJECTED_CRS);
+ }
+ }
+
+ auto compoundCrs = dynamic_cast<crs::CompoundCRS *>(crs.get());
+ if (compoundCrs && authFactory) {
+ if (!ids.empty()) {
+ const auto tmpAuthFactory = io::AuthorityFactory::create(
+ authFactory->databaseContext(), *ids.front()->codeSpace());
+ try {
+ auto resolvedCrs(
+ tmpAuthFactory->createCompoundCRS(ids.front()->code()));
+ if (approxExtent || !extentOut) {
+ extentOut = operation::getExtent(resolvedCrs);
+ }
+ if (resolvedCrs->isEquivalentTo(
+ crs.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ return util::nn_static_pointer_cast<crs::CRS>(resolvedCrs);
+ }
+ } catch (const std::exception &) {
+ }
+ } else {
+ auto outCrs = tryToIdentifyByName(
+ io::AuthorityFactory::ObjectType::COMPOUND_CRS);
+ const auto &components = compoundCrs->componentReferenceSystems();
+ if (outCrs.get() != crs.get()) {
+ bool hasGeoid = false;
+ if (components.size() == 2) {
+ auto vertCRS =
+ dynamic_cast<crs::VerticalCRS *>(components[1].get());
+ if (vertCRS && !vertCRS->geoidModel().empty()) {
+ hasGeoid = true;
+ }
+ }
+ if (!hasGeoid) {
+ return outCrs;
+ }
+ }
+ if (approxExtent || !extentOut) {
+ // If we still did not get a reliable extent, then try to
+ // resolve the components of the compoundCRS, and take the
+ // intersection of their extent.
+ extentOut = metadata::ExtentPtr();
+ for (const auto &component : components) {
+ metadata::ExtentPtr componentExtent;
+ getResolvedCRS(component, authFactory, componentExtent);
+ if (extentOut && componentExtent)
+ extentOut = extentOut->intersection(
+ NN_NO_CHECK(componentExtent));
+ else if (componentExtent)
+ extentOut = componentExtent;
+ }
+ }
+ }
+ }
+ return crs;
+}
+
+//! @endcond
+
+} // namespace crs
+NS_PROJ_END
diff --git a/src/iso19111/operation/esriparammappings.cpp b/src/iso19111/operation/esriparammappings.cpp
new file mode 100644
index 00000000..44886e95
--- /dev/null
+++ b/src/iso19111/operation/esriparammappings.cpp
@@ -0,0 +1,1123 @@
+// This file was generated by scripts/build_esri_projection_mapping.py. DO NOT
+// EDIT !
+
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Mappings between ESRI projection and parameters names and WKT2
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2019, 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 "esriparammappings.hpp"
+#include "proj_constants.h"
+
+#include "proj/internal/internal.hpp"
+
+NS_PROJ_START
+
+using namespace internal;
+
+namespace operation {
+
+//! @cond Doxygen_Suppress
+
+const ESRIParamMapping paramsESRI_Plate_Carree[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+const ESRIParamMapping paramsESRI_Equidistant_Cylindrical[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Miller_Cylindrical[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Mercator[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+const ESRIParamMapping paramsESRI_Gauss_Kruger[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+const ESRIParamMapping paramsESRI_Transverse_Mercator[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Transverse_Mercator_Complex[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Albers[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false},
+ {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Sinusoidal[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Mollweide[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Eckert_I[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Eckert_II[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Eckert_III[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Eckert_IV[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Eckert_V[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Eckert_VI[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Gall_Stereographic[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Behrmann[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", true},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "30.0", true},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Winkel_I[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Winkel_II[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt1[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt2[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false},
+ {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt3[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false},
+ {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false},
+ {"Scale_Factor", nullptr, 0, "1.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt4[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false},
+ {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_ELLIPSOID_SCALE_FACTOR,
+ EPSG_CODE_PARAMETER_ELLIPSOID_SCALE_FACTOR, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Polyconic[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Quartic_Authalic[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Loximuthal[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Central_Parallel", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Bonne[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping
+ paramsESRI_Hotine_Oblique_Mercator_Two_Point_Natural_Origin[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false},
+ {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false},
+ {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false},
+ {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false},
+ {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Stereographic[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Equidistant_Conic[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false},
+ {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Cassini[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Scale_Factor", nullptr, 0, "1.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Van_der_Grinten_I[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Robinson[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Two_Point_Equidistant[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false},
+ {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false},
+ {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false},
+ {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Azimuthal_Equidistant[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Lambert_Azimuthal_Equal_Area[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Cylindrical_Equal_Area[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping
+ paramsESRI_Hotine_Oblique_Mercator_Two_Point_Center[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false},
+ {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false},
+ {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false},
+ {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false},
+ {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+const ESRIParamMapping
+ paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false},
+ {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+const ESRIParamMapping paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false},
+ {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Double_Stereographic[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Krovak_alt1[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Pseudo_Standard_Parallel_1",
+ EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, "0.0", false},
+ {"Azimuth", EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS,
+ EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false},
+ {"X_Scale", nullptr, 0, "1.0", false},
+ {"Y_Scale", nullptr, 0, "1.0", false},
+ {"XY_Plane_Rotation", nullptr, 0, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+static const ESRIParamMapping paramsESRI_Krovak_alt2[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Pseudo_Standard_Parallel_1",
+ EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, "0.0", false},
+ {"Azimuth", EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS,
+ EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false},
+ {"X_Scale", nullptr, 0, "-1.0", false},
+ {"Y_Scale", nullptr, 0, "1.0", false},
+ {"XY_Plane_Rotation", nullptr, 0, "90.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_New_Zealand_Map_Grid[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Longitude_Of_Origin", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Orthographic[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Local[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Scale_Factor", nullptr, 0, "1.0", false},
+ {"Azimuth", nullptr, 0, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Winkel_Tripel[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Aitoff[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Flat_Polar_Quartic[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Craster_Parabolic[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Gnomonic[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Times[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Vertical_Near_Side_Perspective[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, "0.0", false},
+ {"Height", EPSG_NAME_PARAMETER_VIEWPOINT_HEIGHT,
+ EPSG_CODE_PARAMETER_VIEWPOINT_HEIGHT, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Stereographic_North_Pole[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Stereographic_South_Pole[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin[] =
+ {{"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false},
+ {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false},
+ {"XY_Plane_Rotation", EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Center[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false},
+ {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false},
+ {"XY_Plane_Rotation", EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Goode_Homolosine_alt1[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Option", nullptr, 0, "1.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+static const ESRIParamMapping paramsESRI_Goode_Homolosine_alt2[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Option", nullptr, 0, "2.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Equidistant_Cylindrical_Ellipsoidal[] =
+ {{"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Laborde_Oblique_Mercator[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, "0.0", false},
+ {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Gnomonic_Ellipsoidal[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Wagner_IV[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Origin", nullptr, 0, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Wagner_V[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Wagner_VII[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Natural_Earth[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Natural_Earth_II[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Patterson[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Compact_Miller[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Geostationary_Satellite[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Height", "Satellite Height", 0, "0.0", false},
+ {"Option", nullptr, 0, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Mercator_Auxiliary_Sphere[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Auxiliary_Sphere_Type", nullptr, 0, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Mercator_Variant_A[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Mercator_Variant_C[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false},
+ {"Latitude_Of_Origin", nullptr, 0, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_Transverse_Cylindrical_Equal_Area[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIParamMapping paramsESRI_IGAC_Plano_Cartesiano[] = {
+ {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false},
+ {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false},
+ {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false},
+ {"Height", EPSG_NAME_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT,
+ EPSG_CODE_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, "0.0", false},
+ {nullptr, nullptr, 0, "0.0", false}};
+
+static const ESRIMethodMapping esriMappings[] = {
+ {"Plate_Carree", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL,
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, paramsESRI_Plate_Carree},
+ {"Plate_Carree", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL,
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL,
+ paramsESRI_Plate_Carree},
+ {"Equidistant_Cylindrical", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL,
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL,
+ paramsESRI_Equidistant_Cylindrical},
+ {"Miller_Cylindrical", PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL, 0,
+ paramsESRI_Miller_Cylindrical},
+ {"Mercator", EPSG_NAME_METHOD_MERCATOR_VARIANT_B,
+ EPSG_CODE_METHOD_MERCATOR_VARIANT_B, paramsESRI_Mercator},
+ {"Gauss_Kruger", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR,
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, paramsESRI_Gauss_Kruger},
+ {"Transverse_Mercator", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR,
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, paramsESRI_Transverse_Mercator},
+ {"Transverse_Mercator_Complex", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR,
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR,
+ paramsESRI_Transverse_Mercator_Complex},
+ {"Albers", EPSG_NAME_METHOD_ALBERS_EQUAL_AREA,
+ EPSG_CODE_METHOD_ALBERS_EQUAL_AREA, paramsESRI_Albers},
+ {"Sinusoidal", PROJ_WKT2_NAME_METHOD_SINUSOIDAL, 0, paramsESRI_Sinusoidal},
+ {"Mollweide", PROJ_WKT2_NAME_METHOD_MOLLWEIDE, 0, paramsESRI_Mollweide},
+ {"Eckert_I", PROJ_WKT2_NAME_METHOD_ECKERT_I, 0, paramsESRI_Eckert_I},
+ {"Eckert_II", PROJ_WKT2_NAME_METHOD_ECKERT_II, 0, paramsESRI_Eckert_II},
+ {"Eckert_III", PROJ_WKT2_NAME_METHOD_ECKERT_III, 0, paramsESRI_Eckert_III},
+ {"Eckert_IV", PROJ_WKT2_NAME_METHOD_ECKERT_IV, 0, paramsESRI_Eckert_IV},
+ {"Eckert_V", PROJ_WKT2_NAME_METHOD_ECKERT_V, 0, paramsESRI_Eckert_V},
+ {"Eckert_VI", PROJ_WKT2_NAME_METHOD_ECKERT_VI, 0, paramsESRI_Eckert_VI},
+ {"Gall_Stereographic", PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC, 0,
+ paramsESRI_Gall_Stereographic},
+ {"Behrmann", EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA,
+ EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, paramsESRI_Behrmann},
+ {"Winkel_I", "Winkel I", 0, paramsESRI_Winkel_I},
+ {"Winkel_II", "Winkel II", 0, paramsESRI_Winkel_II},
+ {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP,
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP,
+ paramsESRI_Lambert_Conformal_Conic_alt1},
+ {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP,
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP,
+ paramsESRI_Lambert_Conformal_Conic_alt2},
+ {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP,
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP,
+ paramsESRI_Lambert_Conformal_Conic_alt3},
+ {"Lambert_Conformal_Conic",
+ EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN,
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN,
+ paramsESRI_Lambert_Conformal_Conic_alt4},
+ {"Polyconic", EPSG_NAME_METHOD_AMERICAN_POLYCONIC,
+ EPSG_CODE_METHOD_AMERICAN_POLYCONIC, paramsESRI_Polyconic},
+ {"Quartic_Authalic", "Quartic Authalic", 0, paramsESRI_Quartic_Authalic},
+ {"Loximuthal", "Loximuthal", 0, paramsESRI_Loximuthal},
+ {"Bonne", EPSG_NAME_METHOD_BONNE, EPSG_CODE_METHOD_BONNE, paramsESRI_Bonne},
+ {"Hotine_Oblique_Mercator_Two_Point_Natural_Origin",
+ PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0,
+ paramsESRI_Hotine_Oblique_Mercator_Two_Point_Natural_Origin},
+ {"Stereographic", PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC, 0,
+ paramsESRI_Stereographic},
+ {"Equidistant_Conic", PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC, 0,
+ paramsESRI_Equidistant_Conic},
+ {"Cassini", EPSG_NAME_METHOD_CASSINI_SOLDNER,
+ EPSG_CODE_METHOD_CASSINI_SOLDNER, paramsESRI_Cassini},
+ {"Van_der_Grinten_I", PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN, 0,
+ paramsESRI_Van_der_Grinten_I},
+ {"Robinson", PROJ_WKT2_NAME_METHOD_ROBINSON, 0, paramsESRI_Robinson},
+ {"Two_Point_Equidistant", PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, 0,
+ paramsESRI_Two_Point_Equidistant},
+ {"Azimuthal_Equidistant", EPSG_NAME_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT,
+ EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT,
+ paramsESRI_Azimuthal_Equidistant},
+ {"Lambert_Azimuthal_Equal_Area",
+ EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA,
+ EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA,
+ paramsESRI_Lambert_Azimuthal_Equal_Area},
+ {"Cylindrical_Equal_Area", EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA,
+ EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA,
+ paramsESRI_Cylindrical_Equal_Area},
+ {"Hotine_Oblique_Mercator_Two_Point_Center",
+ PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0,
+ paramsESRI_Hotine_Oblique_Mercator_Two_Point_Center},
+ {"Hotine_Oblique_Mercator_Azimuth_Natural_Origin",
+ EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A,
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A,
+ paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin},
+ {"Hotine_Oblique_Mercator_Azimuth_Center",
+ EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B,
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B,
+ paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center},
+ {"Double_Stereographic", EPSG_NAME_METHOD_OBLIQUE_STEREOGRAPHIC,
+ EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC, paramsESRI_Double_Stereographic},
+ {"Krovak", EPSG_NAME_METHOD_KROVAK, EPSG_CODE_METHOD_KROVAK,
+ paramsESRI_Krovak_alt1},
+ {"Krovak", EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED,
+ EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED, paramsESRI_Krovak_alt2},
+ {"New_Zealand_Map_Grid", EPSG_NAME_METHOD_NZMG, EPSG_CODE_METHOD_NZMG,
+ paramsESRI_New_Zealand_Map_Grid},
+ {"Orthographic", PROJ_WKT2_NAME_ORTHOGRAPHIC_SPHERICAL, 0,
+ paramsESRI_Orthographic},
+ {"Local", EPSG_NAME_METHOD_ORTHOGRAPHIC, EPSG_CODE_METHOD_ORTHOGRAPHIC,
+ paramsESRI_Local},
+ {"Winkel_Tripel", "Winkel Tripel", 0, paramsESRI_Winkel_Tripel},
+ {"Aitoff", "Aitoff", 0, paramsESRI_Aitoff},
+ {"Flat_Polar_Quartic", PROJ_WKT2_NAME_METHOD_FLAT_POLAR_QUARTIC, 0,
+ paramsESRI_Flat_Polar_Quartic},
+ {"Craster_Parabolic", "Craster Parabolic", 0, paramsESRI_Craster_Parabolic},
+ {"Gnomonic", PROJ_WKT2_NAME_METHOD_GNOMONIC, 0, paramsESRI_Gnomonic},
+ {"Times", PROJ_WKT2_NAME_METHOD_TIMES, 0, paramsESRI_Times},
+ {"Vertical_Near_Side_Perspective", EPSG_NAME_METHOD_VERTICAL_PERSPECTIVE,
+ EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE,
+ paramsESRI_Vertical_Near_Side_Perspective},
+ {"Stereographic_North_Pole", EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B,
+ EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B,
+ paramsESRI_Stereographic_North_Pole},
+ {"Stereographic_South_Pole", EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B,
+ EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B,
+ paramsESRI_Stereographic_South_Pole},
+ {"Rectified_Skew_Orthomorphic_Natural_Origin",
+ EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A,
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A,
+ paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin},
+ {"Rectified_Skew_Orthomorphic_Center",
+ EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B,
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B,
+ paramsESRI_Rectified_Skew_Orthomorphic_Center},
+ {"Goode_Homolosine", PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, 0,
+ paramsESRI_Goode_Homolosine_alt1},
+ {"Goode_Homolosine",
+ PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE_OCEAN, 0,
+ paramsESRI_Goode_Homolosine_alt2},
+ {"Equidistant_Cylindrical_Ellipsoidal",
+ EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL,
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL,
+ paramsESRI_Equidistant_Cylindrical_Ellipsoidal},
+ {"Laborde_Oblique_Mercator", EPSG_NAME_METHOD_LABORDE_OBLIQUE_MERCATOR,
+ EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR,
+ paramsESRI_Laborde_Oblique_Mercator},
+ {"Gnomonic_Ellipsoidal", PROJ_WKT2_NAME_METHOD_GNOMONIC, 0,
+ paramsESRI_Gnomonic_Ellipsoidal},
+ {"Wagner_IV", PROJ_WKT2_NAME_METHOD_WAGNER_IV, 0, paramsESRI_Wagner_IV},
+ {"Wagner_V", PROJ_WKT2_NAME_METHOD_WAGNER_V, 0, paramsESRI_Wagner_V},
+ {"Wagner_VII", PROJ_WKT2_NAME_METHOD_WAGNER_VII, 0, paramsESRI_Wagner_VII},
+ {"Natural_Earth", PROJ_WKT2_NAME_METHOD_NATURAL_EARTH, 0,
+ paramsESRI_Natural_Earth},
+ {"Natural_Earth_II", PROJ_WKT2_NAME_METHOD_NATURAL_EARTH_II, 0,
+ paramsESRI_Natural_Earth_II},
+ {"Patterson", PROJ_WKT2_NAME_METHOD_PATTERSON, 0, paramsESRI_Patterson},
+ {"Compact_Miller", PROJ_WKT2_NAME_METHOD_COMPACT_MILLER, 0,
+ paramsESRI_Compact_Miller},
+ {"Geostationary_Satellite",
+ PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y, 0,
+ paramsESRI_Geostationary_Satellite},
+ {"Mercator_Auxiliary_Sphere",
+ EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR,
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR,
+ paramsESRI_Mercator_Auxiliary_Sphere},
+ {"Mercator_Variant_A", EPSG_NAME_METHOD_MERCATOR_VARIANT_A,
+ EPSG_CODE_METHOD_MERCATOR_VARIANT_A, paramsESRI_Mercator_Variant_A},
+ {"Mercator_Variant_C", EPSG_NAME_METHOD_MERCATOR_VARIANT_B,
+ EPSG_CODE_METHOD_MERCATOR_VARIANT_B, paramsESRI_Mercator_Variant_C},
+ {"Transverse_Cylindrical_Equal_Area", "Transverse Cylindrical Equal Area",
+ 0, paramsESRI_Transverse_Cylindrical_Equal_Area},
+ {"IGAC_Plano_Cartesiano", EPSG_NAME_METHOD_COLOMBIA_URBAN,
+ EPSG_CODE_METHOD_COLOMBIA_URBAN, paramsESRI_IGAC_Plano_Cartesiano},
+};
+
+// ---------------------------------------------------------------------------
+
+const ESRIMethodMapping *getEsriMappings(size_t &nElts) {
+ nElts = sizeof(esriMappings) / sizeof(esriMappings[0]);
+ return esriMappings;
+}
+
+// ---------------------------------------------------------------------------
+
+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;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
diff --git a/src/iso19111/operation/esriparammappings.hpp b/src/iso19111/operation/esriparammappings.hpp
new file mode 100644
index 00000000..373c1f22
--- /dev/null
+++ b/src/iso19111/operation/esriparammappings.hpp
@@ -0,0 +1,86 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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 ESRIPARAMMAPPINGS_HPP
+#define ESRIPARAMMAPPINGS_HPP
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/util.hpp"
+
+#include "esriparammappings.hpp"
+#include <vector>
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+struct ESRIParamMapping {
+ const char *esri_name;
+ const char *wkt2_name;
+ int epsg_code;
+ const char *fixed_value;
+ bool is_fixed_value;
+};
+
+struct ESRIMethodMapping {
+ const char *esri_name;
+ const char *wkt2_name;
+ int epsg_code;
+ const ESRIParamMapping *const params;
+};
+
+extern const ESRIParamMapping paramsESRI_Plate_Carree[];
+extern const ESRIParamMapping paramsESRI_Equidistant_Cylindrical[];
+extern const ESRIParamMapping paramsESRI_Gauss_Kruger[];
+extern const ESRIParamMapping paramsESRI_Transverse_Mercator[];
+extern const ESRIParamMapping
+ paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin[];
+extern const ESRIParamMapping
+ paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin[];
+extern const ESRIParamMapping
+ paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center[];
+extern const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Center[];
+
+const ESRIMethodMapping *getEsriMappings(size_t &nElts);
+
+std::vector<const ESRIMethodMapping *>
+getMappingsFromESRI(const std::string &esri_name);
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
+
+#endif // ESRIPARAMMAPPINGS_HPP
diff --git a/src/iso19111/operation/operationmethod_private.hpp b/src/iso19111/operation/operationmethod_private.hpp
new file mode 100644
index 00000000..83d29026
--- /dev/null
+++ b/src/iso19111/operation/operationmethod_private.hpp
@@ -0,0 +1,56 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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 OPERATIONMETHOD_PRIVATE_HPP
+#define OPERATIONMETHOD_PRIVATE_HPP
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/util.hpp"
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct OperationMethod::Private {
+ util::optional<std::string> formula_{};
+ util::optional<metadata::Citation> formulaCitation_{};
+ std::vector<GeneralOperationParameterNNPtr> parameters_{};
+ std::string projMethodOverride_{};
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
+
+#endif // OPERATIONMETHOD_PRIVATE_HPP
diff --git a/src/iso19111/operation/oputils.cpp b/src/iso19111/operation/oputils.cpp
new file mode 100644
index 00000000..b5834edf
--- /dev/null
+++ b/src/iso19111/operation/oputils.cpp
@@ -0,0 +1,643 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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 <string.h>
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/crs.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+#include "proj/internal/io_internal.hpp"
+
+#include "oputils.hpp"
+#include "parammappings.hpp"
+
+#include "proj_constants.h"
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+
+using namespace internal;
+
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+const char *BALLPARK_GEOCENTRIC_TRANSLATION = "Ballpark geocentric translation";
+const char *NULL_GEOGRAPHIC_OFFSET = "Null geographic offset";
+const char *NULL_GEOCENTRIC_TRANSLATION = "Null geocentric translation";
+const char *BALLPARK_GEOGRAPHIC_OFFSET = "Ballpark geographic offset";
+const char *BALLPARK_VERTICAL_TRANSFORMATION =
+ " (ballpark vertical transformation)";
+const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT =
+ " (ballpark vertical transformation, without ellipsoid height to vertical "
+ "height correction)";
+
+// ---------------------------------------------------------------------------
+
+OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
+ const char *name = OperationParameter::getNameForEPSGCode(code);
+ assert(name);
+ return OperationParameter::create(createMapNameEPSGCode(name, code));
+}
+
+// ---------------------------------------------------------------------------
+
+util::PropertyMap createMethodMapNameEPSGCode(int code) {
+ const char *name = nullptr;
+ size_t nMethodNameCodes = 0;
+ const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes);
+ for (size_t i = 0; i < nMethodNameCodes; ++i) {
+ const auto &tuple = methodNameCodes[i];
+ if (tuple.epsg_code == code) {
+ name = tuple.name;
+ break;
+ }
+ }
+ assert(name);
+ return createMapNameEPSGCode(name, code);
+}
+
+// ---------------------------------------------------------------------------
+
+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);
+}
+
+// ---------------------------------------------------------------------------
+
+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);
+}
+
+// ---------------------------------------------------------------------------
+
+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 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 "";
+}
+
+// ---------------------------------------------------------------------------
+
+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;
+}
+
+// ---------------------------------------------------------------------------
+
+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);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+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;
+}
+
+// ---------------------------------------------------------------------------
+
+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, BALLPARK_GEOCENTRIC_TRANSLATION)) {
+ opType = BALLPARK_GEOCENTRIC_TRANSLATION;
+ } else if (starts_with(forwardName, BALLPARK_GEOGRAPHIC_OFFSET)) {
+ opType = BALLPARK_GEOGRAPHIC_OFFSET;
+ } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) {
+ opType = NULL_GEOGRAPHIC_OFFSET;
+ } else if (starts_with(forwardName, NULL_GEOCENTRIC_TRANSLATION)) {
+ opType = NULL_GEOCENTRIC_TRANSLATION;
+ } 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 (dynamic_cast<const Transformation *>(op) == nullptr &&
+ dynamic_cast<const ConcatenatedOperation *>(op) == nullptr &&
+ (starts_with(forwardName, INVERSE_OF) ||
+ forwardName.find(" + ") != std::string::npos)) {
+ std::vector<std::string> tokens;
+ std::string curToken;
+ bool inString = false;
+ for (size_t i = 0; i < forwardName.size(); ++i) {
+ if (inString) {
+ curToken += forwardName[i];
+ if (forwardName[i] == '\'') {
+ inString = false;
+ }
+ } else if (i + 3 < forwardName.size() &&
+ memcmp(&forwardName[i], " + ", 3) == 0) {
+ tokens.push_back(curToken);
+ curToken.clear();
+ i += 2;
+ } else if (forwardName[i] == '\'') {
+ inString = true;
+ curToken += forwardName[i];
+ } else {
+ curToken += forwardName[i];
+ }
+ }
+ if (!curToken.empty()) {
+ tokens.push_back(curToken);
+ }
+ for (size_t i = tokens.size(); i > 0;) {
+ i--;
+ if (!name.empty()) {
+ name += " + ";
+ }
+ if (starts_with(tokens[i], INVERSE_OF)) {
+ name += tokens[i].substr(INVERSE_OF.size());
+ } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME ||
+ tokens[i] == AXIS_ORDER_CHANGE_3D_NAME) {
+ name += tokens[i];
+ } else {
+ name += INVERSE_OF + tokens[i];
+ }
+ }
+ } else if (!sourceCRS || !targetCRS ||
+ forwardName != buildOpName(opType, sourceCRS, targetCRS)) {
+ if (forwardName.find(" + ") != std::string::npos) {
+ name = INVERSE_OF + '\'' + forwardName + '\'';
+ } else {
+ 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);
+ }
+
+ const std::string &remarks = op->remarks();
+ if (!remarks.empty()) {
+ map.set(common::IdentifiedObject::REMARKS_KEY, remarks);
+ }
+
+ addModifiedIdentifier(map, op, true, derivedFrom);
+
+ const auto so = dynamic_cast<const SingleOperation *>(op);
+ if (so) {
+ const int soMethodEPSGCode = so->method()->getEPSGCode();
+ if (soMethodEPSGCode > 0) {
+ map.set("OPERATION_METHOD_EPSG_CODE", soMethodEPSGCode);
+ }
+ }
+
+ return map;
+}
+
+// ---------------------------------------------------------------------------
+
+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 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();
+}
+
+// ---------------------------------------------------------------------------
+
+bool isTimeDependent(const std::string &methodName) {
+ return ci_find(methodName, "Time dependent") != std::string::npos ||
+ ci_find(methodName, "Time-dependent") != std::string::npos;
+}
+
+// ---------------------------------------------------------------------------
+
+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;
+}
+
+// ---------------------------------------------------------------------------
+
+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{};
+
+const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs) {
+ const auto &domains = crs->domains();
+ if (!domains.empty()) {
+ return domains[0]->domainOfValidity();
+ }
+ const auto *boundCRS = dynamic_cast<const crs::BoundCRS *>(crs.get());
+ if (boundCRS) {
+ return getExtent(boundCRS->baseCRS());
+ }
+ return nullExtent;
+}
+
+const metadata::ExtentPtr getExtentPossiblySynthetized(const crs::CRSNNPtr &crs,
+ bool &approxOut) {
+ const auto &rawExtent(getExtent(crs));
+ approxOut = false;
+ if (rawExtent)
+ return rawExtent;
+ const auto compoundCRS = dynamic_cast<const crs::CompoundCRS *>(crs.get());
+ if (compoundCRS) {
+ // For a compoundCRS, take the intersection of the extent of its
+ // components.
+ const auto &components = compoundCRS->componentReferenceSystems();
+ metadata::ExtentPtr extent;
+ approxOut = true;
+ for (const auto &component : components) {
+ const auto &componentExtent(getExtent(component));
+ if (extent && componentExtent)
+ extent = extent->intersection(NN_NO_CHECK(componentExtent));
+ else if (componentExtent)
+ extent = componentExtent;
+ }
+ return extent;
+ }
+ return rawExtent;
+}
+
+// ---------------------------------------------------------------------------
+
+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;
+}
+
+// ---------------------------------------------------------------------------
+
+// Returns the accuracy of an operation, or -1 if unknown
+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 concatenated operations, or -1 if unknown
+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;
+}
+
+// ---------------------------------------------------------------------------
+
+void exportSourceCRSAndTargetCRSToWKT(const CoordinateOperation *co,
+ io::WKTFormatter *formatter) {
+ auto l_sourceCRS = co->sourceCRS();
+ assert(l_sourceCRS);
+ auto l_targetCRS = co->targetCRS();
+ assert(l_targetCRS);
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ const bool canExportCRSId =
+ (isWKT2 && formatter->use2019Keywords() &&
+ !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId()));
+
+ const bool hasDomains = !co->domains().empty();
+ if (hasDomains) {
+ formatter->pushDisableUsage();
+ }
+
+ formatter->startNode(io::WKTConstants::SOURCECRS, false);
+ if (canExportCRSId && !l_sourceCRS->identifiers().empty()) {
+ // fake that top node has no id, so that the sourceCRS id is
+ // considered
+ formatter->pushHasId(false);
+ l_sourceCRS->_exportToWKT(formatter);
+ formatter->popHasId();
+ } else {
+ l_sourceCRS->_exportToWKT(formatter);
+ }
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::TARGETCRS, false);
+ if (canExportCRSId && !l_targetCRS->identifiers().empty()) {
+ // fake that top node has no id, so that the targetCRS id is
+ // considered
+ formatter->pushHasId(false);
+ l_targetCRS->_exportToWKT(formatter);
+ formatter->popHasId();
+ } else {
+ l_targetCRS->_exportToWKT(formatter);
+ }
+ formatter->endNode();
+
+ if (hasDomains) {
+ formatter->popDisableUsage();
+ }
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
diff --git a/src/iso19111/operation/oputils.hpp b/src/iso19111/operation/oputils.hpp
new file mode 100644
index 00000000..8c2ad1f0
--- /dev/null
+++ b/src/iso19111/operation/oputils.hpp
@@ -0,0 +1,121 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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 OPUTILS_HPP
+#define OPUTILS_HPP
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+extern const common::Measure nullMeasure;
+
+extern const std::string INVERSE_OF;
+
+extern const char *BALLPARK_GEOCENTRIC_TRANSLATION;
+extern const char *NULL_GEOGRAPHIC_OFFSET;
+extern const char *NULL_GEOCENTRIC_TRANSLATION;
+extern const char *BALLPARK_GEOGRAPHIC_OFFSET;
+extern const char *BALLPARK_VERTICAL_TRANSFORMATION;
+extern const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT;
+
+extern const std::string AXIS_ORDER_CHANGE_2D_NAME;
+extern const std::string AXIS_ORDER_CHANGE_3D_NAME;
+
+OperationParameterNNPtr createOpParamNameEPSGCode(int code);
+
+util::PropertyMap createMethodMapNameEPSGCode(int code);
+
+util::PropertyMap createMapNameEPSGCode(const std::string &name, int code);
+
+util::PropertyMap createMapNameEPSGCode(const char *name, int code);
+
+util::PropertyMap &addDomains(util::PropertyMap &map,
+ const common::ObjectUsage *obj);
+
+std::string buildOpName(const char *opType, const crs::CRSPtr &source,
+ const crs::CRSPtr &target);
+
+void addModifiedIdentifier(util::PropertyMap &map,
+ const common::IdentifiedObject *obj, bool inverse,
+ bool derivedFrom);
+util::PropertyMap
+createPropertiesForInverse(const OperationMethodNNPtr &method);
+
+util::PropertyMap createPropertiesForInverse(const CoordinateOperation *op,
+ bool derivedFrom,
+ bool approximateInversion);
+
+util::PropertyMap addDefaultNameIfNeeded(const util::PropertyMap &properties,
+ const std::string &defaultName);
+
+bool areEquivalentParameters(const std::string &a, const std::string &b);
+
+bool isTimeDependent(const std::string &methodName);
+
+std::string computeConcatenatedName(
+ const std::vector<CoordinateOperationNNPtr> &flattenOps);
+
+metadata::ExtentPtr getExtent(const std::vector<CoordinateOperationNNPtr> &ops,
+ bool conversionExtentIsWorld,
+ bool &emptyIntersection);
+
+metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op,
+ bool conversionExtentIsWorld,
+ bool &emptyIntersection);
+
+const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs);
+
+const metadata::ExtentPtr getExtentPossiblySynthetized(const crs::CRSNNPtr &crs,
+ bool &approxOut);
+
+double getAccuracy(const CoordinateOperationNNPtr &op);
+
+double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops);
+
+void exportSourceCRSAndTargetCRSToWKT(const CoordinateOperation *co,
+ io::WKTFormatter *formatter);
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
+
+#endif // OPUTILS_HPP
diff --git a/src/iso19111/operation/parammappings.cpp b/src/iso19111/operation/parammappings.cpp
new file mode 100644
index 00000000..de5f4c7e
--- /dev/null
+++ b/src/iso19111/operation/parammappings.cpp
@@ -0,0 +1,1551 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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.
+ ****************************************************************************/
+
+#include "parammappings.hpp"
+#include "oputils.hpp"
+#include "proj_constants.h"
+
+#include "proj/internal/internal.hpp"
+
+NS_PROJ_START
+
+using namespace internal;
+
+namespace operation {
+
+//! @cond Doxygen_Suppress
+
+const char *WKT1_LATITUDE_OF_ORIGIN = "latitude_of_origin";
+const char *WKT1_CENTRAL_MERIDIAN = "central_meridian";
+const char *WKT1_SCALE_FACTOR = "scale_factor";
+const char *WKT1_FALSE_EASTING = "false_easting";
+const char *WKT1_FALSE_NORTHING = "false_northing";
+const char *WKT1_STANDARD_PARALLEL_1 = "standard_parallel_1";
+const char *WKT1_STANDARD_PARALLEL_2 = "standard_parallel_2";
+const char *WKT1_LATITUDE_OF_CENTER = "latitude_of_center";
+const char *WKT1_LONGITUDE_OF_CENTER = "longitude_of_center";
+const char *WKT1_AZIMUTH = "azimuth";
+const char *WKT1_RECTIFIED_GRID_ANGLE = "rectified_grid_angle";
+
+static const char *lat_0 = "lat_0";
+static const char *lat_1 = "lat_1";
+static const char *lat_2 = "lat_2";
+static const char *lat_ts = "lat_ts";
+static const char *lon_0 = "lon_0";
+static const char *lon_1 = "lon_1";
+static const char *lon_2 = "lon_2";
+static const char *lonc = "lonc";
+static const char *alpha = "alpha";
+static const char *gamma = "gamma";
+static const char *k_0 = "k_0";
+static const char *k = "k";
+static const char *x_0 = "x_0";
+static const char *y_0 = "y_0";
+static const char *h = "h";
+
+// ---------------------------------------------------------------------------
+
+const ParamMapping paramLatitudeNatOrigin = {
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_ORIGIN,
+ common::UnitOfMeasure::Type::ANGULAR, lat_0};
+
+static const ParamMapping paramLongitudeNatOrigin = {
+ EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, WKT1_CENTRAL_MERIDIAN,
+ common::UnitOfMeasure::Type::ANGULAR, lon_0};
+
+static const ParamMapping paramScaleFactor = {
+ EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR,
+ common::UnitOfMeasure::Type::SCALE, k_0};
+
+static const ParamMapping paramScaleFactorK = {
+ EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR,
+ common::UnitOfMeasure::Type::SCALE, k};
+
+static const ParamMapping paramFalseEasting = {
+ EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING,
+ WKT1_FALSE_EASTING, common::UnitOfMeasure::Type::LINEAR, x_0};
+
+static const ParamMapping paramFalseNorthing = {
+ EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING,
+ WKT1_FALSE_NORTHING, common::UnitOfMeasure::Type::LINEAR, y_0};
+
+static const ParamMapping paramLatitudeFalseOrigin = {
+ EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, WKT1_LATITUDE_OF_ORIGIN,
+ common::UnitOfMeasure::Type::ANGULAR, lat_0};
+
+static const ParamMapping paramLongitudeFalseOrigin = {
+ EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, WKT1_CENTRAL_MERIDIAN,
+ common::UnitOfMeasure::Type::ANGULAR, lon_0};
+
+static const ParamMapping paramFalseEastingOrigin = {
+ EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, WKT1_FALSE_EASTING,
+ common::UnitOfMeasure::Type::LINEAR, x_0};
+
+static const ParamMapping paramFalseNorthingOrigin = {
+ EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, WKT1_FALSE_NORTHING,
+ common::UnitOfMeasure::Type::LINEAR, y_0};
+
+static const ParamMapping paramLatitude1stStdParallel = {
+ EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, WKT1_STANDARD_PARALLEL_1,
+ common::UnitOfMeasure::Type::ANGULAR, lat_1};
+
+static const ParamMapping paramLatitude2ndStdParallel = {
+ EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, WKT1_STANDARD_PARALLEL_2,
+ common::UnitOfMeasure::Type::ANGULAR, lat_2};
+
+static const ParamMapping *const paramsNatOriginScale[] = {
+ &paramLatitudeNatOrigin, &paramLongitudeNatOrigin, &paramScaleFactor,
+ &paramFalseEasting, &paramFalseNorthing, nullptr};
+
+static const ParamMapping *const paramsNatOriginScaleK[] = {
+ &paramLatitudeNatOrigin, &paramLongitudeNatOrigin, &paramScaleFactorK,
+ &paramFalseEasting, &paramFalseNorthing, nullptr};
+
+static const ParamMapping paramLatFirstPoint = {
+ "Latitude of 1st point", 0, "Latitude_Of_1st_Point",
+ common::UnitOfMeasure::Type::ANGULAR, lat_1};
+static const ParamMapping paramLongFirstPoint = {
+ "Longitude of 1st point", 0, "Longitude_Of_1st_Point",
+ common::UnitOfMeasure::Type::ANGULAR, lon_1};
+static const ParamMapping paramLatSecondPoint = {
+ "Latitude of 2nd point", 0, "Latitude_Of_2nd_Point",
+ common::UnitOfMeasure::Type::ANGULAR, lat_2};
+static const ParamMapping paramLongSecondPoint = {
+ "Longitude of 2nd point", 0, "Longitude_Of_2nd_Point",
+ common::UnitOfMeasure::Type::ANGULAR, lon_2};
+
+static const ParamMapping *const paramsTPEQD[] = {&paramLatFirstPoint,
+ &paramLongFirstPoint,
+ &paramLatSecondPoint,
+ &paramLongSecondPoint,
+ &paramFalseEasting,
+ &paramFalseNorthing,
+ nullptr};
+
+static const ParamMapping *const paramsTMG[] = {
+ &paramLatitudeFalseOrigin, &paramLongitudeFalseOrigin,
+ &paramFalseEastingOrigin, &paramFalseNorthingOrigin, nullptr};
+
+static const ParamMapping paramLatFalseOriginLatOfCenter = {
+ EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, WKT1_LATITUDE_OF_CENTER,
+ common::UnitOfMeasure::Type::ANGULAR, lat_0};
+
+static const ParamMapping paramLongFalseOriginLongOfCenter = {
+ EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, WKT1_LONGITUDE_OF_CENTER,
+ common::UnitOfMeasure::Type::ANGULAR, lon_0};
+
+static const ParamMapping *const paramsAEA[] = {
+ &paramLatFalseOriginLatOfCenter,
+ &paramLongFalseOriginLongOfCenter,
+ &paramLatitude1stStdParallel,
+ &paramLatitude2ndStdParallel,
+ &paramFalseEastingOrigin,
+ &paramFalseNorthingOrigin,
+ nullptr};
+
+static const ParamMapping *const paramsLCC2SP[] = {
+ &paramLatitudeFalseOrigin,
+ &paramLongitudeFalseOrigin,
+ &paramLatitude1stStdParallel,
+ &paramLatitude2ndStdParallel,
+ &paramFalseEastingOrigin,
+ &paramFalseNorthingOrigin,
+ nullptr,
+};
+
+static const ParamMapping paramEllipsoidScaleFactor = {
+ EPSG_NAME_PARAMETER_ELLIPSOID_SCALE_FACTOR,
+ EPSG_CODE_PARAMETER_ELLIPSOID_SCALE_FACTOR, nullptr,
+ common::UnitOfMeasure::Type::SCALE, k_0};
+
+static const ParamMapping *const paramsLCC2SPMichigan[] = {
+ &paramLatitudeFalseOrigin, &paramLongitudeFalseOrigin,
+ &paramLatitude1stStdParallel, &paramLatitude2ndStdParallel,
+ &paramFalseEastingOrigin, &paramFalseNorthingOrigin,
+ &paramEllipsoidScaleFactor, nullptr,
+};
+
+static const ParamMapping paramLatNatLatCenter = {
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_CENTER,
+ common::UnitOfMeasure::Type::ANGULAR, lat_0};
+
+static const ParamMapping paramLonNatLonCenter = {
+ EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, WKT1_LONGITUDE_OF_CENTER,
+ common::UnitOfMeasure::Type::ANGULAR, lon_0};
+
+static const ParamMapping *const paramsAEQD[]{
+ &paramLatNatLatCenter, &paramLonNatLonCenter, &paramFalseEasting,
+ &paramFalseNorthing, nullptr};
+
+static const ParamMapping *const paramsNatOrigin[] = {
+ &paramLatitudeNatOrigin, &paramLongitudeNatOrigin, &paramFalseEasting,
+ &paramFalseNorthing, nullptr};
+
+static const ParamMapping paramLatNatOriginLat1 = {
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_STANDARD_PARALLEL_1,
+ common::UnitOfMeasure::Type::ANGULAR, lat_1};
+
+static const ParamMapping *const paramsBonne[] = {
+ &paramLatNatOriginLat1, &paramLongitudeNatOrigin, &paramFalseEasting,
+ &paramFalseNorthing, nullptr};
+
+static const ParamMapping paramLat1stParallelLatTs = {
+ EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, WKT1_STANDARD_PARALLEL_1,
+ common::UnitOfMeasure::Type::ANGULAR, lat_ts};
+
+static const ParamMapping *const paramsCEA[] = {
+ &paramLat1stParallelLatTs, &paramLongitudeNatOrigin, &paramFalseEasting,
+ &paramFalseNorthing, nullptr};
+
+static const ParamMapping *const paramsEQDC[] = {&paramLatNatLatCenter,
+ &paramLonNatLonCenter,
+ &paramLatitude1stStdParallel,
+ &paramLatitude2ndStdParallel,
+ &paramFalseEasting,
+ &paramFalseNorthing,
+ nullptr};
+
+static const ParamMapping *const paramsLonNatOrigin[] = {
+ &paramLongitudeNatOrigin, &paramFalseEasting, &paramFalseNorthing, nullptr};
+
+static const ParamMapping *const paramsEqc[] = {
+ &paramLat1stParallelLatTs,
+ &paramLatitudeNatOrigin, // extension of EPSG, but used by GDAL / PROJ
+ &paramLongitudeNatOrigin, &paramFalseEasting,
+ &paramFalseNorthing, nullptr};
+
+static const ParamMapping paramSatelliteHeight = {
+ "Satellite Height", 0, "satellite_height",
+ common::UnitOfMeasure::Type::LINEAR, h};
+
+static const ParamMapping *const paramsGeos[] = {
+ &paramLongitudeNatOrigin, &paramSatelliteHeight, &paramFalseEasting,
+ &paramFalseNorthing, nullptr};
+
+static const ParamMapping paramLatCentreLatCenter = {
+ EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, WKT1_LATITUDE_OF_CENTER,
+ common::UnitOfMeasure::Type::ANGULAR, lat_0};
+
+static const ParamMapping paramLonCentreLonCenterLonc = {
+ EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, WKT1_LONGITUDE_OF_CENTER,
+ common::UnitOfMeasure::Type::ANGULAR, lonc};
+
+static const ParamMapping paramAzimuth = {
+ EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, WKT1_AZIMUTH,
+ common::UnitOfMeasure::Type::ANGULAR, alpha};
+
+static const ParamMapping paramAngleToSkewGrid = {
+ EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, WKT1_RECTIFIED_GRID_ANGLE,
+ common::UnitOfMeasure::Type::ANGULAR, gamma};
+static const ParamMapping paramScaleFactorInitialLine = {
+ EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE, WKT1_SCALE_FACTOR,
+ common::UnitOfMeasure::Type::SCALE, k};
+
+static const ParamMapping *const paramsHomVariantA[] = {
+ &paramLatCentreLatCenter,
+ &paramLonCentreLonCenterLonc,
+ &paramAzimuth,
+ &paramAngleToSkewGrid,
+ &paramScaleFactorInitialLine,
+ &paramFalseEasting,
+ &paramFalseNorthing,
+ nullptr};
+
+static const ParamMapping paramFalseEastingProjectionCentre = {
+ EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, WKT1_FALSE_EASTING,
+ common::UnitOfMeasure::Type::LINEAR, x_0};
+
+static const ParamMapping paramFalseNorthingProjectionCentre = {
+ EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, WKT1_FALSE_NORTHING,
+ common::UnitOfMeasure::Type::LINEAR, y_0};
+
+static const ParamMapping *const paramsHomVariantB[] = {
+ &paramLatCentreLatCenter,
+ &paramLonCentreLonCenterLonc,
+ &paramAzimuth,
+ &paramAngleToSkewGrid,
+ &paramScaleFactorInitialLine,
+ &paramFalseEastingProjectionCentre,
+ &paramFalseNorthingProjectionCentre,
+ nullptr};
+
+static const ParamMapping paramLatPoint1 = {
+ "Latitude of 1st point", 0, "latitude_of_point_1",
+ common::UnitOfMeasure::Type::ANGULAR, lat_1};
+
+static const ParamMapping paramLonPoint1 = {
+ "Longitude of 1st point", 0, "longitude_of_point_1",
+ common::UnitOfMeasure::Type::ANGULAR, lon_1};
+
+static const ParamMapping paramLatPoint2 = {
+ "Latitude of 2nd point", 0, "latitude_of_point_2",
+ common::UnitOfMeasure::Type::ANGULAR, lat_2};
+
+static const ParamMapping paramLonPoint2 = {
+ "Longitude of 2nd point", 0, "longitude_of_point_2",
+ common::UnitOfMeasure::Type::ANGULAR, lon_2};
+
+static const ParamMapping *const paramsHomTwoPoint[] = {
+ &paramLatCentreLatCenter,
+ &paramLatPoint1,
+ &paramLonPoint1,
+ &paramLatPoint2,
+ &paramLonPoint2,
+ &paramScaleFactorInitialLine,
+ &paramFalseEastingProjectionCentre,
+ &paramFalseNorthingProjectionCentre,
+ nullptr};
+
+static const ParamMapping *const paramsIMWP[] = {
+ &paramLongitudeNatOrigin, &paramLatFirstPoint, &paramLatSecondPoint,
+ &paramFalseEasting, &paramFalseNorthing, nullptr};
+
+static const ParamMapping paramLonCentreLonCenter = {
+ EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, WKT1_LONGITUDE_OF_CENTER,
+ common::UnitOfMeasure::Type::ANGULAR, lon_0};
+
+static const ParamMapping paramColatitudeConeAxis = {
+ EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS,
+ EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, WKT1_AZIMUTH,
+ common::UnitOfMeasure::Type::ANGULAR,
+ "alpha"}; /* ignored by PROJ currently */
+
+static const ParamMapping paramLatitudePseudoStdParallel = {
+ EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL,
+ "pseudo_standard_parallel_1", common::UnitOfMeasure::Type::ANGULAR,
+ nullptr}; /* ignored by PROJ currently */
+
+static const ParamMapping paramScaleFactorPseudoStdParallel = {
+ EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL,
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL,
+ WKT1_SCALE_FACTOR, common::UnitOfMeasure::Type::SCALE,
+ k}; /* ignored by PROJ currently */
+
+static const ParamMapping *const krovakParameters[] = {
+ &paramLatCentreLatCenter,
+ &paramLonCentreLonCenter,
+ &paramColatitudeConeAxis,
+ &paramLatitudePseudoStdParallel,
+ &paramScaleFactorPseudoStdParallel,
+ &paramFalseEasting,
+ &paramFalseNorthing,
+ nullptr};
+
+static const ParamMapping *const paramsLaea[] = {
+ &paramLatNatLatCenter, &paramLonNatLonCenter, &paramFalseEasting,
+ &paramFalseNorthing, nullptr};
+
+static const ParamMapping *const paramsMiller[] = {
+ &paramLonNatLonCenter, &paramFalseEasting, &paramFalseNorthing, nullptr};
+
+static const ParamMapping paramLatMerc1SP = {
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ nullptr, // always set to zero, not to be exported in WKT1
+ common::UnitOfMeasure::Type::ANGULAR,
+ nullptr}; // always set to zero, not to be exported in PROJ strings
+
+static const ParamMapping *const paramsMerc1SP[] = {
+ &paramLatMerc1SP, &paramLongitudeNatOrigin, &paramScaleFactorK,
+ &paramFalseEasting, &paramFalseNorthing, nullptr};
+
+static const ParamMapping *const paramsMerc2SP[] = {
+ &paramLat1stParallelLatTs, &paramLongitudeNatOrigin, &paramFalseEasting,
+ &paramFalseNorthing, nullptr};
+
+static const ParamMapping *const paramsObliqueStereo[] = {
+ &paramLatitudeNatOrigin, &paramLongitudeNatOrigin, &paramScaleFactorK,
+ &paramFalseEasting, &paramFalseNorthing, nullptr};
+
+static const ParamMapping paramLatStdParallel = {
+ EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL,
+ EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, WKT1_LATITUDE_OF_ORIGIN,
+ common::UnitOfMeasure::Type::ANGULAR, lat_ts};
+
+static const ParamMapping paramsLonOrigin = {
+ EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, WKT1_CENTRAL_MERIDIAN,
+ common::UnitOfMeasure::Type::ANGULAR, lon_0};
+
+static const ParamMapping *const paramsPolarStereo[] = {
+ &paramLatStdParallel, &paramsLonOrigin, &paramFalseEasting,
+ &paramFalseNorthing, nullptr};
+
+static const ParamMapping *const paramsLonNatOriginLongitudeCentre[] = {
+ &paramLonNatLonCenter, &paramFalseEasting, &paramFalseNorthing, nullptr};
+
+static const ParamMapping paramLatTrueScaleWag3 = {
+ "Latitude of true scale", 0, WKT1_LATITUDE_OF_ORIGIN,
+ common::UnitOfMeasure::Type::ANGULAR, lat_ts};
+
+static const ParamMapping *const paramsWag3[] = {
+ &paramLatTrueScaleWag3, &paramLongitudeNatOrigin, &paramFalseEasting,
+ &paramFalseNorthing, nullptr};
+
+static const ParamMapping paramPegLat = {
+ "Peg point latitude", 0, "peg_point_latitude",
+ common::UnitOfMeasure::Type::ANGULAR, "plat_0"};
+
+static const ParamMapping paramPegLon = {
+ "Peg point longitude", 0, "peg_point_longitude",
+ common::UnitOfMeasure::Type::ANGULAR, "plon_0"};
+
+static const ParamMapping paramPegHeading = {
+ "Peg point heading", 0, "peg_point_heading",
+ common::UnitOfMeasure::Type::ANGULAR, "phdg_0"};
+
+static const ParamMapping paramPegHeight = {
+ "Peg point height", 0, "peg_point_height",
+ common::UnitOfMeasure::Type::LINEAR, "h_0"};
+
+static const ParamMapping *const paramsSch[] = {
+ &paramPegLat, &paramPegLon, &paramPegHeading, &paramPegHeight, nullptr};
+
+static const ParamMapping *const paramsWink1[] = {
+ &paramLongitudeNatOrigin, &paramLat1stParallelLatTs, &paramFalseEasting,
+ &paramFalseNorthing, nullptr};
+
+static const ParamMapping *const paramsWink2[] = {
+ &paramLongitudeNatOrigin, &paramLatitude1stStdParallel, &paramFalseEasting,
+ &paramFalseNorthing, nullptr};
+
+static const ParamMapping paramLatLoxim = {
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_ORIGIN,
+ common::UnitOfMeasure::Type::ANGULAR, lat_1};
+
+static const ParamMapping *const paramsLoxim[] = {
+ &paramLatLoxim, &paramLongitudeNatOrigin, &paramFalseEasting,
+ &paramFalseNorthing, nullptr};
+
+static const ParamMapping paramLonCentre = {
+ EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, WKT1_LONGITUDE_OF_CENTER,
+ common::UnitOfMeasure::Type::ANGULAR, lon_0};
+
+static const ParamMapping paramLabordeObliqueMercatorAzimuth = {
+ EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE,
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE, WKT1_AZIMUTH,
+ common::UnitOfMeasure::Type::ANGULAR, "azi"};
+
+static const ParamMapping *const paramsLabordeObliqueMercator[] = {
+ &paramLatCentreLatCenter,
+ &paramLonCentre,
+ &paramLabordeObliqueMercatorAzimuth,
+ &paramScaleFactorInitialLine,
+ &paramFalseEasting,
+ &paramFalseNorthing,
+ nullptr};
+
+static const ParamMapping paramLatTopoOrigin = {
+ EPSG_NAME_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, nullptr,
+ common::UnitOfMeasure::Type::ANGULAR, lat_0};
+
+static const ParamMapping paramLonTopoOrigin = {
+ EPSG_NAME_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN,
+ EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, nullptr,
+ common::UnitOfMeasure::Type::ANGULAR, lon_0};
+
+static const ParamMapping paramHeightTopoOrigin = {
+ EPSG_NAME_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN,
+ EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, nullptr,
+ common::UnitOfMeasure::Type::LINEAR,
+ nullptr}; // unsupported by PROJ right now
+
+static const ParamMapping paramViewpointHeight = {
+ EPSG_NAME_PARAMETER_VIEWPOINT_HEIGHT, EPSG_CODE_PARAMETER_VIEWPOINT_HEIGHT,
+ nullptr, common::UnitOfMeasure::Type::LINEAR, "h"};
+
+static const ParamMapping *const paramsVerticalPerspective[] = {
+ &paramLatTopoOrigin,
+ &paramLonTopoOrigin,
+ &paramHeightTopoOrigin, // unsupported by PROJ right now
+ &paramViewpointHeight,
+ &paramFalseEasting, // PROJ addition
+ &paramFalseNorthing, // PROJ addition
+ nullptr};
+
+static const ParamMapping paramProjectionPlaneOriginHeight = {
+ EPSG_NAME_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT,
+ EPSG_CODE_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, "h_0"};
+
+static const ParamMapping *const paramsColombiaUrban[] = {
+ &paramLatitudeNatOrigin,
+ &paramLongitudeNatOrigin,
+ &paramFalseEasting,
+ &paramFalseNorthing,
+ &paramProjectionPlaneOriginHeight,
+ nullptr};
+
+static const ParamMapping paramGeocentricXTopocentricOrigin = {
+ EPSG_NAME_PARAMETER_GEOCENTRIC_X_TOPOCENTRIC_ORIGIN,
+ EPSG_CODE_PARAMETER_GEOCENTRIC_X_TOPOCENTRIC_ORIGIN, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, "X_0"};
+
+static const ParamMapping paramGeocentricYTopocentricOrigin = {
+ EPSG_NAME_PARAMETER_GEOCENTRIC_Y_TOPOCENTRIC_ORIGIN,
+ EPSG_CODE_PARAMETER_GEOCENTRIC_Y_TOPOCENTRIC_ORIGIN, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, "Y_0"};
+
+static const ParamMapping paramGeocentricZTopocentricOrigin = {
+ EPSG_NAME_PARAMETER_GEOCENTRIC_Z_TOPOCENTRIC_ORIGIN,
+ EPSG_CODE_PARAMETER_GEOCENTRIC_Z_TOPOCENTRIC_ORIGIN, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, "Z_0"};
+
+static const ParamMapping *const paramsGeocentricTopocentric[] = {
+ &paramGeocentricXTopocentricOrigin, &paramGeocentricYTopocentricOrigin,
+ &paramGeocentricZTopocentricOrigin, nullptr};
+
+static const ParamMapping paramHeightTopoOriginWithH0 = {
+ EPSG_NAME_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN,
+ EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, "h_0"};
+
+static const ParamMapping *const paramsGeographicTopocentric[] = {
+ &paramLatTopoOrigin, &paramLonTopoOrigin, &paramHeightTopoOriginWithH0,
+ nullptr};
+
+static const MethodMapping projectionMethodMappings[] = {
+ {EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR,
+ "Transverse_Mercator", "tmerc", nullptr, paramsNatOriginScaleK},
+
+ {EPSG_NAME_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED,
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED,
+ "Transverse_Mercator_South_Orientated", "tmerc", "axis=wsu",
+ paramsNatOriginScaleK},
+
+ {PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, 0, "Two_Point_Equidistant",
+ "tpeqd", nullptr, paramsTPEQD},
+
+ {EPSG_NAME_METHOD_TUNISIA_MAPPING_GRID,
+ EPSG_CODE_METHOD_TUNISIA_MAPPING_GRID, "Tunisia_Mapping_Grid", nullptr,
+ nullptr, // no proj equivalent
+ paramsTMG},
+
+ {EPSG_NAME_METHOD_ALBERS_EQUAL_AREA, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA,
+ "Albers_Conic_Equal_Area", "aea", nullptr, paramsAEA},
+
+ {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP,
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP,
+ "Lambert_Conformal_Conic_1SP", "lcc", nullptr,
+ []() {
+ static const ParamMapping paramLatLCC1SP = {
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ WKT1_LATITUDE_OF_ORIGIN, common::UnitOfMeasure::Type::ANGULAR,
+ lat_1};
+
+ static const ParamMapping *const x[] = {
+ &paramLatLCC1SP, &paramLongitudeNatOrigin, &paramScaleFactor,
+ &paramFalseEasting, &paramFalseNorthing, nullptr,
+ };
+ return x;
+ }()},
+
+ {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP,
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP,
+ "Lambert_Conformal_Conic_2SP", "lcc", nullptr, paramsLCC2SP},
+
+ // Oracle WKT
+ {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP,
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, "Lambert Conformal Conic",
+ "lcc", nullptr, paramsLCC2SP},
+
+ {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN,
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN,
+ nullptr, // no mapping to WKT1_GDAL
+ "lcc", nullptr, paramsLCC2SPMichigan},
+
+ {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM,
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM,
+ "Lambert_Conformal_Conic_2SP_Belgium", "lcc",
+ nullptr, // FIXME: this is what is done in GDAL, but the formula of
+ // LCC 2SP
+ // Belgium in the EPSG 7.2 guidance is difference from the regular
+ // LCC 2SP
+ paramsLCC2SP},
+
+ {EPSG_NAME_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT,
+ EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, "Azimuthal_Equidistant",
+ "aeqd", nullptr, paramsAEQD},
+
+ {EPSG_NAME_METHOD_GUAM_PROJECTION, EPSG_CODE_METHOD_GUAM_PROJECTION,
+ nullptr, // no mapping to GDAL WKT1
+ "aeqd", "guam", paramsNatOrigin},
+
+ {EPSG_NAME_METHOD_BONNE, EPSG_CODE_METHOD_BONNE, "Bonne", "bonne", nullptr,
+ paramsBonne},
+
+ {PROJ_WKT2_NAME_METHOD_COMPACT_MILLER, 0, "Compact_Miller", "comill",
+ nullptr, paramsLonNatOrigin},
+
+ {EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL,
+ EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL,
+ "Cylindrical_Equal_Area", "cea", nullptr, paramsCEA},
+
+ {EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA,
+ EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, "Cylindrical_Equal_Area",
+ "cea", nullptr, paramsCEA},
+
+ {EPSG_NAME_METHOD_CASSINI_SOLDNER, EPSG_CODE_METHOD_CASSINI_SOLDNER,
+ "Cassini_Soldner", "cass", nullptr, paramsNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC, 0, "Equidistant_Conic", "eqdc",
+ nullptr, paramsEQDC},
+
+ {PROJ_WKT2_NAME_METHOD_ECKERT_I, 0, "Eckert_I", "eck1", nullptr,
+ paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_ECKERT_II, 0, "Eckert_II", "eck2", nullptr,
+ paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_ECKERT_III, 0, "Eckert_III", "eck3", nullptr,
+ paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_ECKERT_IV, 0, "Eckert_IV", "eck4", nullptr,
+ paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_ECKERT_V, 0, "Eckert_V", "eck5", nullptr,
+ paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_ECKERT_VI, 0, "Eckert_VI", "eck6", nullptr,
+ paramsLonNatOrigin},
+
+ {EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL,
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, "Equirectangular", "eqc",
+ nullptr, paramsEqc},
+
+ {EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL,
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, "Equirectangular",
+ "eqc", nullptr, paramsEqc},
+
+ {PROJ_WKT2_NAME_METHOD_FLAT_POLAR_QUARTIC, 0, "Flat_Polar_Quartic",
+ "mbtfpq", nullptr, paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC, 0, "Gall_Stereographic", "gall",
+ nullptr, paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE, 0, "Goode_Homolosine", "goode",
+ nullptr, paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, 0,
+ "Interrupted_Goode_Homolosine", "igh", nullptr, paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE_OCEAN, 0, nullptr,
+ "igh_o", nullptr, paramsLonNatOrigin},
+
+ // No proper WKT1 representation fr sweep=x
+ {PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X, 0, nullptr, "geos",
+ "sweep=x", paramsGeos},
+
+ {PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y, 0,
+ "Geostationary_Satellite", "geos", nullptr, paramsGeos},
+
+ {PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR, 0,
+ "Gauss_Schreiber_Transverse_Mercator", "gstmerc", nullptr,
+ paramsNatOriginScale},
+
+ {PROJ_WKT2_NAME_METHOD_GNOMONIC, 0, "Gnomonic", "gnom", nullptr,
+ paramsNatOrigin},
+
+ {EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A,
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A,
+ "Hotine_Oblique_Mercator", "omerc", "no_uoff", paramsHomVariantA},
+
+ {EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B,
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B,
+ "Hotine_Oblique_Mercator_Azimuth_Center", "omerc", nullptr,
+ paramsHomVariantB},
+
+ {PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0,
+ "Hotine_Oblique_Mercator_Two_Point_Natural_Origin", "omerc", nullptr,
+ paramsHomTwoPoint},
+
+ {PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC, 0,
+ "International_Map_of_the_World_Polyconic", "imw_p", nullptr, paramsIMWP},
+
+ {EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED,
+ EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED, "Krovak", "krovak", nullptr,
+ krovakParameters},
+
+ {EPSG_NAME_METHOD_KROVAK, EPSG_CODE_METHOD_KROVAK, "Krovak", "krovak",
+ "axis=swu", krovakParameters},
+
+ {EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA,
+ EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA,
+ "Lambert_Azimuthal_Equal_Area", "laea", nullptr, paramsLaea},
+
+ {EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL,
+ EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL,
+ "Lambert_Azimuthal_Equal_Area", "laea", nullptr, paramsLaea},
+
+ {PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL, 0, "Miller_Cylindrical", "mill",
+ "R_A", paramsMiller},
+
+ {EPSG_NAME_METHOD_MERCATOR_VARIANT_A, EPSG_CODE_METHOD_MERCATOR_VARIANT_A,
+ "Mercator_1SP", "merc", nullptr, paramsMerc1SP},
+
+ {EPSG_NAME_METHOD_MERCATOR_VARIANT_B, EPSG_CODE_METHOD_MERCATOR_VARIANT_B,
+ "Mercator_2SP", "merc", nullptr, paramsMerc2SP},
+
+ {EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR,
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR,
+ "Popular_Visualisation_Pseudo_Mercator", // particular case actually
+ // handled manually
+ "webmerc", nullptr, paramsNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_MOLLWEIDE, 0, "Mollweide", "moll", nullptr,
+ paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_NATURAL_EARTH, 0, "Natural_Earth", "natearth",
+ nullptr, paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_NATURAL_EARTH_II, 0, "Natural_Earth_II", "natearth2",
+ nullptr, paramsLonNatOrigin},
+
+ {EPSG_NAME_METHOD_NZMG, EPSG_CODE_METHOD_NZMG, "New_Zealand_Map_Grid",
+ "nzmg", nullptr, paramsNatOrigin},
+
+ {
+ EPSG_NAME_METHOD_OBLIQUE_STEREOGRAPHIC,
+ EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC, "Oblique_Stereographic",
+ "sterea", nullptr, paramsObliqueStereo,
+ },
+
+ {EPSG_NAME_METHOD_ORTHOGRAPHIC, EPSG_CODE_METHOD_ORTHOGRAPHIC,
+ "Orthographic", "ortho", nullptr, paramsNatOrigin},
+
+ {PROJ_WKT2_NAME_ORTHOGRAPHIC_SPHERICAL, 0, "Orthographic", "ortho", "f=0",
+ paramsNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_PATTERSON, 0, "Patterson", "patterson", nullptr,
+ paramsLonNatOrigin},
+
+ {EPSG_NAME_METHOD_AMERICAN_POLYCONIC, EPSG_CODE_METHOD_AMERICAN_POLYCONIC,
+ "Polyconic", "poly", nullptr, paramsNatOrigin},
+
+ {EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A,
+ EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, "Polar_Stereographic",
+ "stere", nullptr, paramsObliqueStereo},
+
+ {EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B,
+ EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, "Polar_Stereographic",
+ "stere", nullptr, paramsPolarStereo},
+
+ {PROJ_WKT2_NAME_METHOD_ROBINSON, 0, "Robinson", "robin", nullptr,
+ paramsLonNatOriginLongitudeCentre},
+
+ {PROJ_WKT2_NAME_METHOD_SINUSOIDAL, 0, "Sinusoidal", "sinu", nullptr,
+ paramsLonNatOriginLongitudeCentre},
+
+ {PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC, 0, "Stereographic", "stere", nullptr,
+ paramsObliqueStereo},
+
+ {PROJ_WKT2_NAME_METHOD_TIMES, 0, "Times", "times", nullptr,
+ paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN, 0, "VanDerGrinten", "vandg", "R_A",
+ paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_WAGNER_I, 0, "Wagner_I", "wag1", nullptr,
+ paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_WAGNER_II, 0, "Wagner_II", "wag2", nullptr,
+ paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_WAGNER_III, 0, "Wagner_III", "wag3", nullptr,
+ paramsWag3},
+
+ {PROJ_WKT2_NAME_METHOD_WAGNER_IV, 0, "Wagner_IV", "wag4", nullptr,
+ paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_WAGNER_V, 0, "Wagner_V", "wag5", nullptr,
+ paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_WAGNER_VI, 0, "Wagner_VI", "wag6", nullptr,
+ paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_WAGNER_VII, 0, "Wagner_VII", "wag7", nullptr,
+ paramsLonNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE, 0,
+ "Quadrilateralized_Spherical_Cube", "qsc", nullptr, paramsNatOrigin},
+
+ {PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT, 0,
+ "Spherical_Cross_Track_Height", "sch", nullptr, paramsSch},
+
+ // The following methods have just the WKT <--> PROJ string mapping, but
+ // no setter. Similarly to GDAL
+
+ {"Aitoff", 0, "Aitoff", "aitoff", nullptr, paramsLonNatOrigin},
+
+ {"Winkel I", 0, "Winkel_I", "wink1", nullptr, paramsWink1},
+
+ {"Winkel II", 0, "Winkel_II", "wink2", nullptr, paramsWink2},
+
+ {"Winkel Tripel", 0, "Winkel_Tripel", "wintri", nullptr, paramsWink2},
+
+ {"Craster Parabolic", 0, "Craster_Parabolic", "crast", nullptr,
+ paramsLonNatOrigin},
+
+ {"Loximuthal", 0, "Loximuthal", "loxim", nullptr, paramsLoxim},
+
+ {"Quartic Authalic", 0, "Quartic_Authalic", "qua_aut", nullptr,
+ paramsLonNatOrigin},
+
+ {"Transverse Cylindrical Equal Area", 0,
+ "Transverse_Cylindrical_Equal_Area", "tcea", nullptr, paramsObliqueStereo},
+
+ {EPSG_NAME_METHOD_EQUAL_EARTH, EPSG_CODE_METHOD_EQUAL_EARTH, nullptr,
+ "eqearth", nullptr, paramsLonNatOrigin},
+
+ {EPSG_NAME_METHOD_LABORDE_OBLIQUE_MERCATOR,
+ EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR, "Laborde_Oblique_Mercator",
+ "labrd", nullptr, paramsLabordeObliqueMercator},
+
+ {EPSG_NAME_METHOD_VERTICAL_PERSPECTIVE,
+ EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE, nullptr, "nsper", nullptr,
+ paramsVerticalPerspective},
+
+ {EPSG_NAME_METHOD_COLOMBIA_URBAN, EPSG_CODE_METHOD_COLOMBIA_URBAN, nullptr,
+ "col_urban", nullptr, paramsColombiaUrban},
+
+ {EPSG_NAME_METHOD_GEOCENTRIC_TOPOCENTRIC,
+ EPSG_CODE_METHOD_GEOCENTRIC_TOPOCENTRIC, nullptr, "topocentric", nullptr,
+ paramsGeocentricTopocentric},
+
+ {EPSG_NAME_METHOD_GEOGRAPHIC_TOPOCENTRIC,
+ EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC, nullptr, nullptr, nullptr,
+ paramsGeographicTopocentric},
+};
+
+const MethodMapping *getProjectionMethodMappings(size_t &nElts) {
+ nElts =
+ sizeof(projectionMethodMappings) / sizeof(projectionMethodMappings[0]);
+ return projectionMethodMappings;
+}
+
+#define METHOD_NAME_CODE(method) \
+ { EPSG_NAME_METHOD_##method, EPSG_CODE_METHOD_##method }
+
+const struct MethodNameCode methodNameCodes[] = {
+ // Projection methods
+ METHOD_NAME_CODE(TRANSVERSE_MERCATOR),
+ METHOD_NAME_CODE(TRANSVERSE_MERCATOR_SOUTH_ORIENTATED),
+ METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_1SP), METHOD_NAME_CODE(NZMG),
+ METHOD_NAME_CODE(TUNISIA_MAPPING_GRID), METHOD_NAME_CODE(ALBERS_EQUAL_AREA),
+ METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP),
+ METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM),
+ METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN),
+ METHOD_NAME_CODE(MODIFIED_AZIMUTHAL_EQUIDISTANT),
+ METHOD_NAME_CODE(GUAM_PROJECTION), METHOD_NAME_CODE(BONNE),
+ METHOD_NAME_CODE(LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL),
+ METHOD_NAME_CODE(LAMBERT_CYLINDRICAL_EQUAL_AREA),
+ METHOD_NAME_CODE(CASSINI_SOLDNER),
+ METHOD_NAME_CODE(EQUIDISTANT_CYLINDRICAL),
+ METHOD_NAME_CODE(EQUIDISTANT_CYLINDRICAL_SPHERICAL),
+ METHOD_NAME_CODE(HOTINE_OBLIQUE_MERCATOR_VARIANT_A),
+ METHOD_NAME_CODE(HOTINE_OBLIQUE_MERCATOR_VARIANT_B),
+ METHOD_NAME_CODE(KROVAK_NORTH_ORIENTED), METHOD_NAME_CODE(KROVAK),
+ METHOD_NAME_CODE(LAMBERT_AZIMUTHAL_EQUAL_AREA),
+ METHOD_NAME_CODE(POPULAR_VISUALISATION_PSEUDO_MERCATOR),
+ METHOD_NAME_CODE(MERCATOR_VARIANT_A), METHOD_NAME_CODE(MERCATOR_VARIANT_B),
+ METHOD_NAME_CODE(OBLIQUE_STEREOGRAPHIC),
+ METHOD_NAME_CODE(AMERICAN_POLYCONIC),
+ METHOD_NAME_CODE(POLAR_STEREOGRAPHIC_VARIANT_A),
+ METHOD_NAME_CODE(POLAR_STEREOGRAPHIC_VARIANT_B),
+ METHOD_NAME_CODE(EQUAL_EARTH), METHOD_NAME_CODE(LABORDE_OBLIQUE_MERCATOR),
+ METHOD_NAME_CODE(VERTICAL_PERSPECTIVE), METHOD_NAME_CODE(COLOMBIA_URBAN),
+ // Other conversions
+ METHOD_NAME_CODE(CHANGE_VERTICAL_UNIT),
+ METHOD_NAME_CODE(HEIGHT_DEPTH_REVERSAL),
+ METHOD_NAME_CODE(AXIS_ORDER_REVERSAL_2D),
+ METHOD_NAME_CODE(AXIS_ORDER_REVERSAL_3D),
+ METHOD_NAME_CODE(GEOGRAPHIC_GEOCENTRIC),
+ METHOD_NAME_CODE(GEOCENTRIC_TOPOCENTRIC),
+ METHOD_NAME_CODE(GEOGRAPHIC_TOPOCENTRIC),
+ // Transformations
+ METHOD_NAME_CODE(LONGITUDE_ROTATION),
+ METHOD_NAME_CODE(AFFINE_PARAMETRIC_TRANSFORMATION),
+ METHOD_NAME_CODE(COORDINATE_FRAME_GEOCENTRIC),
+ METHOD_NAME_CODE(COORDINATE_FRAME_GEOGRAPHIC_2D),
+ METHOD_NAME_CODE(COORDINATE_FRAME_GEOGRAPHIC_3D),
+ METHOD_NAME_CODE(POSITION_VECTOR_GEOCENTRIC),
+ METHOD_NAME_CODE(POSITION_VECTOR_GEOGRAPHIC_2D),
+ METHOD_NAME_CODE(POSITION_VECTOR_GEOGRAPHIC_3D),
+ METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOCENTRIC),
+ METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D),
+ METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D),
+ METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC),
+ METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D),
+ METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D),
+ METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC),
+ METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D),
+ METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D),
+ METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOCENTRIC),
+ METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D),
+ METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D),
+ METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOCENTRIC),
+ METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D),
+ METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D),
+ METHOD_NAME_CODE(MOLODENSKY), METHOD_NAME_CODE(ABRIDGED_MOLODENSKY),
+ METHOD_NAME_CODE(GEOGRAPHIC2D_OFFSETS),
+ METHOD_NAME_CODE(GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS),
+ METHOD_NAME_CODE(GEOGRAPHIC3D_OFFSETS), METHOD_NAME_CODE(VERTICAL_OFFSET),
+ METHOD_NAME_CODE(NTV2), METHOD_NAME_CODE(NTV1), METHOD_NAME_CODE(NADCON),
+ METHOD_NAME_CODE(VERTCON),
+ METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN),
+};
+
+const MethodNameCode *getMethodNameCodes(size_t &nElts) {
+ nElts = sizeof(methodNameCodes) / sizeof(methodNameCodes[0]);
+ return methodNameCodes;
+}
+
+#define PARAM_NAME_CODE(method) \
+ { EPSG_NAME_PARAMETER_##method, EPSG_CODE_PARAMETER_##method }
+
+const struct ParamNameCode paramNameCodes[] = {
+ // Parameters of projection methods
+ PARAM_NAME_CODE(COLATITUDE_CONE_AXIS),
+ PARAM_NAME_CODE(LATITUDE_OF_NATURAL_ORIGIN),
+ PARAM_NAME_CODE(LONGITUDE_OF_NATURAL_ORIGIN),
+ PARAM_NAME_CODE(SCALE_FACTOR_AT_NATURAL_ORIGIN),
+ PARAM_NAME_CODE(FALSE_EASTING), PARAM_NAME_CODE(FALSE_NORTHING),
+ PARAM_NAME_CODE(LATITUDE_PROJECTION_CENTRE),
+ PARAM_NAME_CODE(LONGITUDE_PROJECTION_CENTRE),
+ PARAM_NAME_CODE(AZIMUTH_INITIAL_LINE),
+ PARAM_NAME_CODE(ANGLE_RECTIFIED_TO_SKEW_GRID),
+ PARAM_NAME_CODE(SCALE_FACTOR_INITIAL_LINE),
+ PARAM_NAME_CODE(EASTING_PROJECTION_CENTRE),
+ PARAM_NAME_CODE(NORTHING_PROJECTION_CENTRE),
+ PARAM_NAME_CODE(LATITUDE_PSEUDO_STANDARD_PARALLEL),
+ PARAM_NAME_CODE(SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL),
+ PARAM_NAME_CODE(LATITUDE_FALSE_ORIGIN),
+ PARAM_NAME_CODE(LONGITUDE_FALSE_ORIGIN),
+ PARAM_NAME_CODE(LATITUDE_1ST_STD_PARALLEL),
+ PARAM_NAME_CODE(LATITUDE_2ND_STD_PARALLEL),
+ PARAM_NAME_CODE(EASTING_FALSE_ORIGIN),
+ PARAM_NAME_CODE(NORTHING_FALSE_ORIGIN),
+ PARAM_NAME_CODE(LATITUDE_STD_PARALLEL),
+ PARAM_NAME_CODE(LONGITUDE_OF_ORIGIN),
+ PARAM_NAME_CODE(ELLIPSOID_SCALE_FACTOR),
+ PARAM_NAME_CODE(PROJECTION_PLANE_ORIGIN_HEIGHT),
+ PARAM_NAME_CODE(GEOCENTRIC_X_TOPOCENTRIC_ORIGIN),
+ PARAM_NAME_CODE(GEOCENTRIC_Y_TOPOCENTRIC_ORIGIN),
+ PARAM_NAME_CODE(GEOCENTRIC_Z_TOPOCENTRIC_ORIGIN),
+ // Parameters of transformations
+ PARAM_NAME_CODE(SEMI_MAJOR_AXIS_DIFFERENCE),
+ PARAM_NAME_CODE(FLATTENING_DIFFERENCE),
+ PARAM_NAME_CODE(LATITUDE_LONGITUDE_DIFFERENCE_FILE),
+ PARAM_NAME_CODE(GEOID_CORRECTION_FILENAME),
+ PARAM_NAME_CODE(VERTICAL_OFFSET_FILE),
+ PARAM_NAME_CODE(LATITUDE_DIFFERENCE_FILE),
+ PARAM_NAME_CODE(LONGITUDE_DIFFERENCE_FILE),
+ PARAM_NAME_CODE(UNIT_CONVERSION_SCALAR), PARAM_NAME_CODE(LATITUDE_OFFSET),
+ PARAM_NAME_CODE(LONGITUDE_OFFSET), PARAM_NAME_CODE(VERTICAL_OFFSET),
+ PARAM_NAME_CODE(GEOID_UNDULATION), PARAM_NAME_CODE(A0), PARAM_NAME_CODE(A1),
+ PARAM_NAME_CODE(A2), PARAM_NAME_CODE(B0), PARAM_NAME_CODE(B1),
+ PARAM_NAME_CODE(B2), PARAM_NAME_CODE(X_AXIS_TRANSLATION),
+ PARAM_NAME_CODE(Y_AXIS_TRANSLATION), PARAM_NAME_CODE(Z_AXIS_TRANSLATION),
+ PARAM_NAME_CODE(X_AXIS_ROTATION), PARAM_NAME_CODE(Y_AXIS_ROTATION),
+ PARAM_NAME_CODE(Z_AXIS_ROTATION), PARAM_NAME_CODE(SCALE_DIFFERENCE),
+ PARAM_NAME_CODE(RATE_X_AXIS_TRANSLATION),
+ PARAM_NAME_CODE(RATE_Y_AXIS_TRANSLATION),
+ PARAM_NAME_CODE(RATE_Z_AXIS_TRANSLATION),
+ PARAM_NAME_CODE(RATE_X_AXIS_ROTATION),
+ PARAM_NAME_CODE(RATE_Y_AXIS_ROTATION),
+ PARAM_NAME_CODE(RATE_Z_AXIS_ROTATION),
+ PARAM_NAME_CODE(RATE_SCALE_DIFFERENCE), PARAM_NAME_CODE(REFERENCE_EPOCH),
+ PARAM_NAME_CODE(TRANSFORMATION_REFERENCE_EPOCH),
+ PARAM_NAME_CODE(ORDINATE_1_EVAL_POINT),
+ PARAM_NAME_CODE(ORDINATE_2_EVAL_POINT),
+ PARAM_NAME_CODE(ORDINATE_3_EVAL_POINT),
+ PARAM_NAME_CODE(GEOCENTRIC_TRANSLATION_FILE),
+};
+
+const ParamNameCode *getParamNameCodes(size_t &nElts) {
+ nElts = sizeof(paramNameCodes) / sizeof(paramNameCodes[0]);
+ return paramNameCodes;
+}
+
+static const ParamMapping paramUnitConversionScalar = {
+ EPSG_NAME_PARAMETER_UNIT_CONVERSION_SCALAR,
+ EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR, nullptr,
+ common::UnitOfMeasure::Type::SCALE, nullptr};
+
+static const ParamMapping *const paramsChangeVerticalUnit[] = {
+ &paramUnitConversionScalar, nullptr};
+
+static const ParamMapping paramLongitudeOffset = {
+ EPSG_NAME_PARAMETER_LONGITUDE_OFFSET, EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
+ nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr};
+
+static const ParamMapping *const paramsLongitudeRotation[] = {
+ &paramLongitudeOffset, nullptr};
+
+static const ParamMapping paramA0 = {
+ EPSG_NAME_PARAMETER_A0, EPSG_CODE_PARAMETER_A0, nullptr,
+ common::UnitOfMeasure::Type::UNKNOWN, nullptr};
+
+static const ParamMapping paramA1 = {
+ EPSG_NAME_PARAMETER_A1, EPSG_CODE_PARAMETER_A1, nullptr,
+ common::UnitOfMeasure::Type::UNKNOWN, nullptr};
+
+static const ParamMapping paramA2 = {
+ EPSG_NAME_PARAMETER_A2, EPSG_CODE_PARAMETER_A2, nullptr,
+ common::UnitOfMeasure::Type::UNKNOWN, nullptr};
+
+static const ParamMapping paramB0 = {
+ EPSG_NAME_PARAMETER_B0, EPSG_CODE_PARAMETER_B0, nullptr,
+ common::UnitOfMeasure::Type::UNKNOWN, nullptr};
+
+static const ParamMapping paramB1 = {
+ EPSG_NAME_PARAMETER_B1, EPSG_CODE_PARAMETER_B1, nullptr,
+ common::UnitOfMeasure::Type::UNKNOWN, nullptr};
+
+static const ParamMapping paramB2 = {
+ EPSG_NAME_PARAMETER_B2, EPSG_CODE_PARAMETER_B2, nullptr,
+ common::UnitOfMeasure::Type::UNKNOWN, nullptr};
+
+static const ParamMapping *const paramsAffineParametricTransformation[] = {
+ &paramA0, &paramA1, &paramA2, &paramB0, &paramB1, &paramB2, nullptr};
+
+static const ParamMapping paramXTranslation = {
+ EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION,
+ EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramYTranslation = {
+ EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION,
+ EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramZTranslation = {
+ EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION,
+ EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramXRotation = {
+ EPSG_NAME_PARAMETER_X_AXIS_ROTATION, EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
+ nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramYRotation = {
+ EPSG_NAME_PARAMETER_Y_AXIS_ROTATION, EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
+ nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramZRotation = {
+ EPSG_NAME_PARAMETER_Z_AXIS_ROTATION, EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
+ nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramScaleDifference = {
+ EPSG_NAME_PARAMETER_SCALE_DIFFERENCE, EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
+ nullptr, common::UnitOfMeasure::Type::SCALE, nullptr};
+
+static const ParamMapping *const paramsHelmert3[] = {
+ &paramXTranslation, &paramYTranslation, &paramZTranslation, nullptr};
+
+static const ParamMapping *const paramsHelmert7[] = {
+ &paramXTranslation, &paramYTranslation,
+ &paramZTranslation, &paramXRotation,
+ &paramYRotation, &paramZRotation,
+ &paramScaleDifference, nullptr};
+
+static const ParamMapping paramRateXTranslation = {
+ EPSG_NAME_PARAMETER_RATE_X_AXIS_TRANSLATION,
+ EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramRateYTranslation = {
+ EPSG_NAME_PARAMETER_RATE_Y_AXIS_TRANSLATION,
+ EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramRateZTranslation = {
+ EPSG_NAME_PARAMETER_RATE_Z_AXIS_TRANSLATION,
+ EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramRateXRotation = {
+ EPSG_NAME_PARAMETER_RATE_X_AXIS_ROTATION,
+ EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramRateYRotation = {
+ EPSG_NAME_PARAMETER_RATE_Y_AXIS_ROTATION,
+ EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramRateZRotation = {
+ EPSG_NAME_PARAMETER_RATE_Z_AXIS_ROTATION,
+ EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramRateScaleDifference = {
+ EPSG_NAME_PARAMETER_RATE_SCALE_DIFFERENCE,
+ EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE, nullptr,
+ common::UnitOfMeasure::Type::SCALE, nullptr};
+
+static const ParamMapping paramReferenceEpoch = {
+ EPSG_NAME_PARAMETER_REFERENCE_EPOCH, EPSG_CODE_PARAMETER_REFERENCE_EPOCH,
+ nullptr, common::UnitOfMeasure::Type::TIME, nullptr};
+
+static const ParamMapping *const paramsHelmert15[] = {
+ &paramXTranslation, &paramYTranslation,
+ &paramZTranslation, &paramXRotation,
+ &paramYRotation, &paramZRotation,
+ &paramScaleDifference, &paramRateXTranslation,
+ &paramRateYTranslation, &paramRateZTranslation,
+ &paramRateXRotation, &paramRateYRotation,
+ &paramRateZRotation, &paramRateScaleDifference,
+ &paramReferenceEpoch, nullptr};
+
+static const ParamMapping paramOrdinate1EvalPoint = {
+ EPSG_NAME_PARAMETER_ORDINATE_1_EVAL_POINT,
+ EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramOrdinate2EvalPoint = {
+ EPSG_NAME_PARAMETER_ORDINATE_2_EVAL_POINT,
+ EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramOrdinate3EvalPoint = {
+ EPSG_NAME_PARAMETER_ORDINATE_3_EVAL_POINT,
+ EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping *const paramsMolodenskyBadekas[] = {
+ &paramXTranslation,
+ &paramYTranslation,
+ &paramZTranslation,
+ &paramXRotation,
+ &paramYRotation,
+ &paramZRotation,
+ &paramScaleDifference,
+ &paramOrdinate1EvalPoint,
+ &paramOrdinate2EvalPoint,
+ &paramOrdinate3EvalPoint,
+ nullptr};
+
+static const ParamMapping paramSemiMajorAxisDifference = {
+ EPSG_NAME_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE,
+ EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE, nullptr,
+ common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping paramFlatteningDifference = {
+ EPSG_NAME_PARAMETER_FLATTENING_DIFFERENCE,
+ EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE, nullptr,
+ common::UnitOfMeasure::Type::NONE, nullptr};
+
+static const ParamMapping *const paramsMolodensky[] = {
+ &paramXTranslation, &paramYTranslation,
+ &paramZTranslation, &paramSemiMajorAxisDifference,
+ &paramFlatteningDifference, nullptr};
+
+static const ParamMapping paramLatitudeOffset = {
+ EPSG_NAME_PARAMETER_LATITUDE_OFFSET, EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
+ nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr};
+
+static const ParamMapping *const paramsGeographic2DOffsets[] = {
+ &paramLatitudeOffset, &paramLongitudeOffset, nullptr};
+
+static const ParamMapping paramGeoidUndulation = {
+ EPSG_NAME_PARAMETER_GEOID_UNDULATION, EPSG_CODE_PARAMETER_GEOID_UNDULATION,
+ nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping *const paramsGeographic2DWithHeightOffsets[] = {
+ &paramLatitudeOffset, &paramLongitudeOffset, &paramGeoidUndulation,
+ nullptr};
+
+static const ParamMapping paramVerticalOffset = {
+ EPSG_NAME_PARAMETER_VERTICAL_OFFSET, EPSG_CODE_PARAMETER_VERTICAL_OFFSET,
+ nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr};
+
+static const ParamMapping *const paramsGeographic3DOffsets[] = {
+ &paramLatitudeOffset, &paramLongitudeOffset, &paramVerticalOffset, nullptr};
+
+static const ParamMapping *const paramsVerticalOffsets[] = {
+ &paramVerticalOffset, nullptr};
+
+static const ParamMapping paramLatitudeLongitudeDifferenceFile = {
+ EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, nullptr,
+ common::UnitOfMeasure::Type::NONE, nullptr};
+
+static const ParamMapping *const paramsNTV2[] = {
+ &paramLatitudeLongitudeDifferenceFile, nullptr};
+
+static const ParamMapping paramGeocentricTranslationFile = {
+ EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE,
+ EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, nullptr,
+ common::UnitOfMeasure::Type::NONE, nullptr};
+
+static const ParamMapping
+ *const paramsGeocentricTranslationGridInterpolationIGN[] = {
+ &paramGeocentricTranslationFile, nullptr};
+
+static const ParamMapping paramLatitudeDifferenceFile = {
+ EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE, nullptr,
+ common::UnitOfMeasure::Type::NONE, nullptr};
+
+static const ParamMapping paramLongitudeDifferenceFile = {
+ EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE, nullptr,
+ common::UnitOfMeasure::Type::NONE, nullptr};
+
+static const ParamMapping *const paramsNADCON[] = {
+ &paramLatitudeDifferenceFile, &paramLongitudeDifferenceFile, nullptr};
+
+static const ParamMapping paramVerticalOffsetFile = {
+ EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE,
+ EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE, nullptr,
+ common::UnitOfMeasure::Type::NONE, nullptr};
+
+static const ParamMapping *const paramsVERTCON[] = {&paramVerticalOffsetFile,
+ nullptr};
+
+static const ParamMapping paramSouthPoleLatGRIB = {
+ PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION, 0, nullptr,
+ common::UnitOfMeasure::Type::ANGULAR, nullptr};
+
+static const ParamMapping paramSouthPoleLonGRIB = {
+ PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION, 0, nullptr,
+ common::UnitOfMeasure::Type::ANGULAR, nullptr};
+
+static const ParamMapping paramAxisRotationGRIB = {
+ PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION, 0, nullptr,
+ common::UnitOfMeasure::Type::ANGULAR, nullptr};
+
+static const ParamMapping *const paramsPoleRotationGRIBConvention[] = {
+ &paramSouthPoleLatGRIB, &paramSouthPoleLonGRIB, &paramAxisRotationGRIB,
+ nullptr};
+
+static const MethodMapping otherMethodMappings[] = {
+ {EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT,
+ EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT, nullptr, nullptr, nullptr,
+ paramsChangeVerticalUnit},
+ {EPSG_NAME_METHOD_HEIGHT_DEPTH_REVERSAL,
+ EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL, nullptr, nullptr, nullptr,
+ paramsChangeVerticalUnit},
+ {EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_2D,
+ EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D, nullptr, nullptr, nullptr,
+ nullptr},
+ {EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_3D,
+ EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D, nullptr, nullptr, nullptr,
+ nullptr},
+ {EPSG_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC,
+ EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC, nullptr, nullptr, nullptr,
+ nullptr},
+ {EPSG_NAME_METHOD_LONGITUDE_ROTATION, EPSG_CODE_METHOD_LONGITUDE_ROTATION,
+ nullptr, nullptr, nullptr, paramsLongitudeRotation},
+ {EPSG_NAME_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION,
+ EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION, nullptr, nullptr,
+ nullptr, paramsAffineParametricTransformation},
+
+ {PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION, 0, nullptr, nullptr,
+ nullptr, paramsPoleRotationGRIBConvention},
+
+ {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC,
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC, nullptr, nullptr,
+ nullptr, paramsHelmert3},
+ {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D,
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D, nullptr, nullptr,
+ nullptr, paramsHelmert3},
+ {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D,
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D, nullptr, nullptr,
+ nullptr, paramsHelmert3},
+
+ {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOCENTRIC,
+ EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC, nullptr, nullptr, nullptr,
+ paramsHelmert7},
+ {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D,
+ EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D, nullptr, nullptr, nullptr,
+ paramsHelmert7},
+ {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D,
+ EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D, nullptr, nullptr, nullptr,
+ paramsHelmert7},
+
+ {EPSG_NAME_METHOD_POSITION_VECTOR_GEOCENTRIC,
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC, nullptr, nullptr, nullptr,
+ paramsHelmert7},
+ {EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D,
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D, nullptr, nullptr, nullptr,
+ paramsHelmert7},
+ {EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D,
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D, nullptr, nullptr, nullptr,
+ paramsHelmert7},
+
+ {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC,
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC, nullptr,
+ nullptr, nullptr, paramsHelmert15},
+ {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D,
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D, nullptr,
+ nullptr, nullptr, paramsHelmert15},
+ {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D,
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D, nullptr,
+ nullptr, nullptr, paramsHelmert15},
+
+ {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC,
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC, nullptr,
+ nullptr, nullptr, paramsHelmert15},
+ {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D,
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D, nullptr,
+ nullptr, nullptr, paramsHelmert15},
+ {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D,
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D, nullptr,
+ nullptr, nullptr, paramsHelmert15},
+
+ {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC,
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC, nullptr, nullptr,
+ nullptr, paramsMolodenskyBadekas},
+ {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D,
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D, nullptr, nullptr,
+ nullptr, paramsMolodenskyBadekas},
+ {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D,
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D, nullptr, nullptr,
+ nullptr, paramsMolodenskyBadekas},
+
+ {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC,
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC, nullptr, nullptr,
+ nullptr, paramsMolodenskyBadekas},
+ {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D,
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D, nullptr, nullptr,
+ nullptr, paramsMolodenskyBadekas},
+ {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D,
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D, nullptr, nullptr,
+ nullptr, paramsMolodenskyBadekas},
+
+ {EPSG_NAME_METHOD_MOLODENSKY, EPSG_CODE_METHOD_MOLODENSKY, nullptr, nullptr,
+ nullptr, paramsMolodensky},
+
+ {EPSG_NAME_METHOD_ABRIDGED_MOLODENSKY, EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY,
+ nullptr, nullptr, nullptr, paramsMolodensky},
+
+ {EPSG_NAME_METHOD_GEOGRAPHIC2D_OFFSETS,
+ EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS, nullptr, nullptr, nullptr,
+ paramsGeographic2DOffsets},
+
+ {EPSG_NAME_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS,
+ EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS, nullptr, nullptr,
+ nullptr, paramsGeographic2DWithHeightOffsets},
+
+ {EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSETS,
+ EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS, nullptr, nullptr, nullptr,
+ paramsGeographic3DOffsets},
+
+ {EPSG_NAME_METHOD_VERTICAL_OFFSET, EPSG_CODE_METHOD_VERTICAL_OFFSET,
+ nullptr, nullptr, nullptr, paramsVerticalOffsets},
+
+ {EPSG_NAME_METHOD_NTV2, EPSG_CODE_METHOD_NTV2, nullptr, nullptr, nullptr,
+ paramsNTV2},
+
+ {EPSG_NAME_METHOD_NTV1, EPSG_CODE_METHOD_NTV1, nullptr, nullptr, nullptr,
+ paramsNTV2},
+
+ {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN,
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN, nullptr,
+ nullptr, nullptr, paramsGeocentricTranslationGridInterpolationIGN},
+
+ {EPSG_NAME_METHOD_NADCON, EPSG_CODE_METHOD_NADCON, nullptr, nullptr,
+ nullptr, paramsNADCON},
+
+ {EPSG_NAME_METHOD_VERTCON, EPSG_CODE_METHOD_VERTCON, nullptr, nullptr,
+ nullptr, paramsVERTCON},
+ {EPSG_NAME_METHOD_VERTCON_OLDNAME, EPSG_CODE_METHOD_VERTCON, nullptr,
+ nullptr, nullptr, paramsVERTCON},
+};
+
+const MethodMapping *getOtherMethodMappings(size_t &nElts) {
+ nElts = sizeof(otherMethodMappings) / sizeof(otherMethodMappings[0]);
+ return otherMethodMappings;
+}
+
+// ---------------------------------------------------------------------------
+
+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 mentioned 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;
+ }
+ }
+ for (const auto &mapping : otherMethodMappings) {
+ 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;
+}
+
+// ---------------------------------------------------------------------------
+
+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;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
diff --git a/src/iso19111/operation/parammappings.hpp b/src/iso19111/operation/parammappings.hpp
new file mode 100644
index 00000000..05fe8a2d
--- /dev/null
+++ b/src/iso19111/operation/parammappings.hpp
@@ -0,0 +1,114 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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 PARAMMAPPINGS_HPP
+#define PARAMMAPPINGS_HPP
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/util.hpp"
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+extern const char *WKT1_LATITUDE_OF_ORIGIN;
+extern const char *WKT1_CENTRAL_MERIDIAN;
+extern const char *WKT1_SCALE_FACTOR;
+extern const char *WKT1_FALSE_EASTING;
+extern const char *WKT1_FALSE_NORTHING;
+extern const char *WKT1_STANDARD_PARALLEL_1;
+extern const char *WKT1_STANDARD_PARALLEL_2;
+extern const char *WKT1_LATITUDE_OF_CENTER;
+extern const char *WKT1_LONGITUDE_OF_CENTER;
+extern const char *WKT1_AZIMUTH;
+extern const char *WKT1_RECTIFIED_GRID_ANGLE;
+
+struct ParamMapping {
+ const char *wkt2_name;
+ const int epsg_code;
+ const char *wkt1_name;
+ const common::UnitOfMeasure::Type unit_type;
+ const char *proj_name;
+};
+
+struct MethodMapping {
+ const char *wkt2_name;
+ const int epsg_code;
+ const char *wkt1_name;
+ const char *proj_name_main;
+ const char *proj_name_aux;
+ const ParamMapping *const *params;
+};
+
+extern const ParamMapping paramLatitudeNatOrigin;
+
+const MethodMapping *getProjectionMethodMappings(size_t &nElts);
+const MethodMapping *getOtherMethodMappings(size_t &nElts);
+
+struct MethodNameCode {
+ const char *name;
+ int epsg_code;
+};
+
+const MethodNameCode *getMethodNameCodes(size_t &nElts);
+
+struct ParamNameCode {
+ const char *name;
+ int epsg_code;
+};
+
+const ParamNameCode *getParamNameCodes(size_t &nElts);
+
+const MethodMapping *getMapping(int epsg_code) noexcept;
+const MethodMapping *getMappingFromWKT1(const std::string &wkt1_name) noexcept;
+const MethodMapping *getMapping(const char *wkt2_name) noexcept;
+const MethodMapping *getMapping(const OperationMethod *method) noexcept;
+std::vector<const MethodMapping *>
+getMappingsFromPROJName(const std::string &projName);
+const ParamMapping *getMapping(const MethodMapping *mapping,
+ const OperationParameterNNPtr &param);
+const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping,
+ const std::string &wkt1_name);
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
+
+#endif // PARAMMAPPINGS_HPP
diff --git a/src/iso19111/operation/projbasedoperation.cpp b/src/iso19111/operation/projbasedoperation.cpp
new file mode 100644
index 00000000..6e0fd109
--- /dev/null
+++ b/src/iso19111/operation/projbasedoperation.cpp
@@ -0,0 +1,316 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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/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 "coordinateoperation_internal.hpp"
+#include "oputils.hpp"
+
+// PROJ include order is sensitive
+// clang-format off
+#include "proj.h"
+#include "proj_internal.h" // M_PI
+// clang-format on
+#include "proj_constants.h"
+#include "proj_json_streaming_writer.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+using namespace NS_PROJ::internal;
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+PROJBasedOperation::~PROJBasedOperation() = default;
+
+// ---------------------------------------------------------------------------
+
+PROJBasedOperation::PROJBasedOperation(const OperationMethodNNPtr &methodIn)
+ : SingleOperation(methodIn) {}
+
+// ---------------------------------------------------------------------------
+
+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 method = OperationMethod::create(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ "PROJ-based operation method: " + PROJString),
+ std::vector<GeneralOperationParameterNNPtr>{});
+ auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(method);
+ op->assignSelf(op);
+ op->projString_ = PROJString;
+ 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;
+}
+
+// ---------------------------------------------------------------------------
+
+PROJBasedOperationNNPtr PROJBasedOperation::create(
+ const util::PropertyMap &properties,
+ const io::IPROJStringExportableNNPtr &projExportable, bool inverse,
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::CRSPtr &interpolationCRS,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies,
+ bool hasBallparkTransformation) {
+
+ auto formatter = io::PROJStringFormatter::create();
+ if (inverse) {
+ formatter->startInversion();
+ }
+ projExportable->_exportToPROJString(formatter.get());
+ if (inverse) {
+ formatter->stopInversion();
+ }
+ auto projString = formatter->toString();
+
+ auto method = OperationMethod::create(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ "PROJ-based operation method (approximate): " +
+ projString),
+ std::vector<GeneralOperationParameterNNPtr>{});
+ auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(method);
+ op->assignSelf(op);
+ op->projString_ = projString;
+ op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
+ op->setProperties(
+ addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation"));
+ op->setAccuracies(accuracies);
+ op->projStringExportable_ = projExportable.as_nullable();
+ op->inverse_ = inverse;
+ op->setHasBallparkTransformation(hasBallparkTransformation);
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr PROJBasedOperation::inverse() const {
+
+ if (projStringExportable_ && sourceCRS() && targetCRS()) {
+ 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()),
+ interpolationCRS(), coordinateOperationAccuracies(),
+ hasBallparkTransformation()));
+ }
+
+ auto formatter = io::PROJStringFormatter::create();
+ formatter->startInversion();
+ try {
+ formatter->ingestPROJString(projString_);
+ } catch (const io::ParsingException &e) {
+ throw util::UnsupportedOperationException(
+ std::string("PROJBasedOperation::inverse() failed: ") + e.what());
+ }
+ formatter->stopInversion();
+
+ auto op = PROJBasedOperation::create(
+ createPropertiesForInverse(this, false, false), formatter->toString(),
+ targetCRS(), sourceCRS(), coordinateOperationAccuracies());
+ if (sourceCRS() && targetCRS()) {
+ op->setCRSs(NN_NO_CHECK(targetCRS()), NN_NO_CHECK(sourceCRS()),
+ interpolationCRS());
+ }
+ op->setHasBallparkTransformation(hasBallparkTransformation());
+ return util::nn_static_pointer_cast<CoordinateOperation>(op);
+}
+
+// ---------------------------------------------------------------------------
+
+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::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext(
+ (sourceCRS() && targetCRS()) ? "Transformation" : "Conversion",
+ !identifiers().empty()));
+
+ writer->AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer->Add("unnamed");
+ } else {
+ writer->Add(l_name);
+ }
+
+ if (sourceCRS() && targetCRS()) {
+ writer->AddObjKey("source_crs");
+ formatter->setAllowIDInImmediateChild();
+ sourceCRS()->_exportToJSON(formatter);
+
+ writer->AddObjKey("target_crs");
+ formatter->setAllowIDInImmediateChild();
+ targetCRS()->_exportToJSON(formatter);
+ }
+
+ writer->AddObjKey("method");
+ formatter->setOmitTypeInImmediateChild();
+ formatter->setAllowIDInImmediateChild();
+ method()->_exportToJSON(formatter);
+
+ const auto &l_parameterValues = parameterValues();
+ if (!l_parameterValues.empty()) {
+ writer->AddObjKey("parameters");
+ {
+ auto parametersContext(writer->MakeArrayContext(false));
+ for (const auto &genOpParamvalue : l_parameterValues) {
+ formatter->setAllowIDInImmediateChild();
+ formatter->setOmitTypeInImmediateChild();
+ genOpParamvalue->_exportToJSON(formatter);
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJBasedOperation::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const {
+ if (projStringExportable_) {
+ if (inverse_) {
+ formatter->startInversion();
+ }
+ projStringExportable_->_exportToPROJString(formatter);
+ if (inverse_) {
+ formatter->stopInversion();
+ }
+ return;
+ }
+
+ try {
+ formatter->ingestPROJString(projString_);
+ } catch (const io::ParsingException &e) {
+ throw io::FormattingException(
+ std::string("PROJBasedOperation::exportToPROJString() failed: ") +
+ e.what());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr PROJBasedOperation::_shallowClone() const {
+ auto op = PROJBasedOperation::nn_make_shared<PROJBasedOperation>(*this);
+ op->assignSelf(op);
+ op->setCRSs(this, false);
+ return util::nn_static_pointer_cast<CoordinateOperation>(op);
+}
+
+// ---------------------------------------------------------------------------
+
+std::set<GridDescription>
+PROJBasedOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) 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, considerKnownGridsAsAvailable,
+ 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/operation/singleoperation.cpp b/src/iso19111/operation/singleoperation.cpp
new file mode 100644
index 00000000..0cd7b57a
--- /dev/null
+++ b/src/iso19111/operation/singleoperation.cpp
@@ -0,0 +1,2218 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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/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 "coordinateoperation_internal.hpp"
+#include "coordinateoperation_private.hpp"
+#include "operationmethod_private.hpp"
+#include "oputils.hpp"
+#include "parammappings.hpp"
+
+// PROJ include order is sensitive
+// clang-format off
+#include "proj.h"
+#include "proj_internal.h" // M_PI
+// clang-format on
+#include "proj_constants.h"
+#include "proj_json_streaming_writer.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+using namespace NS_PROJ::internal;
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection(
+ const std::string &message)
+ : InvalidOperation(message) {}
+
+InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection(
+ const InvalidOperationEmptyIntersection &) = default;
+
+InvalidOperationEmptyIntersection::~InvalidOperationEmptyIntersection() =
+ default;
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+GridDescription::GridDescription()
+ : shortName{}, fullName{}, packageName{}, url{}, directDownload(false),
+ openLicense(false), available(false) {}
+
+GridDescription::~GridDescription() = default;
+
+GridDescription::GridDescription(const GridDescription &) = default;
+
+GridDescription::GridDescription(GridDescription &&other) noexcept
+ : shortName(std::move(other.shortName)),
+ fullName(std::move(other.fullName)),
+ packageName(std::move(other.packageName)),
+ url(std::move(other.url)),
+ directDownload(other.directDownload),
+ openLicense(other.openLicense),
+ available(other.available) {}
+
+//! @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 instantiated as
+ * a PROJ pipeline, checking in particular that referenced grids are
+ * available.
+ */
+bool CoordinateOperation::isPROJInstantiable(
+ const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const {
+ try {
+ exportToPROJString(io::PROJStringFormatter::create().get());
+ } catch (const std::exception &) {
+ return false;
+ }
+ for (const auto &gridDesc :
+ gridsNeeded(databaseContext, considerKnownGridsAsAvailable)) {
+ if (!gridDesc.available) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether a coordinate operation has a "ballpark"
+ * transformation,
+ * that is a very approximate one, due to lack of more accurate transformations.
+ *
+ * Typically a null geographic offset between two horizontal datum, or a
+ * null vertical offset (or limited to unit changes) between two vertical
+ * datum. Errors of several tens to one hundred meters might be expected,
+ * compared to more accurate transformations.
+ */
+bool CoordinateOperation::hasBallparkTransformation() const {
+ return d->hasBallparkTransformation_;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperation::setHasBallparkTransformation(bool b) {
+ d->hasBallparkTransformation_ = b;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperation::setProperties(
+ const util::PropertyMap &properties) // throw(InvalidValueTypeException)
+{
+ ObjectUsage::setProperties(properties);
+ properties.getStringValue(OPERATION_VERSION_KEY, d->operationVersion_);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a variation of the current coordinate operation whose axis
+ * order is the one expected for visualization purposes.
+ */
+CoordinateOperationNNPtr
+CoordinateOperation::normalizeForVisualization() const {
+ auto l_sourceCRS = sourceCRS();
+ auto l_targetCRS = targetCRS();
+ if (!l_sourceCRS || !l_targetCRS) {
+ throw util::UnsupportedOperationException(
+ "Cannot retrieve source or target CRS");
+ }
+ const bool swapSource =
+ l_sourceCRS->mustAxisOrderBeSwitchedForVisualization();
+ const bool swapTarget =
+ l_targetCRS->mustAxisOrderBeSwitchedForVisualization();
+ auto l_this = NN_NO_CHECK(std::dynamic_pointer_cast<CoordinateOperation>(
+ shared_from_this().as_nullable()));
+ if (!swapSource && !swapTarget) {
+ return l_this;
+ }
+ std::vector<CoordinateOperationNNPtr> subOps;
+ if (swapSource) {
+ auto op = Conversion::createAxisOrderReversal(false);
+ op->setCRSs(l_sourceCRS->normalizeForVisualization(),
+ NN_NO_CHECK(l_sourceCRS), nullptr);
+ subOps.emplace_back(op);
+ }
+ subOps.emplace_back(l_this);
+ if (swapTarget) {
+ auto op = Conversion::createAxisOrderReversal(false);
+ op->setCRSs(NN_NO_CHECK(l_targetCRS),
+ l_targetCRS->normalizeForVisualization(), nullptr);
+ subOps.emplace_back(op);
+ }
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ ConcatenatedOperation::createComputeMetadata(subOps, true));
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+CoordinateOperationNNPtr CoordinateOperation::shallowClone() const {
+ return _shallowClone();
+}
+//! @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_PURE_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_PURE_DEFN {
+ return d->formulaCitation_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameters of this operation method.
+ *
+ * @return the parameters.
+ */
+const std::vector<GeneralOperationParameterNNPtr> &
+OperationMethod::parameters() PROJ_PURE_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_PURE_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)"));
+ }
+ size_t nMethodNameCodes = 0;
+ const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes);
+ for (size_t i = 0; i < nMethodNameCodes; ++i) {
+ const auto &tuple = methodNameCodes[i];
+ 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
+void OperationMethod::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext("OperationMethod",
+ !identifiers().empty()));
+
+ writer->AddObjKey("name");
+ writer->Add(nameStr());
+
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool OperationMethod::_isEquivalentTo(
+ const util::IComparable *other, util::IComparable::Criterion criterion,
+ const io::DatabaseContextPtr &dbContext) const {
+ auto otherOM = dynamic_cast<const OperationMethod *>(other);
+ if (otherOM == nullptr ||
+ !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) {
+ 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,
+ dbContext)) {
+ 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,
+ dbContext)) {
+ 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_PURE_DEFN {
+ return d->parameter;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameter value.
+ *
+ * @return the parameter value.
+ */
+const ParameterValueNNPtr &
+OperationParameterValue::parameterValue() PROJ_PURE_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
+void OperationParameterValue::_exportToJSON(
+ io::JSONFormatter *formatter) const {
+ auto writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext(
+ "ParameterValue", !parameter()->identifiers().empty()));
+
+ writer->AddObjKey("name");
+ writer->Add(parameter()->nameStr());
+
+ const auto &l_value(parameterValue());
+ if (l_value->type() == ParameterValue::Type::MEASURE) {
+ writer->AddObjKey("value");
+ writer->Add(l_value->value().value(), 15);
+ writer->AddObjKey("unit");
+ const auto &l_unit(l_value->value().unit());
+ if (l_unit == common::UnitOfMeasure::METRE ||
+ l_unit == common::UnitOfMeasure::DEGREE ||
+ l_unit == common::UnitOfMeasure::SCALE_UNITY) {
+ writer->Add(l_unit.name());
+ } else {
+ l_unit._exportToJSON(formatter);
+ }
+ } else if (l_value->type() == ParameterValue::Type::FILENAME) {
+ writer->AddObjKey("value");
+ writer->Add(l_value->valueFile());
+ }
+
+ if (formatter->outputId()) {
+ parameter()->formatID(formatter);
+ }
+}
+//! @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 io::DatabaseContextPtr &dbContext) const {
+ auto otherOPV = dynamic_cast<const OperationParameterValue *>(other);
+ if (otherOPV == nullptr) {
+ return false;
+ }
+ if (!d->parameter->_isEquivalentTo(otherOPV->d->parameter.get(), criterion,
+ dbContext)) {
+ 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, dbContext)) {
+ 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 io::DatabaseContextPtr &dbContext) const {
+ auto otherOP = dynamic_cast<const OperationParameter *>(other);
+ if (otherOP == nullptr) {
+ return false;
+ }
+ if (criterion == util::IComparable::Criterion::STRICT) {
+ return IdentifiedObject::_isEquivalentTo(other, criterion, dbContext);
+ }
+ if (IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) {
+ 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 {
+ size_t nParamNameCodes = 0;
+ const auto paramNameCodes = getParamNameCodes(nParamNameCodes);
+ for (size_t i = 0; i < nParamNameCodes; ++i) {
+ const auto &tuple = paramNameCodes[i];
+ 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_PURE_DEFN {
+ int epsg_code = IdentifiedObject::getEPSGCode();
+ if (epsg_code == 0) {
+ const auto &l_name = nameStr();
+ size_t nParamNameCodes = 0;
+ const auto paramNameCodes = getParamNameCodes(nParamNameCodes);
+ for (size_t i = 0; i < nParamNameCodes; ++i) {
+ const auto &tuple = paramNameCodes[i];
+ 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_PURE_DEFN {
+ return d->parameterValues_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the operation method associated to the operation.
+ *
+ * @return the operation method.
+ */
+const OperationMethodNNPtr &SingleOperation::method() PROJ_PURE_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;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \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;
+}
+
+double SingleOperation::parameterValueNumeric(
+ const char *param_name, const common::UnitOfMeasure &targetUnit) const
+ noexcept {
+ const auto &val = parameterValue(param_name, 0);
+ if (val && val->type() == ParameterValue::Type::MEASURE) {
+ return val->value().convertToUnit(targetUnit);
+ }
+ return 0.0;
+}
+
+//! @endcond
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate 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
+bool SingleOperation::_isEquivalentTo(
+ const util::IComparable *other, util::IComparable::Criterion criterion,
+ const io::DatabaseContextPtr &dbContext) const {
+ return _isEquivalentTo(other, criterion, dbContext, false);
+}
+
+bool SingleOperation::_isEquivalentTo(const util::IComparable *other,
+ util::IComparable::Criterion criterion,
+ const io::DatabaseContextPtr &dbContext,
+ bool inOtherDirection) const {
+
+ auto otherSO = dynamic_cast<const SingleOperation *>(other);
+ if (otherSO == nullptr ||
+ (criterion == util::IComparable::Criterion::STRICT &&
+ !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) {
+ 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,
+ dbContext);
+ 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_LAMBERT_CYLINDRICAL_EQUAL_AREA &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) ||
+ (otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA &&
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_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, dbContext);
+ } 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,
+ dbContext);
+ }
+ }
+ }
+ }
+
+ 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,
+ dbContext)) {
+ 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,
+ dbContext)) {
+ 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,
+ dbContext);
+ 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, dbContext) &&
+ value_2nd->_isEquivalentTo(
+ otherSO
+ ->parameterValue(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL)
+ .get(),
+ criterion, dbContext);
+ }
+ }
+ }
+
+ 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, dbContext, 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, dbContext);
+ }
+ }
+ }
+ return equivalent;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+std::set<GridDescription>
+SingleOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) 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) {
+ const auto gridNames = split(value->valueFile(), ",");
+ for (const auto &gridName : gridNames) {
+ GridDescription desc;
+ desc.shortName = gridName;
+ if (databaseContext) {
+ databaseContext->lookForGridInfo(
+ desc.shortName, considerKnownGridsAsAvailable,
+ desc.fullName, desc.packageName, desc.url,
+ desc.directDownload, desc.openLicense,
+ desc.available);
+ }
+ res.insert(desc);
+ }
+ }
+ }
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Validate the parameters used by a coordinate 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();
+ size_t nProjectionMethodMappings = 0;
+ const auto projectionMethodMappings =
+ getProjectionMethodMappings(nProjectionMethodMappings);
+ for (size_t i = 0; i < nProjectionMethodMappings; ++i) {
+ const auto &mapping = projectionMethodMappings[i];
+ if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
+ methodName.c_str()) ||
+ (methodEPSGCode != 0 && methodEPSGCode == mapping.epsg_code)) {
+ methodMapping = &mapping;
+ }
+ }
+ if (methodMapping == nullptr) {
+ size_t nOtherMethodMappings = 0;
+ const auto otherMethodMappings =
+ getOtherMethodMappings(nOtherMethodMappings);
+ for (size_t i = 0; i < nOtherMethodMappings; ++i) {
+ const auto &mapping = otherMethodMappings[i];
+ 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) {
+ if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) &&
+ paramMapping == &paramLatitudeNatOrigin) {
+ // extension of EPSG used by GDAL/PROJ, so we should not
+ // warn on its absence.
+ continue;
+ }
+ 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("Parameter 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 Instantiate 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 Instantiate 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 Instantiate 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 Instantiate 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 Instantiate a ParameterValue from a integer value.
+ *
+ * @return a new ParameterValue.
+ */
+ParameterValueNNPtr ParameterValue::create(int integerValueIn) {
+ return ParameterValue::nn_make_shared<ParameterValue>(integerValueIn);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate 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_PURE_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_PURE_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_PURE_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_PURE_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_PURE_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_PURE_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();
+ if (l_type == Type::MEASURE) {
+ const auto &l_value = value();
+ if (formatter->abridgedTransformation()) {
+ 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 {
+ 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) {
+ const auto &targetUnit = *(formatter->axisLinearUnit());
+ if (targetUnit.conversionToSI() == 0.0) {
+ throw io::FormattingException(
+ "cannot convert value to target linear unit");
+ }
+ formatter->add(l_value.convertToUnit(targetUnit));
+ } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) {
+ const auto &targetUnit = *(formatter->axisAngularUnit());
+ if (targetUnit.conversionToSI() == 0.0) {
+ throw io::FormattingException(
+ "cannot convert value to target angular unit");
+ }
+ formatter->add(l_value.convertToUnit(targetUnit));
+ } 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 io::DatabaseContextPtr &) 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, 2e-10);
+ }
+
+ 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
+// ---------------------------------------------------------------------------
+
+InvalidOperation::InvalidOperation(const char *message) : Exception(message) {}
+
+// ---------------------------------------------------------------------------
+
+InvalidOperation::InvalidOperation(const std::string &message)
+ : Exception(message) {}
+
+// ---------------------------------------------------------------------------
+
+InvalidOperation::InvalidOperation(const InvalidOperation &) = default;
+
+// ---------------------------------------------------------------------------
+
+InvalidOperation::~InvalidOperation() = default;
+//! @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");
+ }
+
+ if (formatter->abridgedTransformation()) {
+ formatter->startNode(io::WKTConstants::ABRIDGEDTRANSFORMATION,
+ !identifiers().empty());
+ } else {
+ formatter->startNode(io::WKTConstants::COORDINATEOPERATION,
+ !identifiers().empty());
+ }
+
+ formatter->addQuotedString(nameStr());
+
+ if (formatter->use2019Keywords()) {
+ const auto &version = operationVersion();
+ if (version.has_value()) {
+ formatter->startNode(io::WKTConstants::VERSION, false);
+ formatter->addQuotedString(*version);
+ formatter->endNode();
+ }
+ }
+
+ if (!formatter->abridgedTransformation()) {
+ exportSourceCRSAndTargetCRSToWKT(this, formatter);
+ }
+
+ method()->_exportToWKT(formatter);
+
+ for (const auto &paramValue : parameterValues()) {
+ paramValue->_exportToWKT(formatter, nullptr);
+ }
+
+ 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();
+}
+
+// ---------------------------------------------------------------------------
+
+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");
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
+ 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 == "m") {
+ // do nothing
+ } else 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);
+ }
+ return true;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
+ formatter->addStep("axisswap");
+ formatter->addParam("order", "1,2,-3");
+ return true;
+ }
+
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+InverseCoordinateOperation::~InverseCoordinateOperation() = default;
+
+// ---------------------------------------------------------------------------
+
+InverseCoordinateOperation::InverseCoordinateOperation(
+ const CoordinateOperationNNPtr &forwardOperationIn,
+ bool wktSupportsInversion)
+ : forwardOperation_(forwardOperationIn),
+ wktSupportsInversion_(wktSupportsInversion) {}
+
+// ---------------------------------------------------------------------------
+
+void InverseCoordinateOperation::setPropertiesFromForward() {
+ setProperties(
+ createPropertiesForInverse(forwardOperation_.get(), false, false));
+ setAccuracies(forwardOperation_->coordinateOperationAccuracies());
+ if (forwardOperation_->sourceCRS() && forwardOperation_->targetCRS()) {
+ setCRSs(forwardOperation_.get(), true);
+ }
+ setHasBallparkTransformation(
+ forwardOperation_->hasBallparkTransformation());
+}
+
+// ---------------------------------------------------------------------------
+
+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 io::DatabaseContextPtr &dbContext) const {
+ auto otherICO = dynamic_cast<const InverseCoordinateOperation *>(other);
+ if (otherICO == nullptr ||
+ !ObjectUsage::_isEquivalentTo(other, criterion, dbContext)) {
+ return false;
+ }
+ return inverse()->_isEquivalentTo(otherICO->inverse().get(), criterion,
+ dbContext);
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+PointMotionOperation::~PointMotionOperation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+
+NS_PROJ_END
diff --git a/src/iso19111/operation/transformation.cpp b/src/iso19111/operation/transformation.cpp
new file mode 100644
index 00000000..fec821a1
--- /dev/null
+++ b/src/iso19111/operation/transformation.cpp
@@ -0,0 +1,3274 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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/crs.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+
+#include "coordinateoperation_internal.hpp"
+#include "coordinateoperation_private.hpp"
+#include "esriparammappings.hpp"
+#include "operationmethod_private.hpp"
+#include "oputils.hpp"
+#include "parammappings.hpp"
+#include "vectorofvaluesparams.hpp"
+
+// PROJ include order is sensitive
+// clang-format off
+#include "proj.h"
+#include "proj_internal.h" // M_PI
+// clang-format on
+#include "proj_constants.h"
+
+#include "proj_json_streaming_writer.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+using namespace NS_PROJ::internal;
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct Transformation::Private {
+
+ TransformationPtr forwardOperation_{};
+
+ static TransformationNNPtr registerInv(const Transformation *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_PURE_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_PURE_DEFN {
+ return CoordinateOperation::getPrivate()->strongRef_->targetCRS_;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+TransformationNNPtr Transformation::shallowClone() const {
+ auto transf = Transformation::nn_make_shared<Transformation>(*this);
+ transf->assignSelf(transf);
+ transf->setCRSs(this, false);
+ if (transf->d->forwardOperation_) {
+ transf->d->forwardOperation_ =
+ transf->d->forwardOperation_->shallowClone().as_nullable();
+ }
+ return transf;
+}
+
+CoordinateOperationNNPtr Transformation::_shallowClone() const {
+ return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone());
+}
+
+// ---------------------------------------------------------------------------
+
+TransformationNNPtr
+Transformation::promoteTo3D(const std::string &,
+ const io::DatabaseContextPtr &dbContext) const {
+ auto transf = shallowClone();
+ transf->setCRSs(sourceCRS()->promoteTo3D(std::string(), dbContext),
+ targetCRS()->promoteTo3D(std::string(), dbContext),
+ interpolationCRS());
+ return transf;
+}
+
+// ---------------------------------------------------------------------------
+
+TransformationNNPtr
+Transformation::demoteTo2D(const std::string &,
+ const io::DatabaseContextPtr &dbContext) const {
+ auto transf = shallowClone();
+ transf->setCRSs(sourceCRS()->demoteTo2D(std::string(), dbContext),
+ targetCRS()->demoteTo2D(std::string(), dbContext),
+ interpolationCRS());
+ return transf;
+}
+
+//! @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 Instantiate 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 transf = Transformation::nn_make_shared<Transformation>(
+ sourceCRSIn, targetCRSIn, interpolationCRSIn, methodIn, values,
+ accuracies);
+ transf->assignSelf(transf);
+ transf->setProperties(properties);
+ std::string name;
+ if (properties.getStringValue(common::IdentifiedObject::NAME_KEY, name) &&
+ ci_find(name, "ballpark") != std::string::npos) {
+ transf->setHasBallparkTransformation(true);
+ }
+ return transf;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate 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;
+}
+
+// ---------------------------------------------------------------------------
+
+static int
+useOperationMethodEPSGCodeIfPresent(const util::PropertyMap &properties,
+ int nDefaultOperationMethodEPSGCode) {
+ const auto *operationMethodEPSGCode =
+ properties.get("OPERATION_METHOD_EPSG_CODE");
+ if (operationMethodEPSGCode) {
+ const auto boxedValue = dynamic_cast<const util::BoxedValue *>(
+ (*operationMethodEPSGCode).get());
+ if (boxedValue &&
+ boxedValue->type() == util::BoxedValue::Type::INTEGER) {
+ return boxedValue->integerValue();
+ }
+ }
+ return nDefaultOperationMethodEPSGCode;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate 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(useOperationMethodEPSGCodeIfPresent(
+ properties,
+ 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 Instantiate 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(useOperationMethodEPSGCodeIfPresent(
+ properties,
+ 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 Instantiate 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(useOperationMethodEPSGCodeIfPresent(
+ properties,
+ 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 Instantiate 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(useOperationMethodEPSGCodeIfPresent(
+ properties,
+ 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 Instantiate 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(useOperationMethodEPSGCodeIfPresent(
+ properties,
+ 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 Instantiate 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 Instantiate 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 Instantiate 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 Instantiate 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 crs::CRSPtr &interpolationCRSIn, const std::string &filename,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+
+ return Transformation::create(
+ properties, sourceCRSIn, targetCRSIn, interpolationCRSIn,
+ 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 Instantiate 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 interpolationCRSIn Interpolation CRS. (might be null)
+ * @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 crs::CRSPtr &interpolationCRSIn,
+ const std::string &filename,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+
+ return _createGravityRelatedHeightToGeographic3D(
+ properties, false, sourceCRSIn, targetCRSIn, interpolationCRSIn,
+ filename, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate 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 Instantiate 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 Instantiate 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 Instantiate 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 Instantiate 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 Instantiate 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);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a transformation 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 sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param factor Conversion factor
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return a new Transformation.
+ */
+TransformationNNPtr Transformation::createChangeVerticalUnit(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const common::Scale &factor,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR),
+ },
+ VectorOfValues{
+ factor,
+ },
+ accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+// 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(const Transformation *thisIn,
+ TransformationNNPtr invTransform) {
+ invTransform->d->forwardOperation_ = thisIn->shallowClone().as_nullable();
+ invTransform->setHasBallparkTransformation(
+ thisIn->hasBallparkTransformation());
+ 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);
+ auto properties = createPropertiesForInverse(this, false, false);
+ return Private::registerInv(
+ this, create(properties, l_targetCRS, l_sourceCRS, nullptr,
+ createMethodMapNameEPSGCode(
+ useOperationMethodEPSGCodeIfPresent(
+ properties, methodEPSGCode)),
+ 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(negate(x)),
+ common::Length(negate(y)),
+ common::Length(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 Private::registerInv(
+ this,
+ createAbridgedMolodensky(
+ createPropertiesForInverse(this, false, false), l_targetCRS,
+ l_sourceCRS, negate(x), negate(y), negate(z), negate(da),
+ negate(df), coordinateOperationAccuracies()));
+ } else {
+ return Private::registerInv(
+ 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 Private::registerInv(
+ 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 Private::registerInv(
+ 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 Private::registerInv(
+ 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 Private::registerInv(
+ 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 Private::registerInv(
+ this,
+ createVerticalOffset(createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, newOffsetHeight,
+ coordinateOperationAccuracies()));
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
+ const double convFactor = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
+ return Private::registerInv(
+ this, createChangeVerticalUnit(
+ createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, common::Scale(1.0 / convFactor),
+ coordinateOperationAccuracies()));
+ }
+
+#ifdef notdef
+ // We don't need that currently, but we might...
+ if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
+ return Private::registerInv(
+ this,
+ createHeightDepthReversal(
+ createPropertiesForInverse(this, false, false), l_targetCRS,
+ l_sourceCRS, coordinateOperationAccuracies()));
+ }
+#endif
+
+ 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;
+}
+
+// ---------------------------------------------------------------------------
+
+TransformationNNPtr InverseTransformation::inverseAsTransformation() const {
+ return NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_));
+}
+
+// ---------------------------------------------------------------------------
+
+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);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr InverseTransformation::_shallowClone() const {
+ auto op = InverseTransformation::nn_make_shared<InverseTransformation>(
+ inverseAsTransformation()->shallowClone());
+ op->assignSelf(op);
+ op->setCRSs(this, false);
+ return util::nn_static_pointer_cast<CoordinateOperation>(op);
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void Transformation::_exportToWKT(io::WKTFormatter *formatter) const {
+ exportTransformationToWKT(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void Transformation::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext(
+ formatter->abridgedTransformation() ? "AbridgedTransformation"
+ : "Transformation",
+ !identifiers().empty()));
+
+ writer->AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer->Add("unnamed");
+ } else {
+ writer->Add(l_name);
+ }
+
+ if (!formatter->abridgedTransformation()) {
+ writer->AddObjKey("source_crs");
+ formatter->setAllowIDInImmediateChild();
+ sourceCRS()->_exportToJSON(formatter);
+
+ writer->AddObjKey("target_crs");
+ formatter->setAllowIDInImmediateChild();
+ targetCRS()->_exportToJSON(formatter);
+
+ const auto &l_interpolationCRS = interpolationCRS();
+ if (l_interpolationCRS) {
+ writer->AddObjKey("interpolation_crs");
+ formatter->setAllowIDInImmediateChild();
+ l_interpolationCRS->_exportToJSON(formatter);
+ }
+ }
+
+ writer->AddObjKey("method");
+ formatter->setOmitTypeInImmediateChild();
+ formatter->setAllowIDInImmediateChild();
+ method()->_exportToJSON(formatter);
+
+ writer->AddObjKey("parameters");
+ {
+ auto parametersContext(writer->MakeArrayContext(false));
+ for (const auto &genOpParamvalue : parameterValues()) {
+ formatter->setAllowIDInImmediateChild();
+ formatter->setOmitTypeInImmediateChild();
+ genOpParamvalue->_exportToJSON(formatter);
+ }
+ }
+
+ if (!formatter->abridgedTransformation()) {
+ if (!coordinateOperationAccuracies().empty()) {
+ writer->AddObjKey("accuracy");
+ writer->Add(coordinateOperationAccuracies()[0]->value());
+ }
+ }
+
+ if (formatter->abridgedTransformation()) {
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+ } else {
+ ObjectUsage::baseExportToJSON(formatter);
+ }
+}
+
+//! @endcond
+
+//! @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 &
+_getHorizontalShiftGTIFFFilename(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_HORIZONTAL_SHIFT_GTIFF) ||
+ (allowInverse &&
+ ci_equal(methodName,
+ INVERSE_OF + PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF))) {
+ 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 &
+_getGeocentricTranslationFilename(const Transformation *op, bool allowInverse) {
+
+ const auto &l_method = op->method();
+ const auto &methodName = l_method->nameStr();
+ if (l_method->getEPSGCode() ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN ||
+ (allowInverse &&
+ ci_equal(
+ methodName,
+ INVERSE_OF +
+ EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN))) {
+ const auto &fileParameter =
+ op->parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE,
+ EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_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
+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)
+ "1081", // Geographic3D to GravityRelatedHeight (BEV AT)
+ "1083", // Geog3D to Geog2D+Vertical (AUSGeoid v2)
+ "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)
+ "9635", // Geog3D to Geog2D+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
+const std::string &Transformation::getHeightToGeographic3DFilename() const {
+
+ const std::string &ret = _getHeightToGeographic3DFilename(this, false);
+ if (!ret.empty())
+ return ret;
+ 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) {
+ return fileParameter->valueFile();
+ }
+ }
+ return nullString;
+}
+//! @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());
+
+ const std::string &forwardName = obj->nameStr();
+ if (!forwardName.empty()) {
+ map.set(common::IdentifiedObject::NAME_KEY, forwardName);
+ }
+
+ const std::string &remarks = obj->remarks();
+ if (!remarks.empty()) {
+ map.set(common::IdentifiedObject::REMARKS_KEY, remarks);
+ }
+
+ 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) {
+ if (inverseDirection) {
+ throw util::UnsupportedOperationException(
+ "Inverse direction for " + projFilename + " not supported");
+ }
+ return self;
+ }
+
+ const auto &l_sourceCRS = sourceCRS();
+ const auto &l_targetCRS = targetCRS();
+ const auto &l_accuracies = coordinateOperationAccuracies();
+ if (projGridFormat == "GTiff") {
+ 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_HORIZONTAL_SHIFT_GTIFF);
+ 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);
+ }
+ } else 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);
+ }
+ }
+ }
+
+ 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 (inverseDirection) {
+ throw util::UnsupportedOperationException(
+ "Inverse direction for "
+ "Geographic3DToGravityRelatedHeight not supported");
+ }
+
+ if (filename == projFilename) {
+ return self;
+ }
+
+ auto parameters = std::vector<OperationParameterNNPtr>{
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)};
+#ifdef disabled_for_now
+ if (inverseDirection) {
+ return create(createPropertiesForInverse(
+ self.as_nullable().get(), true, false),
+ targetCRS(), sourceCRS(), nullptr,
+ createSimilarPropertiesMethod(method()),
+ parameters, {ParameterValue::createFilename(
+ projFilename)},
+ coordinateOperationAccuracies())
+ ->inverseAsTransformation();
+ } else
+#endif
+ {
+ return create(
+ createSimilarPropertiesTransformation(self),
+ sourceCRS(), targetCRS(), nullptr,
+ createSimilarPropertiesMethod(method()), parameters,
+ {ParameterValue::createFilename(projFilename)},
+ coordinateOperationAccuracies());
+ }
+ }
+ }
+ }
+
+ const auto &geocentricTranslationFilename =
+ _getGeocentricTranslationFilename(this, false);
+ if (!geocentricTranslationFilename.empty()) {
+ if (databaseContext->lookForGridAlternative(
+ geocentricTranslationFilename, projFilename, projGridFormat,
+ inverseDirection)) {
+
+ if (inverseDirection) {
+ throw util::UnsupportedOperationException(
+ "Inverse direction for "
+ "GeocentricTranslation not supported");
+ }
+
+ if (geocentricTranslationFilename == projFilename) {
+ return self;
+ }
+
+ auto parameters =
+ std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE)};
+ return create(createSimilarPropertiesTransformation(self),
+ sourceCRS(), targetCRS(), interpolationCRS(),
+ createSimilarPropertiesMethod(method()), parameters,
+ {ParameterValue::createFilename(projFilename)},
+ coordinateOperationAccuracies());
+ }
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON ||
+ methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD ||
+ methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT ||
+ methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX) {
+ auto fileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE,
+ EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+
+ auto filename = fileParameter->valueFile();
+ if (databaseContext->lookForGridAlternative(
+ filename, projFilename, projGridFormat, inverseDirection)) {
+
+ if (filename == projFilename) {
+ if (inverseDirection) {
+ throw util::UnsupportedOperationException(
+ "Inverse direction for " + projFilename +
+ " not supported");
+ }
+ return self;
+ }
+
+ auto parameters = std::vector<OperationParameterNNPtr>{
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)};
+ 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 ThrowExceptionNotGeodeticGeographic(const char *trfrm_name) {
+ throw io::FormattingException(concat("Can apply ", std::string(trfrm_name),
+ " only to GeodeticCRS / "
+ "GeographicCRS"));
+}
+
+// ---------------------------------------------------------------------------
+
+// If crs is a geographic CRS, or a compound CRS of a geographic CRS,
+// or a compoundCRS of a bound CRS of a geographic CRS, return that
+// geographic CRS
+static crs::GeographicCRSPtr
+extractGeographicCRSIfGeographicCRSOrEquivalent(const crs::CRSNNPtr &crs) {
+ auto geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(crs);
+ if (!geogCRS) {
+ auto compoundCRS = util::nn_dynamic_pointer_cast<crs::CompoundCRS>(crs);
+ if (compoundCRS) {
+ const auto &components = compoundCRS->componentReferenceSystems();
+ if (!components.empty()) {
+ geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ components[0]);
+ if (!geogCRS) {
+ auto boundCRS =
+ util::nn_dynamic_pointer_cast<crs::BoundCRS>(
+ components[0]);
+ if (boundCRS) {
+ geogCRS =
+ util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ boundCRS->baseCRS());
+ }
+ }
+ }
+ } else {
+ auto boundCRS = util::nn_dynamic_pointer_cast<crs::BoundCRS>(crs);
+ if (boundCRS) {
+ geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ boundCRS->baseCRS());
+ }
+ }
+ }
+ return geogCRS;
+}
+
+// ---------------------------------------------------------------------------
+
+static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter,
+ const crs::CRSNNPtr &crs, bool addPushV3,
+ const char *trfrm_name) {
+ auto sourceCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs);
+ if (sourceCRSGeog) {
+ formatter->startInversion();
+ sourceCRSGeog->_exportToPROJString(formatter);
+ formatter->stopInversion();
+ if (util::isOfExactType<crs::DerivedGeographicCRS>(
+ *(sourceCRSGeog.get()))) {
+ // The export of a DerivedGeographicCRS in non-CRS mode adds
+ // unit conversion and axis swapping. We must compensate for that
+ formatter->startInversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+ }
+
+ if (addPushV3) {
+ formatter->addStep("push");
+ formatter->addParam("v_3");
+ }
+
+ formatter->addStep("cart");
+ sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+ } else {
+ auto sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get());
+ if (!sourceCRSGeod) {
+ ThrowExceptionNotGeodeticGeographic(trfrm_name);
+ }
+ formatter->startInversion();
+ sourceCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter);
+ formatter->stopInversion();
+ }
+}
+// ---------------------------------------------------------------------------
+
+static void setupPROJGeodeticTargetCRS(io::PROJStringFormatter *formatter,
+ const crs::CRSNNPtr &crs, bool addPopV3,
+ const char *trfrm_name) {
+ auto targetCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs);
+ if (targetCRSGeog) {
+ formatter->addStep("cart");
+ formatter->setCurrentStepInverted(true);
+ targetCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+
+ if (addPopV3) {
+ formatter->addStep("pop");
+ formatter->addParam("v_3");
+ }
+ if (util::isOfExactType<crs::DerivedGeographicCRS>(
+ *(targetCRSGeog.get()))) {
+ // The export of a DerivedGeographicCRS in non-CRS mode adds
+ // unit conversion and axis swapping. We must compensate for that
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ }
+ targetCRSGeog->_exportToPROJString(formatter);
+ } else {
+ auto targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get());
+ if (!targetCRSGeod) {
+ ThrowExceptionNotGeodeticGeographic(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);
+
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ const bool addPushPopV3 =
+ !CoordinateOperation::getPrivate()->use3DHelmert_ &&
+ ((sourceCRSGeog &&
+ sourceCRSGeog->coordinateSystem()->axisList().size() == 2) ||
+ (targetCRSGeog &&
+ targetCRSGeog->coordinateSystem()->axisList().size() == 2));
+
+ setupPROJGeodeticSourceCRS(formatter, sourceCRS(), addPushPopV3,
+ "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(), addPushPopV3,
+ "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);
+
+ bool addPushPopV3 =
+ (methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D);
+
+ setupPROJGeodeticSourceCRS(formatter, sourceCRS(), addPushPopV3,
+ "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(), addPushPopV3,
+ "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 &HorizontalShiftGTIFFFilename =
+ _getHorizontalShiftGTIFFFilename(this, true);
+ const auto &hGridShiftFilename =
+ !HorizontalShiftGTIFFFilename.empty()
+ ? HorizontalShiftGTIFFFilename
+ : !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty()
+ ? NTv2Filename
+ : CTABLE2Filename;
+ if (!hGridShiftFilename.empty()) {
+ auto sourceCRSGeog =
+ extractGeographicCRSIfGeographicCRSOrEquivalent(sourceCRS());
+ if (!sourceCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName, " only to GeographicCRS"));
+ }
+
+ auto targetCRSGeog =
+ extractGeographicCRSIfGeographicCRSOrEquivalent(targetCRS());
+ 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 &geocentricTranslationFilename =
+ _getGeocentricTranslationFilename(this, true);
+ if (!geocentricTranslationFilename.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"));
+ }
+
+ const auto &interpCRS = interpolationCRS();
+ if (!interpCRS) {
+ throw io::FormattingException(
+ "InterpolationCRS required "
+ "for"
+ " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN);
+ }
+ const bool interpIsSrc = interpCRS->_isEquivalentTo(
+ sourceCRS().get(), util::IComparable::Criterion::EQUIVALENT);
+ const bool interpIsTarget = interpCRS->_isEquivalentTo(
+ targetCRS().get(), util::IComparable::Criterion::EQUIVALENT);
+ if (!interpIsSrc && !interpIsTarget) {
+ throw io::FormattingException(
+ "For"
+ " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN
+ ", interpolation CRS should be the source or target CRS");
+ }
+
+ formatter->startInversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ if (isMethodInverseOf) {
+ formatter->startInversion();
+ }
+
+ formatter->addStep("push");
+ formatter->addParam("v_3");
+
+ formatter->addStep("cart");
+ sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+
+ formatter->addStep("xyzgridshift");
+ formatter->addParam("grids", geocentricTranslationFilename);
+ formatter->addParam("grid_ref",
+ interpIsTarget ? "output_crs" : "input_crs");
+ (interpIsTarget ? targetCRSGeog : sourceCRSGeog)
+ ->ellipsoid()
+ ->_exportToPROJString(formatter);
+
+ formatter->startInversion();
+ formatter->addStep("cart");
+ targetCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+ formatter->stopInversion();
+
+ formatter->addStep("pop");
+ formatter->addParam("v_3");
+
+ if (isMethodInverseOf) {
+ formatter->stopInversion();
+ }
+
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+
+ return;
+ }
+
+ const auto &heightFilename = _getHeightToGeographic3DFilename(this, true);
+ if (!heightFilename.empty()) {
+ auto targetCRSGeog =
+ extractGeographicCRSIfGeographicCRSOrEquivalent(targetCRS());
+ if (!targetCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName, " only to GeographicCRS"));
+ }
+
+ if (!formatter->omitHorizontalConversionInVertTransformation()) {
+ formatter->startInversion();
+ formatter->pushOmitZUnitConversion();
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->popOmitZUnitConversion();
+ formatter->stopInversion();
+ }
+
+ if (isMethodInverseOf) {
+ formatter->startInversion();
+ }
+ formatter->addStep("vgridshift");
+ formatter->addParam("grids", heightFilename);
+ formatter->addParam("multiplier", 1.0);
+ if (isMethodInverseOf) {
+ formatter->stopInversion();
+ }
+
+ if (!formatter->omitHorizontalConversionInVertTransformation()) {
+ formatter->pushOmitZUnitConversion();
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->popOmitZUnitConversion();
+ }
+
+ 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();
+
+ auto sourceCRSGeog =
+ extractGeographicCRSIfGeographicCRSOrEquivalent(sourceCRS());
+ if (!sourceCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName, " only to GeographicCRS"));
+ }
+
+ if (!formatter->omitHorizontalConversionInVertTransformation()) {
+ formatter->startInversion();
+ formatter->pushOmitZUnitConversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->popOmitZUnitConversion();
+ formatter->stopInversion();
+ }
+
+ bool doInversion = isMethodInverseOf;
+ // The EPSG Geog3DToHeight is the reverse convention of PROJ !
+ doInversion = !doInversion;
+ if (doInversion) {
+ formatter->startInversion();
+ }
+ formatter->addStep("vgridshift");
+ formatter->addParam("grids", filename);
+ formatter->addParam("multiplier", 1.0);
+ if (doInversion) {
+ formatter->stopInversion();
+ }
+
+ if (!formatter->omitHorizontalConversionInVertTransformation()) {
+ formatter->pushOmitZUnitConversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->popOmitZUnitConversion();
+ }
+
+ 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");
+ formatter->addParam("grids", fileParameter->valueFile());
+ if (fileParameter->valueFile().find(".tif") != std::string::npos) {
+ formatter->addParam("multiplier", 1.0);
+ } else {
+ // The vertcon grids go from NGVD 29 to NAVD 88, with units
+ // in millimeter (see
+ // https://github.com/OSGeo/proj.4/issues/1071), for gtx files
+ formatter->addParam("multiplier", 0.001);
+ }
+ return;
+ }
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD ||
+ methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT ||
+ methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX) {
+ auto fileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE,
+ EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ formatter->addStep("vgridshift");
+ formatter->addParam("grids", fileParameter->valueFile());
+ formatter->addParam("multiplier", 1.0);
+ return;
+ }
+ }
+
+ if (isLongitudeRotation()) {
+ double offsetDeg =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
+ 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(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ // 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");
+}
+
+} // namespace crs
+NS_PROJ_END
diff --git a/src/iso19111/operation/vectorofvaluesparams.cpp b/src/iso19111/operation/vectorofvaluesparams.cpp
new file mode 100644
index 00000000..e70ecced
--- /dev/null
+++ b/src/iso19111/operation/vectorofvaluesparams.cpp
@@ -0,0 +1,116 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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.
+ ****************************************************************************/
+
+#include "vectorofvaluesparams.hpp"
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+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;
+
+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)};
+}
+
+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)};
+}
+
+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),
+ };
+}
+
+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),
+ };
+}
+
+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),
+ };
+}
+
+// This way, we disable inlining of destruction, and save a lot of space
+VectorOfParameters::~VectorOfParameters() = default;
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
diff --git a/src/iso19111/operation/vectorofvaluesparams.hpp b/src/iso19111/operation/vectorofvaluesparams.hpp
new file mode 100644
index 00000000..6f12543b
--- /dev/null
+++ b/src/iso19111/operation/vectorofvaluesparams.hpp
@@ -0,0 +1,101 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2019 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 VECTOROFVALUESPARAMS_HPP
+#define VECTOROFVALUESPARAMS_HPP
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/util.hpp"
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+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();
+};
+
+VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3);
+
+VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3,
+ const common::Measure &m4);
+
+VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3,
+ const common::Measure &m4,
+ const common::Measure &m5);
+
+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);
+
+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);
+
+// ---------------------------------------------------------------------------
+
+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();
+};
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END
+
+#endif // VECTOROFVALUESPARAMS_HPP