aboutsummaryrefslogtreecommitdiff
path: root/src/coordinateoperation.cpp
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@mines-paris.org>2018-11-15 10:12:17 +0100
committerGitHub <noreply@github.com>2018-11-15 10:12:17 +0100
commit4ad1477c24c3da4be6c7962606e6f9d47336b6a3 (patch)
treeeb4ca374b50ab3c1183a25ac264245c7b4fd681b /src/coordinateoperation.cpp
parent73865a4df1a7d4c37052647f0458567751735f00 (diff)
parentd928db15d53805d9b728b440079756081961c536 (diff)
downloadPROJ-4ad1477c24c3da4be6c7962606e6f9d47336b6a3.tar.gz
PROJ-4ad1477c24c3da4be6c7962606e6f9d47336b6a3.zip
Merge pull request #1175 from rouault/iso19111_for_merge
Implement RFC 2: Initial integration of "GDAL SRS barn" work
Diffstat (limited to 'src/coordinateoperation.cpp')
-rw-r--r--src/coordinateoperation.cpp10915
1 files changed, 10915 insertions, 0 deletions
diff --git a/src/coordinateoperation.cpp b/src/coordinateoperation.cpp
new file mode 100644
index 00000000..645bd0ed
--- /dev/null
+++ b/src/coordinateoperation.cpp
@@ -0,0 +1,10915 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2018 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+#define FROM_COORDINATE_OPERATION_CPP
+
+#include "proj/coordinateoperation.hpp"
+#include "proj/common.hpp"
+#include "proj/crs.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+#include "proj/internal/io_internal.hpp"
+
+#include "projects.h" // M_PI
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+using namespace NS_PROJ::internal;
+
+#if 0
+namespace dropbox{ namespace oxygen {
+template<> nn<NS_PROJ::operation::CoordinateOperationPtr>::~nn() = default;
+template<> nn<NS_PROJ::operation::SingleOperationPtr>::~nn() = default;
+template<> nn<NS_PROJ::operation::ConversionPtr>::~nn() = default;
+template<> nn<NS_PROJ::operation::TransformationPtr>::~nn() = default;
+template<> nn<NS_PROJ::operation::ConcatenatedOperationPtr>::~nn() = default;
+template<> nn<NS_PROJ::operation::PointMotionOperationPtr>::~nn() = default;
+template<> nn<NS_PROJ::operation::GeneralOperationParameterPtr>::~nn() = default;
+template<> nn<NS_PROJ::operation::OperationParameterPtr>::~nn() = default;
+template<> nn<NS_PROJ::operation::GeneralParameterValuePtr>::~nn() = default;
+template<> nn<NS_PROJ::operation::ParameterValuePtr>::~nn() = default;
+template<> nn<NS_PROJ::operation::OperationMethodPtr>::~nn() = default;
+template<> nn<NS_PROJ::operation::OperationParameterValuePtr>::~nn() = default;
+template<> nn<std::unique_ptr<NS_PROJ::operation::CoordinateOperationFactory, std::default_delete<NS_PROJ::operation::CoordinateOperationFactory> > >::~nn() = default;
+template<> nn<std::unique_ptr<NS_PROJ::operation::CoordinateOperationContext, std::default_delete<NS_PROJ::operation::CoordinateOperationContext> > >::~nn() = default;
+}}
+#endif
+
+#include "proj/internal/coordinateoperation_constants.hpp"
+#include "proj/internal/coordinateoperation_internal.hpp"
+#include "proj/internal/esri_projection_mappings.hpp"
+
+#if 0
+namespace dropbox{ namespace oxygen {
+template<> nn<std::shared_ptr<NS_PROJ::operation::InverseCoordinateOperation>>::~nn() = default;
+template<> nn<std::shared_ptr<NS_PROJ::operation::InverseConversion>>::~nn() = default;
+template<> nn<std::shared_ptr<NS_PROJ::operation::InverseTransformation>>::~nn() = default;
+template<> nn<NS_PROJ::operation::PROJBasedOperationPtr>::~nn() = default;
+}}
+#endif
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_START
+namespace operation {
+
+//! @cond Doxygen_Suppress
+
+constexpr double UTM_LATITUDE_OF_NATURAL_ORIGIN = 0.0;
+constexpr double UTM_SCALE_FACTOR = 0.9996;
+constexpr double UTM_FALSE_EASTING = 500000.0;
+constexpr double UTM_NORTH_FALSE_NORTHING = 0.0;
+constexpr double UTM_SOUTH_FALSE_NORTHING = 10000000.0;
+
+static const std::string INVERSE_OF = "Inverse of ";
+static const char *NULL_GEOCENTRIC_TRANSLATION = "Null geocentric translation";
+static const char *NULL_GEOGRAPHIC_OFFSET = "Null geographic offset";
+//! @endcond
+
+//! @cond Doxygen_Suppress
+static util::PropertyMap
+createPropertiesForInverse(const CoordinateOperation *op, bool derivedFrom,
+ bool approximateInversion);
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+class InvalidOperationEmptyIntersection : public InvalidOperation {
+ public:
+ explicit InvalidOperationEmptyIntersection(const std::string &message);
+ InvalidOperationEmptyIntersection(
+ const InvalidOperationEmptyIntersection &other);
+ ~InvalidOperationEmptyIntersection() override;
+};
+
+InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection(
+ const std::string &message)
+ : InvalidOperation(message) {}
+
+InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection(
+ const InvalidOperationEmptyIntersection &) = default;
+
+InvalidOperationEmptyIntersection::~InvalidOperationEmptyIntersection() =
+ default;
+
+// ---------------------------------------------------------------------------
+
+static std::string createEntryEqParam(const std::string &a,
+ const std::string &b) {
+ return a < b ? a + b : b + a;
+}
+
+static std::set<std::string> buildSetEquivalentParameters() {
+
+ std::set<std::string> set;
+
+ const char *const listOfEquivalentParameterNames[][5] = {
+ {"latitude_of_point_1", "Latitude_Of_1st_Point", nullptr},
+ {"longitude_of_point_1", "Longitude_Of_1st_Point", nullptr},
+ {"latitude_of_point_2", "Latitude_Of_2nd_Point", nullptr},
+ {"longitude_of_point_2", "Longitude_Of_2nd_Point", nullptr},
+
+ {EPSG_NAME_PARAMETER_FALSE_EASTING,
+ EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN,
+ EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, nullptr},
+
+ {EPSG_NAME_PARAMETER_FALSE_NORTHING,
+ EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN,
+ EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, nullptr},
+
+ {EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN,
+ EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, nullptr},
+
+ {EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN,
+ EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, nullptr},
+ };
+
+ for (const auto &paramList : listOfEquivalentParameterNames) {
+ for (size_t i = 0; paramList[i]; i++) {
+ auto a = metadata::Identifier::canonicalizeName(paramList[i]);
+ for (size_t j = i + 1; paramList[j]; j++) {
+ auto b = metadata::Identifier::canonicalizeName(paramList[j]);
+ set.insert(createEntryEqParam(a, b));
+ }
+ }
+ }
+ return set;
+}
+
+static bool areEquivalentParameters(const std::string &a,
+ const std::string &b) {
+
+ static const std::set<std::string> setEquivalentParameters =
+ buildSetEquivalentParameters();
+
+ auto a_can = metadata::Identifier::canonicalizeName(a);
+ auto b_can = metadata::Identifier::canonicalizeName(b);
+ return setEquivalentParameters.find(createEntryEqParam(a_can, b_can)) !=
+ setEquivalentParameters.end();
+}
+
+// ---------------------------------------------------------------------------
+
+PROJ_NO_INLINE const MethodMapping *getMapping(int epsg_code) noexcept {
+ for (const auto &mapping : methodMappings) {
+ if (mapping.epsg_code == epsg_code) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+const MethodMapping *getMapping(const OperationMethod *method) noexcept {
+ const std::string &name(method->nameStr());
+ const int epsg_code = method->getEPSGCode();
+ for (const auto &mapping : methodMappings) {
+ if ((epsg_code != 0 && mapping.epsg_code == epsg_code) ||
+ metadata::Identifier::isEquivalentName(mapping.wkt2_name,
+ name.c_str())) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+const MethodMapping *getMappingFromWKT1(const std::string &wkt1_name) noexcept {
+ // Unusual for a WKT1 projection name, but mentionned in OGC 12-063r5 C.4.2
+ if (ci_starts_with(wkt1_name, "UTM zone")) {
+ return getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR);
+ }
+
+ for (const auto &mapping : methodMappings) {
+ if (mapping.wkt1_name && metadata::Identifier::isEquivalentName(
+ mapping.wkt1_name, wkt1_name.c_str())) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+// ---------------------------------------------------------------------------
+
+const MethodMapping *getMapping(const char *wkt2_name) noexcept {
+ for (const auto &mapping : methodMappings) {
+ if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
+ wkt2_name)) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<const MethodMapping *>
+getMappingsFromPROJName(const std::string &projName) {
+ std::vector<const MethodMapping *> res;
+ for (const auto &mapping : methodMappings) {
+ if (mapping.proj_name_main && projName == mapping.proj_name_main) {
+ res.push_back(&mapping);
+ }
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+const ParamMapping *getMapping(const MethodMapping *mapping,
+ const OperationParameterValue *param) {
+ const auto &param_name = param->parameter()->name();
+ const std::string &name = *(param_name->description());
+ const std::string &code = param_name->code();
+ const int epsg_code = !code.empty() ? ::atoi(code.c_str()) : 0;
+ for (int i = 0; mapping->params[i] != nullptr; ++i) {
+ const auto *paramMapping = mapping->params[i];
+ if (metadata::Identifier::isEquivalentName(paramMapping->wkt2_name,
+ name.c_str()) ||
+ (epsg_code != 0 && paramMapping->epsg_code == epsg_code) ||
+ areEquivalentParameters(paramMapping->wkt2_name, name)) {
+ return paramMapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping,
+ const std::string &wkt1_name) {
+ for (int i = 0; mapping->params[i] != nullptr; ++i) {
+ const auto *paramMapping = mapping->params[i];
+ if (paramMapping->wkt1_name &&
+ (metadata::Identifier::isEquivalentName(paramMapping->wkt1_name,
+ wkt1_name.c_str()) ||
+ areEquivalentParameters(paramMapping->wkt1_name, wkt1_name))) {
+ return paramMapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<const ESRIMethodMapping *>
+getMappingsFromESRI(const std::string &esri_name) {
+ std::vector<const ESRIMethodMapping *> res;
+ for (const auto &mapping : esriMappings) {
+ if (ci_equal(esri_name, mapping.esri_name)) {
+ res.push_back(&mapping);
+ }
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+static const ESRIMethodMapping *getESRIMapping(const std::string &wkt2_name,
+ int epsg_code) {
+ for (const auto &mapping : esriMappings) {
+ if ((epsg_code != 0 && mapping.epsg_code == epsg_code) ||
+ ci_equal(wkt2_name, mapping.wkt2_name)) {
+ return &mapping;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+static double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops);
+
+// Returns the accuracy of an operation, or -1 if unknown
+static double getAccuracy(const CoordinateOperationNNPtr &op) {
+
+ if (dynamic_cast<const Conversion *>(op.get())) {
+ // A conversion is perfectly accurate.
+ return 0.0;
+ }
+
+ double accuracy = -1.0;
+ const auto &accuracies = op->coordinateOperationAccuracies();
+ if (!accuracies.empty()) {
+ try {
+ accuracy = c_locale_stod(accuracies[0]->value());
+ } catch (const std::exception &) {
+ }
+ } else {
+ auto concatenated =
+ dynamic_cast<const ConcatenatedOperation *>(op.get());
+ if (concatenated) {
+ accuracy = getAccuracy(concatenated->operations());
+ }
+ }
+ return accuracy;
+}
+
+// ---------------------------------------------------------------------------
+
+// Returns the accuracy of a set of concantenated operations, or -1 if unknown
+static double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops) {
+ double accuracy = -1.0;
+ for (const auto &subop : ops) {
+ const double subops_accuracy = getAccuracy(subop);
+ if (subops_accuracy < 0.0) {
+ return -1.0;
+ }
+ if (accuracy < 0.0) {
+ accuracy = 0.0;
+ }
+ accuracy += subops_accuracy;
+ }
+ return accuracy;
+}
+
+// ---------------------------------------------------------------------------
+
+static metadata::ExtentPtr
+getExtent(const std::vector<CoordinateOperationNNPtr> &ops,
+ bool conversionExtentIsWorld, bool &emptyIntersection);
+
+static metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op,
+ bool conversionExtentIsWorld,
+ bool &emptyIntersection) {
+ auto conv = dynamic_cast<const Conversion *>(op.get());
+ if (conv) {
+ emptyIntersection = false;
+ return metadata::Extent::WORLD;
+ }
+ const auto &domains = op->domains();
+ if (!domains.empty()) {
+ emptyIntersection = false;
+ return domains[0]->domainOfValidity();
+ }
+ auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get());
+ if (!concatenated) {
+ emptyIntersection = false;
+ return nullptr;
+ }
+ return getExtent(concatenated->operations(), conversionExtentIsWorld,
+ emptyIntersection);
+}
+
+// ---------------------------------------------------------------------------
+
+static const metadata::ExtentPtr nullExtent{};
+
+static const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs) {
+ const auto &domains = crs->domains();
+ if (!domains.empty()) {
+ return domains[0]->domainOfValidity();
+ }
+ return nullExtent;
+}
+
+// ---------------------------------------------------------------------------
+
+static metadata::ExtentPtr
+getExtent(const std::vector<CoordinateOperationNNPtr> &ops,
+ bool conversionExtentIsWorld, bool &emptyIntersection) {
+ metadata::ExtentPtr res = nullptr;
+ for (const auto &subop : ops) {
+
+ const auto &subExtent =
+ getExtent(subop, conversionExtentIsWorld, emptyIntersection);
+ if (!subExtent) {
+ if (emptyIntersection) {
+ return nullptr;
+ }
+ continue;
+ }
+ if (res == nullptr) {
+ res = subExtent;
+ } else {
+ res = res->intersection(NN_NO_CHECK(subExtent));
+ if (!res) {
+ emptyIntersection = true;
+ return nullptr;
+ }
+ }
+ }
+ emptyIntersection = false;
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+static double getPseudoArea(const metadata::ExtentPtr &extent) {
+ if (!extent)
+ return 0.0;
+ const auto &geographicElements = extent->geographicElements();
+ if (geographicElements.empty())
+ return 0.0;
+ auto bbox = dynamic_cast<const metadata::GeographicBoundingBox *>(
+ geographicElements[0].get());
+ if (!bbox)
+ return 0;
+ double w = bbox->westBoundLongitude();
+ double s = bbox->southBoundLatitude();
+ double e = bbox->eastBoundLongitude();
+ double n = bbox->northBoundLatitude();
+ if (w > e) {
+ e += 360.0;
+ }
+ // Integrate cos(lat) between south_lat and north_lat
+ return (e - w) * (std::sin(common::Angle(n).getSIValue()) -
+ std::sin(common::Angle(s).getSIValue()));
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct CoordinateOperation::Private {
+ util::optional<std::string> operationVersion_{};
+ std::vector<metadata::PositionalAccuracyNNPtr>
+ coordinateOperationAccuracies_{};
+ std::weak_ptr<crs::CRS> sourceCRSWeak_{};
+ std::weak_ptr<crs::CRS> targetCRSWeak_{};
+ crs::CRSPtr interpolationCRS_{};
+ util::optional<common::DataEpoch> sourceCoordinateEpoch_{};
+ util::optional<common::DataEpoch> targetCoordinateEpoch_{};
+
+ // do not set this for a ProjectedCRS.definingConversion
+ struct CRSStrongRef {
+ crs::CRSNNPtr sourceCRS_;
+ crs::CRSNNPtr targetCRS_;
+ CRSStrongRef(const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn)
+ : sourceCRS_(sourceCRSIn), targetCRS_(targetCRSIn) {}
+ };
+ std::unique_ptr<CRSStrongRef> strongRef_{};
+
+ Private() = default;
+ Private(const Private &other)
+ : operationVersion_(other.operationVersion_),
+ coordinateOperationAccuracies_(other.coordinateOperationAccuracies_),
+ sourceCRSWeak_(other.sourceCRSWeak_),
+ targetCRSWeak_(other.targetCRSWeak_),
+ interpolationCRS_(other.interpolationCRS_),
+ sourceCoordinateEpoch_(other.sourceCoordinateEpoch_),
+ targetCoordinateEpoch_(other.targetCoordinateEpoch_),
+ strongRef_(other.strongRef_ ? internal::make_unique<CRSStrongRef>(
+ *(other.strongRef_))
+ : nullptr) {}
+
+ Private &operator=(const Private &) = delete;
+};
+
+// ---------------------------------------------------------------------------
+
+GridDescription::GridDescription() = default;
+
+GridDescription::~GridDescription() = default;
+
+GridDescription::GridDescription(const GridDescription &) = default;
+
+GridDescription::GridDescription(GridDescription &&) noexcept = default;
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperation::CoordinateOperation()
+ : d(internal::make_unique<Private>()) {}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperation::CoordinateOperation(const CoordinateOperation &other)
+ : ObjectUsage(other), d(internal::make_unique<Private>(*other.d)) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+CoordinateOperation::~CoordinateOperation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the version of the coordinate transformation (i.e.
+ * instantiation
+ * due to the stochastic nature of the parameters).
+ *
+ * Mandatory when describing a coordinate transformation or point motion
+ * operation, and should not be supplied for a coordinate conversion.
+ *
+ * @return version or empty.
+ */
+const util::optional<std::string> &
+CoordinateOperation::operationVersion() const {
+ return d->operationVersion_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return estimate(s) of the impact of this coordinate operation on
+ * point accuracy.
+ *
+ * Gives position error estimates for target coordinates of this coordinate
+ * operation, assuming no errors in source coordinates.
+ *
+ * @return estimate(s) or empty vector.
+ */
+const std::vector<metadata::PositionalAccuracyNNPtr> &
+CoordinateOperation::coordinateOperationAccuracies() const {
+ return d->coordinateOperationAccuracies_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the source CRS of this coordinate operation.
+ *
+ * This should not be null, expect for of a derivingConversion of a DerivedCRS
+ * when the owning DerivedCRS has been destroyed.
+ *
+ * @return source CRS, or null.
+ */
+const crs::CRSPtr CoordinateOperation::sourceCRS() const {
+ return d->sourceCRSWeak_.lock();
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the target CRS of this coordinate operation.
+ *
+ * This should not be null, expect for of a derivingConversion of a DerivedCRS
+ * when the owning DerivedCRS has been destroyed.
+ *
+ * @return target CRS, or null.
+ */
+const crs::CRSPtr CoordinateOperation::targetCRS() const {
+ return d->targetCRSWeak_.lock();
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the interpolation CRS of this coordinate operation.
+ *
+ * @return interpolation CRS, or null.
+ */
+const crs::CRSPtr &CoordinateOperation::interpolationCRS() const {
+ return d->interpolationCRS_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the source epoch of coordinates.
+ *
+ * @return source epoch of coordinates, or empty.
+ */
+const util::optional<common::DataEpoch> &
+CoordinateOperation::sourceCoordinateEpoch() const {
+ return d->sourceCoordinateEpoch_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the target epoch of coordinates.
+ *
+ * @return target epoch of coordinates, or empty.
+ */
+const util::optional<common::DataEpoch> &
+CoordinateOperation::targetCoordinateEpoch() const {
+ return d->targetCoordinateEpoch_;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperation::setWeakSourceTargetCRS(
+ std::weak_ptr<crs::CRS> sourceCRSIn, std::weak_ptr<crs::CRS> targetCRSIn) {
+ d->sourceCRSWeak_ = sourceCRSIn;
+ d->targetCRSWeak_ = targetCRSIn;
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperation::setCRSs(const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn,
+ const crs::CRSPtr &interpolationCRSIn) {
+ d->strongRef_ =
+ internal::make_unique<Private::CRSStrongRef>(sourceCRSIn, targetCRSIn);
+ d->sourceCRSWeak_ = sourceCRSIn.as_nullable();
+ d->targetCRSWeak_ = targetCRSIn.as_nullable();
+ d->interpolationCRS_ = interpolationCRSIn;
+}
+// ---------------------------------------------------------------------------
+
+void CoordinateOperation::setCRSs(const CoordinateOperation *in,
+ bool inverseSourceTarget) {
+ auto l_sourceCRS = in->sourceCRS();
+ auto l_targetCRS = in->targetCRS();
+ if (l_sourceCRS && l_targetCRS) {
+ auto nn_sourceCRS = NN_NO_CHECK(l_sourceCRS);
+ auto nn_targetCRS = NN_NO_CHECK(l_targetCRS);
+ if (inverseSourceTarget) {
+ setCRSs(nn_targetCRS, nn_sourceCRS, in->interpolationCRS());
+ } else {
+ setCRSs(nn_sourceCRS, nn_targetCRS, in->interpolationCRS());
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperation::setAccuracies(
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ d->coordinateOperationAccuracies_ = accuracies;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether a coordinate operation can be instanciated as
+ * a PROJ pipeline, checking in particular that referenced grids are
+ * available.
+ */
+bool CoordinateOperation::isPROJInstanciable(
+ const io::DatabaseContextPtr &databaseContext) const {
+ try {
+ exportToPROJString(io::PROJStringFormatter::create().get());
+ } catch (const std::exception &) {
+ return false;
+ }
+ for (const auto &gridDesc : gridsNeeded(databaseContext)) {
+ if (!gridDesc.available) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct OperationMethod::Private {
+ util::optional<std::string> formula_{};
+ util::optional<metadata::Citation> formulaCitation_{};
+ std::vector<GeneralOperationParameterNNPtr> parameters_{};
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+OperationMethod::OperationMethod() : d(internal::make_unique<Private>()) {}
+
+// ---------------------------------------------------------------------------
+
+OperationMethod::OperationMethod(const OperationMethod &other)
+ : IdentifiedObject(other), d(internal::make_unique<Private>(*other.d)) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+OperationMethod::~OperationMethod() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the formula(s) or procedure used by this coordinate operation
+ * method.
+ *
+ * This may be a reference to a publication (in which case use
+ * formulaCitation()).
+ *
+ * Note that the operation method may not be analytic, in which case this
+ * attribute references or contains the procedure, not an analytic formula.
+ *
+ * @return the formula, or empty.
+ */
+const util::optional<std::string> &OperationMethod::formula() PROJ_CONST_DEFN {
+ return d->formula_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a reference to a publication giving the formula(s) or
+ * procedure
+ * used by the coordinate operation method.
+ *
+ * @return the formula citation, or empty.
+ */
+const util::optional<metadata::Citation> &
+OperationMethod::formulaCitation() PROJ_CONST_DEFN {
+ return d->formulaCitation_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameters of this operation method.
+ *
+ * @return the parameters.
+ */
+const std::vector<GeneralOperationParameterNNPtr> &
+OperationMethod::parameters() PROJ_CONST_DEFN {
+ return d->parameters_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a operation method from a vector of
+ * GeneralOperationParameter.
+ *
+ * @param properties See \ref general_properties. At minimum the name should be
+ * defined.
+ * @param parameters Vector of GeneralOperationParameterNNPtr.
+ * @return a new OperationMethod.
+ */
+OperationMethodNNPtr OperationMethod::create(
+ const util::PropertyMap &properties,
+ const std::vector<GeneralOperationParameterNNPtr> &parameters) {
+ OperationMethodNNPtr method(
+ OperationMethod::nn_make_shared<OperationMethod>());
+ method->assignSelf(method);
+ method->setProperties(properties);
+ method->d->parameters_ = parameters;
+ return method;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a operation method from a vector of OperationParameter.
+ *
+ * @param properties See \ref general_properties. At minimum the name should be
+ * defined.
+ * @param parameters Vector of OperationParameterNNPtr.
+ * @return a new OperationMethod.
+ */
+OperationMethodNNPtr OperationMethod::create(
+ const util::PropertyMap &properties,
+ const std::vector<OperationParameterNNPtr> &parameters) {
+ std::vector<GeneralOperationParameterNNPtr> parametersGeneral;
+ parametersGeneral.reserve(parameters.size());
+ for (const auto &p : parameters) {
+ parametersGeneral.push_back(p);
+ }
+ return create(properties, parametersGeneral);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the EPSG code, either directly, or through the name
+ * @return code, or 0 if not found
+ */
+int OperationMethod::getEPSGCode() PROJ_CONST_DEFN {
+ int epsg_code = IdentifiedObject::getEPSGCode();
+ if (epsg_code == 0) {
+ const auto &l_name = nameStr();
+ for (const auto &tuple : methodNameCodes) {
+ if (metadata::Identifier::isEquivalentName(l_name.c_str(),
+ tuple.name)) {
+ return tuple.epsg_code;
+ }
+ }
+ }
+ return epsg_code;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void OperationMethod::_exportToWKT(io::WKTFormatter *formatter) const {
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ formatter->startNode(isWKT2 ? io::WKTConstants::METHOD
+ : io::WKTConstants::PROJECTION,
+ !identifiers().empty());
+ std::string l_name(nameStr());
+ if (!isWKT2) {
+ const MethodMapping *mapping = getMapping(this);
+ if (mapping == nullptr) {
+ l_name = replaceAll(l_name, " ", "_");
+ } else {
+ if (mapping->wkt1_name == nullptr) {
+ throw io::FormattingException(
+ std::string("Unsupported conversion method: ") +
+ mapping->wkt2_name);
+ }
+ l_name = mapping->wkt1_name;
+ }
+ }
+ formatter->addQuotedString(l_name);
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+ formatter->endNode();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool OperationMethod::_isEquivalentTo(
+ const util::IComparable *other,
+ util::IComparable::Criterion criterion) const {
+ auto otherOM = dynamic_cast<const OperationMethod *>(other);
+ if (otherOM == nullptr ||
+ !IdentifiedObject::_isEquivalentTo(other, criterion)) {
+ return false;
+ }
+ // TODO test formula and formulaCitation
+ const auto &params = parameters();
+ const auto &otherParams = otherOM->parameters();
+ const auto paramsSize = params.size();
+ if (paramsSize != otherParams.size()) {
+ return false;
+ }
+ if (criterion == util::IComparable::Criterion::STRICT) {
+ for (size_t i = 0; i < paramsSize; i++) {
+ if (!params[i]->_isEquivalentTo(otherParams[i].get(), criterion)) {
+ return false;
+ }
+ }
+ } else {
+ std::vector<bool> candidateIndices(paramsSize, true);
+ for (size_t i = 0; i < paramsSize; i++) {
+ bool found = false;
+ for (size_t j = 0; j < paramsSize; j++) {
+ if (candidateIndices[j] &&
+ params[i]->_isEquivalentTo(otherParams[j].get(),
+ criterion)) {
+ candidateIndices[j] = false;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct GeneralParameterValue::Private {};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+GeneralParameterValue::GeneralParameterValue() : d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+GeneralParameterValue::GeneralParameterValue(const GeneralParameterValue &)
+ : d(nullptr) {}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+GeneralParameterValue::~GeneralParameterValue() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct OperationParameterValue::Private {
+ OperationParameterNNPtr parameter;
+ ParameterValueNNPtr parameterValue;
+
+ Private(const OperationParameterNNPtr &parameterIn,
+ const ParameterValueNNPtr &valueIn)
+ : parameter(parameterIn), parameterValue(valueIn) {}
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+OperationParameterValue::OperationParameterValue(
+ const OperationParameterValue &other)
+ : GeneralParameterValue(other),
+ d(internal::make_unique<Private>(*other.d)) {}
+
+// ---------------------------------------------------------------------------
+
+OperationParameterValue::OperationParameterValue(
+ const OperationParameterNNPtr &parameterIn,
+ const ParameterValueNNPtr &valueIn)
+ : GeneralParameterValue(),
+ d(internal::make_unique<Private>(parameterIn, valueIn)) {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a OperationParameterValue.
+ *
+ * @param parameterIn Parameter (definition).
+ * @param valueIn Parameter value.
+ * @return a new OperationParameterValue.
+ */
+OperationParameterValueNNPtr
+OperationParameterValue::create(const OperationParameterNNPtr &parameterIn,
+ const ParameterValueNNPtr &valueIn) {
+ return OperationParameterValue::nn_make_shared<OperationParameterValue>(
+ parameterIn, valueIn);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+OperationParameterValue::~OperationParameterValue() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameter (definition)
+ *
+ * @return the parameter (definition).
+ */
+const OperationParameterNNPtr &
+OperationParameterValue::parameter() PROJ_CONST_DEFN {
+ return d->parameter;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameter value.
+ *
+ * @return the parameter value.
+ */
+const ParameterValueNNPtr &
+OperationParameterValue::parameterValue() PROJ_CONST_DEFN {
+ return d->parameterValue;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void OperationParameterValue::_exportToWKT(
+ // cppcheck-suppress passedByValue
+ io::WKTFormatter *formatter) const {
+ _exportToWKT(formatter, nullptr);
+}
+
+void OperationParameterValue::_exportToWKT(io::WKTFormatter *formatter,
+ const MethodMapping *mapping) const {
+ const ParamMapping *paramMapping =
+ mapping ? getMapping(mapping, this) : nullptr;
+ if (paramMapping && paramMapping->wkt1_name == nullptr) {
+ return;
+ }
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ if (isWKT2 && parameterValue()->type() == ParameterValue::Type::FILENAME) {
+ formatter->startNode(io::WKTConstants::PARAMETERFILE,
+ !parameter()->identifiers().empty());
+ } else {
+ formatter->startNode(io::WKTConstants::PARAMETER,
+ !parameter()->identifiers().empty());
+ }
+ if (paramMapping) {
+ formatter->addQuotedString(paramMapping->wkt1_name);
+ } else {
+ formatter->addQuotedString(parameter()->nameStr());
+ }
+ parameterValue()->_exportToWKT(formatter);
+ if (formatter->outputId()) {
+ parameter()->formatID(formatter);
+ }
+ formatter->endNode();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+/** Utility method used on WKT2 import to convert from abridged transformation
+ * to "normal" transformation parameters.
+ */
+bool OperationParameterValue::convertFromAbridged(
+ const std::string &paramName, double &val,
+ const common::UnitOfMeasure *&unit, int &paramEPSGCode) {
+ if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION) ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) {
+ unit = &common::UnitOfMeasure::METRE;
+ paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION;
+ return true;
+ } else if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION) ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) {
+ unit = &common::UnitOfMeasure::METRE;
+ paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION;
+ return true;
+ } else if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION) ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) {
+ unit = &common::UnitOfMeasure::METRE;
+ paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION;
+ return true;
+ } else if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_ROTATION) ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_ROTATION) {
+ unit = &common::UnitOfMeasure::ARC_SECOND;
+ paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_ROTATION;
+ return true;
+ } else if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_ROTATION) ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) {
+ unit = &common::UnitOfMeasure::ARC_SECOND;
+ paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_ROTATION;
+ return true;
+
+ } else if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_ROTATION) ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) {
+ unit = &common::UnitOfMeasure::ARC_SECOND;
+ paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_ROTATION;
+ return true;
+
+ } else if (metadata::Identifier::isEquivalentName(
+ paramName.c_str(), EPSG_NAME_PARAMETER_SCALE_DIFFERENCE) ||
+ paramEPSGCode == EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) {
+ val = (val - 1.0) * 1e6;
+ unit = &common::UnitOfMeasure::PARTS_PER_MILLION;
+ paramEPSGCode = EPSG_CODE_PARAMETER_SCALE_DIFFERENCE;
+ return true;
+ }
+ return false;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool OperationParameterValue::_isEquivalentTo(
+ const util::IComparable *other,
+ util::IComparable::Criterion criterion) const {
+ auto otherOPV = dynamic_cast<const OperationParameterValue *>(other);
+ if (otherOPV == nullptr) {
+ return false;
+ }
+ return d->parameter->_isEquivalentTo(otherOPV->d->parameter.get(),
+ criterion) &&
+ d->parameterValue->_isEquivalentTo(otherOPV->d->parameterValue.get(),
+ criterion);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct GeneralOperationParameter::Private {};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+GeneralOperationParameter::GeneralOperationParameter() : d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+GeneralOperationParameter::GeneralOperationParameter(
+ const GeneralOperationParameter &other)
+ : IdentifiedObject(other), d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+GeneralOperationParameter::~GeneralOperationParameter() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct OperationParameter::Private {};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+OperationParameter::OperationParameter() : d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+OperationParameter::OperationParameter(const OperationParameter &other)
+ : GeneralOperationParameter(other), d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+OperationParameter::~OperationParameter() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a OperationParameter.
+ *
+ * @param properties See \ref general_properties. At minimum the name should be
+ * defined.
+ * @return a new OperationParameter.
+ */
+OperationParameterNNPtr
+OperationParameter::create(const util::PropertyMap &properties) {
+ OperationParameterNNPtr op(
+ OperationParameter::nn_make_shared<OperationParameter>());
+ op->assignSelf(op);
+ op->setProperties(properties);
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool OperationParameter::_isEquivalentTo(
+ const util::IComparable *other,
+ util::IComparable::Criterion criterion) const {
+ auto otherOP = dynamic_cast<const OperationParameter *>(other);
+ return otherOP != nullptr &&
+ IdentifiedObject::_isEquivalentTo(other, criterion);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+void OperationParameter::_exportToWKT(io::WKTFormatter *) const {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the name of a parameter designed by its EPSG code
+ * @return name, or nullptr if not found
+ */
+const char *OperationParameter::getNameForEPSGCode(int epsg_code) noexcept {
+ for (const auto &tuple : paramNameCodes) {
+ if (tuple.epsg_code == epsg_code) {
+ return tuple.name;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the EPSG code, either directly, or through the name
+ * @return code, or 0 if not found
+ */
+int OperationParameter::getEPSGCode() PROJ_CONST_DEFN {
+ int epsg_code = IdentifiedObject::getEPSGCode();
+ if (epsg_code == 0) {
+ const auto &l_name = nameStr();
+ for (const auto &tuple : paramNameCodes) {
+ if (metadata::Identifier::isEquivalentName(l_name.c_str(),
+ tuple.name)) {
+ return tuple.epsg_code;
+ }
+ }
+ }
+ return epsg_code;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct SingleOperation::Private {
+ std::vector<GeneralParameterValueNNPtr> parameterValues_{};
+ OperationMethodNNPtr method_;
+
+ explicit Private(const OperationMethodNNPtr &methodIn)
+ : method_(methodIn) {}
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+SingleOperation::SingleOperation(const OperationMethodNNPtr &methodIn)
+ : d(internal::make_unique<Private>(methodIn)) {}
+
+// ---------------------------------------------------------------------------
+
+SingleOperation::SingleOperation(const SingleOperation &other)
+ :
+#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT)
+ CoordinateOperation(other),
+#endif
+ d(internal::make_unique<Private>(*other.d)) {
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+SingleOperation::~SingleOperation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameter values.
+ *
+ * @return the parameter values.
+ */
+const std::vector<GeneralParameterValueNNPtr> &
+SingleOperation::parameterValues() PROJ_CONST_DEFN {
+ return d->parameterValues_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the operation method associated to the operation.
+ *
+ * @return the operation method.
+ */
+const OperationMethodNNPtr &SingleOperation::method() PROJ_CONST_DEFN {
+ return d->method_;
+}
+
+// ---------------------------------------------------------------------------
+
+void SingleOperation::setParameterValues(
+ const std::vector<GeneralParameterValueNNPtr> &values) {
+ d->parameterValues_ = values;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const ParameterValuePtr nullParameterValue;
+//! @endcond
+
+/** \brief Return the parameter value corresponding to a parameter name or
+ * EPSG code
+ *
+ * @param paramName the parameter name (or empty, in which case epsg_code
+ * should be non zero)
+ * @param epsg_code the parameter EPSG code (possibly zero)
+ * @return the value, or nullptr if not found.
+ */
+const ParameterValuePtr &
+SingleOperation::parameterValue(const std::string &paramName,
+ int epsg_code) const noexcept {
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = opParamvalue->parameter();
+ if ((epsg_code != 0 && parameter->getEPSGCode() == epsg_code) ||
+ ci_equal(paramName, parameter->nameStr())) {
+ return opParamvalue->parameterValue();
+ }
+ }
+ }
+ return nullParameterValue;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the parameter value corresponding to a EPSG code
+ *
+ * @param epsg_code the parameter EPSG code
+ * @return the value, or nullptr if not found.
+ */
+const ParameterValuePtr &SingleOperation::parameterValue(int epsg_code) const
+ noexcept {
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = opParamvalue->parameter();
+ if (parameter->getEPSGCode() == epsg_code) {
+ return opParamvalue->parameterValue();
+ }
+ }
+ }
+ return nullParameterValue;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const common::Measure nullMeasure;
+//! @endcond
+
+/** \brief Return the parameter value, as a measure, corresponding to a
+ * parameter name or EPSG code
+ *
+ * @param paramName the parameter name (or empty, in which case epsg_code
+ * should be non zero)
+ * @param epsg_code the parameter EPSG code (possibly zero)
+ * @return the measure, or the empty Measure() object if not found.
+ */
+const common::Measure &
+SingleOperation::parameterValueMeasure(const std::string &paramName,
+ int epsg_code) const noexcept {
+ const auto &val = parameterValue(paramName, epsg_code);
+ if (val && val->type() == ParameterValue::Type::MEASURE) {
+ return val->value();
+ }
+ return nullMeasure;
+}
+
+/** \brief Return the parameter value, as a measure, corresponding to a
+ * EPSG code
+ *
+ * @param epsg_code the parameter EPSG code
+ * @return the measure, or the empty Measure() object if not found.
+ */
+const common::Measure &
+SingleOperation::parameterValueMeasure(int epsg_code) const noexcept {
+ const auto &val = parameterValue(epsg_code);
+ if (val && val->type() == ParameterValue::Type::MEASURE) {
+ return val->value();
+ }
+ return nullMeasure;
+}
+
+//! @cond Doxygen_Suppress
+
+double SingleOperation::parameterValueNumericAsSI(int epsg_code) const
+ noexcept {
+ const auto &val = parameterValue(epsg_code);
+ if (val && val->type() == ParameterValue::Type::MEASURE) {
+ return val->value().getSIValue();
+ }
+ return 0.0;
+}
+
+double SingleOperation::parameterValueNumeric(
+ int epsg_code, const common::UnitOfMeasure &targetUnit) const noexcept {
+ const auto &val = parameterValue(epsg_code);
+ if (val && val->type() == ParameterValue::Type::MEASURE) {
+ return val->value().convertToUnit(targetUnit);
+ }
+ return 0.0;
+}
+
+//! @endcond
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a PROJ-based single operation.
+ *
+ * \note The operation might internally be a pipeline chaining several
+ * operations.
+ * The use of the SingleOperation modeling here is mostly to be able to get
+ * the PROJ string as a parameter.
+ *
+ * @param properties Properties
+ * @param PROJString the PROJ string.
+ * @param sourceCRS source CRS (might be null).
+ * @param targetCRS target CRS (might be null).
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return the new instance
+ */
+SingleOperationNNPtr SingleOperation::createPROJBased(
+ const util::PropertyMap &properties, const std::string &PROJString,
+ const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return util::nn_static_pointer_cast<SingleOperation>(
+ PROJBasedOperation::create(properties, PROJString, sourceCRS, targetCRS,
+ accuracies));
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static SingleOperationNNPtr createPROJBased(
+ const util::PropertyMap &properties,
+ const io::IPROJStringExportableNNPtr &projExportable,
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies =
+ std::vector<metadata::PositionalAccuracyNNPtr>()) {
+ return util::nn_static_pointer_cast<SingleOperation>(
+ PROJBasedOperation::create(properties, projExportable, false, sourceCRS,
+ targetCRS, accuracies));
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool SingleOperation::_isEquivalentTo(
+ const util::IComparable *other,
+ util::IComparable::Criterion criterion) const {
+ auto otherSO = dynamic_cast<const SingleOperation *>(other);
+ if (otherSO == nullptr ||
+ (criterion == util::IComparable::Criterion::STRICT &&
+ !ObjectUsage::_isEquivalentTo(other, criterion))) {
+ return false;
+ }
+
+ const int methodEPSGCode = d->method_->getEPSGCode();
+ if (!d->method_->_isEquivalentTo(otherSO->d->method_.get(), criterion)) {
+ if (criterion == util::IComparable::Criterion::EQUIVALENT) {
+ // _1SP methods can sometimes be equivalent to _2SP ones
+ // Check it by using convertToOtherMethod()
+
+ const int otherMethodEPSGCode = otherSO->d->method_->getEPSGCode();
+ if (methodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
+ // Convert from 2SP to 1SP as the other direction has more
+ // degree of liberties.
+ return otherSO->_isEquivalentTo(this, criterion);
+ } else if ((methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_MERCATOR_VARIANT_B) ||
+ (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_MERCATOR_VARIANT_A) ||
+ (methodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
+ otherMethodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)) {
+ auto conv = dynamic_cast<const Conversion *>(this);
+ if (conv) {
+ auto eqConv =
+ conv->convertToOtherMethod(otherMethodEPSGCode);
+ if (eqConv) {
+ return eqConv->_isEquivalentTo(other, criterion);
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ const auto &values = d->parameterValues_;
+ const auto &otherValues = otherSO->d->parameterValues_;
+ const auto valuesSize = values.size();
+ if (valuesSize != otherValues.size()) {
+ return false;
+ }
+ if (criterion == util::IComparable::Criterion::STRICT) {
+ for (size_t i = 0; i < valuesSize; i++) {
+ if (!values[i]->_isEquivalentTo(otherValues[i].get(), criterion)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ std::vector<bool> candidateIndices(valuesSize, true);
+ bool equivalent = true;
+ for (size_t i = 0; i < valuesSize; i++) {
+ bool found = false;
+ for (size_t j = 0; j < valuesSize; j++) {
+ if (candidateIndices[j] &&
+ values[i]->_isEquivalentTo(otherValues[j].get(), criterion)) {
+ candidateIndices[j] = false;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ equivalent = false;
+ if (methodEPSGCode ==
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
+ // For LCC_2SP, the standard parallels can be switched and
+ // this will result in the same result.
+ auto opParamvalue =
+ dynamic_cast<const OperationParameterValue *>(
+ values[i].get());
+ if (opParamvalue) {
+ const int paramEPSGCode =
+ opParamvalue->parameter()->getEPSGCode();
+ if (paramEPSGCode ==
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL ||
+ paramEPSGCode ==
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) {
+ auto value_1st = parameterValue(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL);
+ auto value_2nd = parameterValue(
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL);
+ if (value_1st && value_2nd) {
+ equivalent =
+ value_1st->_isEquivalentTo(
+ otherSO
+ ->parameterValue(
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL)
+ .get(),
+ criterion) &&
+ value_2nd->_isEquivalentTo(
+ otherSO
+ ->parameterValue(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL)
+ .get(),
+ criterion);
+ }
+ }
+ }
+ }
+ if (!equivalent) {
+ break;
+ }
+ }
+ }
+
+ // Equivalent formulations of 2SP can have different parameters
+ // Then convert to 1SP and compare.
+ if (!equivalent &&
+ methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
+ auto conv = dynamic_cast<const Conversion *>(this);
+ auto otherConv = dynamic_cast<const Conversion *>(other);
+ if (conv && otherConv) {
+ auto thisAs1SP = conv->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ auto otherAs1SP = otherConv->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ if (thisAs1SP && otherAs1SP) {
+ equivalent =
+ thisAs1SP->_isEquivalentTo(otherAs1SP.get(), criterion);
+ }
+ }
+ }
+ return equivalent;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+std::set<GridDescription> SingleOperation::gridsNeeded(
+ const io::DatabaseContextPtr &databaseContext) const {
+ std::set<GridDescription> res;
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &value = opParamvalue->parameterValue();
+ if (value->type() == ParameterValue::Type::FILENAME) {
+ GridDescription desc;
+ desc.shortName = value->valueFile();
+ if (databaseContext) {
+ databaseContext->lookForGridInfo(
+ desc.shortName, desc.fullName, desc.packageName,
+ desc.url, desc.directDownload, desc.openLicense,
+ desc.available);
+ }
+ res.insert(desc);
+ }
+ }
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct ParameterValue::Private {
+ ParameterValue::Type type_{ParameterValue::Type::STRING};
+ std::unique_ptr<common::Measure> measure_{};
+ std::unique_ptr<std::string> stringValue_{};
+ int integerValue_{};
+ bool booleanValue_{};
+
+ explicit Private(const common::Measure &valueIn)
+ : type_(ParameterValue::Type::MEASURE),
+ measure_(internal::make_unique<common::Measure>(valueIn)) {}
+
+ Private(const std::string &stringValueIn, ParameterValue::Type typeIn)
+ : type_(typeIn),
+ stringValue_(internal::make_unique<std::string>(stringValueIn)) {}
+
+ explicit Private(int integerValueIn)
+ : type_(ParameterValue::Type::INTEGER), integerValue_(integerValueIn) {}
+
+ explicit Private(bool booleanValueIn)
+ : type_(ParameterValue::Type::BOOLEAN), booleanValue_(booleanValueIn) {}
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+ParameterValue::~ParameterValue() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+ParameterValue::ParameterValue(const common::Measure &measureIn)
+ : d(internal::make_unique<Private>(measureIn)) {}
+
+// ---------------------------------------------------------------------------
+
+ParameterValue::ParameterValue(const std::string &stringValueIn,
+ ParameterValue::Type typeIn)
+ : d(internal::make_unique<Private>(stringValueIn, typeIn)) {}
+
+// ---------------------------------------------------------------------------
+
+ParameterValue::ParameterValue(int integerValueIn)
+ : d(internal::make_unique<Private>(integerValueIn)) {}
+
+// ---------------------------------------------------------------------------
+
+ParameterValue::ParameterValue(bool booleanValueIn)
+ : d(internal::make_unique<Private>(booleanValueIn)) {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ParameterValue from a Measure (i.e. a value associated
+ * with a
+ * unit)
+ *
+ * @return a new ParameterValue.
+ */
+ParameterValueNNPtr ParameterValue::create(const common::Measure &measureIn) {
+ return ParameterValue::nn_make_shared<ParameterValue>(measureIn);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ParameterValue from a string value.
+ *
+ * @return a new ParameterValue.
+ */
+ParameterValueNNPtr ParameterValue::create(const char *stringValueIn) {
+ return ParameterValue::nn_make_shared<ParameterValue>(
+ std::string(stringValueIn), ParameterValue::Type::STRING);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ParameterValue from a string value.
+ *
+ * @return a new ParameterValue.
+ */
+ParameterValueNNPtr ParameterValue::create(const std::string &stringValueIn) {
+ return ParameterValue::nn_make_shared<ParameterValue>(
+ stringValueIn, ParameterValue::Type::STRING);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ParameterValue from a filename.
+ *
+ * @return a new ParameterValue.
+ */
+ParameterValueNNPtr
+ParameterValue::createFilename(const std::string &stringValueIn) {
+ return ParameterValue::nn_make_shared<ParameterValue>(
+ stringValueIn, ParameterValue::Type::FILENAME);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ParameterValue from a integer value.
+ *
+ * @return a new ParameterValue.
+ */
+ParameterValueNNPtr ParameterValue::create(int integerValueIn) {
+ return ParameterValue::nn_make_shared<ParameterValue>(integerValueIn);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ParameterValue from a boolean value.
+ *
+ * @return a new ParameterValue.
+ */
+ParameterValueNNPtr ParameterValue::create(bool booleanValueIn) {
+ return ParameterValue::nn_make_shared<ParameterValue>(booleanValueIn);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the type of a parameter value.
+ *
+ * @return the type.
+ */
+const ParameterValue::Type &ParameterValue::type() PROJ_CONST_DEFN {
+ return d->type_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the value as a Measure (assumes type() == Type::MEASURE)
+ * @return the value as a Measure.
+ */
+const common::Measure &ParameterValue::value() PROJ_CONST_DEFN {
+ return *d->measure_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the value as a string (assumes type() == Type::STRING)
+ * @return the value as a string.
+ */
+const std::string &ParameterValue::stringValue() PROJ_CONST_DEFN {
+ return *d->stringValue_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the value as a filename (assumes type() == Type::FILENAME)
+ * @return the value as a filename.
+ */
+const std::string &ParameterValue::valueFile() PROJ_CONST_DEFN {
+ return *d->stringValue_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the value as a integer (assumes type() == Type::INTEGER)
+ * @return the value as a integer.
+ */
+int ParameterValue::integerValue() PROJ_CONST_DEFN { return d->integerValue_; }
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the value as a boolean (assumes type() == Type::BOOLEAN)
+ * @return the value as a boolean.
+ */
+bool ParameterValue::booleanValue() PROJ_CONST_DEFN { return d->booleanValue_; }
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void ParameterValue::_exportToWKT(io::WKTFormatter *formatter) const {
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+
+ const auto &l_type = type();
+ const auto &l_value = value();
+ if (formatter->abridgedTransformation() && l_type == Type::MEASURE) {
+ const auto &unit = l_value.unit();
+ const auto &unitType = unit.type();
+ if (unitType == common::UnitOfMeasure::Type::LINEAR) {
+ formatter->add(l_value.getSIValue());
+ } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) {
+ formatter->add(
+ l_value.convertToUnit(common::UnitOfMeasure::ARC_SECOND));
+ } else if (unit == common::UnitOfMeasure::PARTS_PER_MILLION) {
+ formatter->add(1.0 + l_value.value() * 1e-6);
+ } else {
+ formatter->add(l_value.value());
+ }
+ } else if (l_type == Type::MEASURE) {
+ const auto &unit = l_value.unit();
+ if (isWKT2) {
+ formatter->add(l_value.value());
+ } else {
+ // In WKT1, as we don't output the natural unit, output to the
+ // registered linear / angular unit.
+ const auto &unitType = unit.type();
+ if (unitType == common::UnitOfMeasure::Type::LINEAR) {
+ formatter->add(
+ l_value.convertToUnit(*(formatter->axisLinearUnit())));
+ } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) {
+ formatter->add(
+ l_value.convertToUnit(*(formatter->axisAngularUnit())));
+ } else {
+ formatter->add(l_value.getSIValue());
+ }
+ }
+ if (isWKT2 && unit != common::UnitOfMeasure::NONE) {
+ if (!formatter->primeMeridianOrParameterUnitOmittedIfSameAsAxis() ||
+ (unit != common::UnitOfMeasure::SCALE_UNITY &&
+ unit != *(formatter->axisLinearUnit()) &&
+ unit != *(formatter->axisAngularUnit()))) {
+ unit._exportToWKT(formatter);
+ }
+ }
+ } else if (l_type == Type::STRING || l_type == Type::FILENAME) {
+ formatter->addQuotedString(stringValue());
+ } else if (l_type == Type::INTEGER) {
+ formatter->add(integerValue());
+ } else {
+ throw io::FormattingException("boolean parameter value not handled");
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool ParameterValue::_isEquivalentTo(
+ const util::IComparable *other,
+ util::IComparable::Criterion criterion) const {
+ auto otherPV = dynamic_cast<const ParameterValue *>(other);
+ if (otherPV == nullptr) {
+ return false;
+ }
+ if (type() != otherPV->type()) {
+ return false;
+ }
+ switch (type()) {
+ case Type::MEASURE: {
+ return value()._isEquivalentTo(otherPV->value(), criterion);
+ }
+
+ case Type::STRING:
+ case Type::FILENAME: {
+ return stringValue() == otherPV->stringValue();
+ }
+
+ case Type::INTEGER: {
+ return integerValue() == otherPV->integerValue();
+ }
+
+ case Type::BOOLEAN: {
+ return booleanValue() == otherPV->booleanValue();
+ }
+
+ default: {
+ assert(false);
+ break;
+ }
+ }
+ return true;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct Conversion::Private {};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+Conversion::Conversion(const OperationMethodNNPtr &methodIn,
+ const std::vector<GeneralParameterValueNNPtr> &values)
+ : SingleOperation(methodIn), d(nullptr) {
+ setParameterValues(values);
+}
+
+// ---------------------------------------------------------------------------
+
+Conversion::Conversion(const Conversion &other)
+ : CoordinateOperation(other), SingleOperation(other), d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+Conversion::~Conversion() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+ConversionNNPtr Conversion::shallowClone() const {
+ auto conv = Conversion::nn_make_shared<Conversion>(*this);
+ conv->assignSelf(conv);
+ conv->setCRSs(this, false);
+ return conv;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a Conversion from a vector of GeneralParameterValue.
+ *
+ * @param properties See \ref general_properties. At minimum the name should be
+ * defined.
+ * @param methodIn the operation method.
+ * @param values the values.
+ * @return a new Conversion.
+ * @throws InvalidOperation
+ */
+ConversionNNPtr Conversion::create(const util::PropertyMap &properties,
+ const OperationMethodNNPtr &methodIn,
+ const std::vector<GeneralParameterValueNNPtr>
+ &values) // throw InvalidOperation
+{
+ if (methodIn->parameters().size() != values.size()) {
+ throw InvalidOperation(
+ "Inconsistent number of parameters and parameter values");
+ }
+ auto conv = Conversion::nn_make_shared<Conversion>(methodIn, values);
+ conv->assignSelf(conv);
+ conv->setProperties(properties);
+ return conv;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a Conversion and its OperationMethod
+ *
+ * @param propertiesConversion See \ref general_properties of the conversion.
+ * At minimum the name should be defined.
+ * @param propertiesOperationMethod See \ref general_properties of the operation
+ * method. At minimum the name should be defined.
+ * @param parameters the operation parameters.
+ * @param values the operation values. Constraint:
+ * values.size() == parameters.size()
+ * @return a new Conversion.
+ * @throws InvalidOperation
+ */
+ConversionNNPtr Conversion::create(
+ const util::PropertyMap &propertiesConversion,
+ const util::PropertyMap &propertiesOperationMethod,
+ const std::vector<OperationParameterNNPtr> &parameters,
+ const std::vector<ParameterValueNNPtr> &values) // throw InvalidOperation
+{
+ OperationMethodNNPtr op(
+ OperationMethod::create(propertiesOperationMethod, parameters));
+
+ if (parameters.size() != values.size()) {
+ throw InvalidOperation(
+ "Inconsistent number of parameters and parameter values");
+ }
+ std::vector<GeneralParameterValueNNPtr> generalParameterValues;
+ generalParameterValues.reserve(values.size());
+ for (size_t i = 0; i < values.size(); i++) {
+ generalParameterValues.push_back(
+ OperationParameterValue::create(parameters[i], values[i]));
+ }
+ return create(propertiesConversion, op, generalParameterValues);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+static util::PropertyMap createMapNameEPSGCode(const std::string &name,
+ int code) {
+ return util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, name)
+ .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
+ .set(metadata::Identifier::CODE_KEY, code);
+}
+
+// ---------------------------------------------------------------------------
+
+static util::PropertyMap createMapNameEPSGCode(const char *name, int code) {
+ return util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, name)
+ .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
+ .set(metadata::Identifier::CODE_KEY, code);
+}
+
+// ---------------------------------------------------------------------------
+
+static util::PropertyMap createMethodMapNameEPSGCode(int code) {
+ const char *name = nullptr;
+ for (const auto &tuple : methodNameCodes) {
+ if (tuple.epsg_code == code) {
+ name = tuple.name;
+ break;
+ }
+ }
+ assert(name);
+ return createMapNameEPSGCode(name, code);
+}
+
+// ---------------------------------------------------------------------------
+
+static util::PropertyMap
+getUTMConversionProperty(const util::PropertyMap &properties, int zone,
+ bool north) {
+ if (properties.find(common::IdentifiedObject::NAME_KEY) ==
+ properties.end()) {
+ std::string conversionName("UTM zone ");
+ conversionName += toString(zone);
+ conversionName += (north ? 'N' : 'S');
+
+ return createMapNameEPSGCode(conversionName,
+ (north ? 16000 : 17000) + zone);
+ } else {
+ return properties;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static util::PropertyMap
+addDefaultNameIfNeeded(const util::PropertyMap &properties,
+ const std::string &defaultName) {
+ if (properties.find(common::IdentifiedObject::NAME_KEY) ==
+ properties.end()) {
+ return util::PropertyMap(properties)
+ .set(common::IdentifiedObject::NAME_KEY, defaultName);
+ } else {
+ return properties;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static ConversionNNPtr
+createConversion(const util::PropertyMap &properties,
+ const MethodMapping *mapping,
+ const std::vector<ParameterValueNNPtr> &values) {
+
+ std::vector<OperationParameterNNPtr> parameters;
+ for (int i = 0; mapping->params[i] != nullptr; i++) {
+ const auto *param = mapping->params[i];
+ auto paramProperties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, param->wkt2_name);
+ if (param->epsg_code != 0) {
+ paramProperties
+ .set(metadata::Identifier::CODESPACE_KEY,
+ metadata::Identifier::EPSG)
+ .set(metadata::Identifier::CODE_KEY, param->epsg_code);
+ }
+ auto parameter = OperationParameter::create(paramProperties);
+ parameters.push_back(parameter);
+ }
+
+ auto methodProperties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, mapping->wkt2_name);
+ if (mapping->epsg_code != 0) {
+ methodProperties
+ .set(metadata::Identifier::CODESPACE_KEY,
+ metadata::Identifier::EPSG)
+ .set(metadata::Identifier::CODE_KEY, mapping->epsg_code);
+ }
+ return Conversion::create(
+ addDefaultNameIfNeeded(properties, mapping->wkt2_name),
+ methodProperties, parameters, values);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+ConversionNNPtr
+Conversion::create(const util::PropertyMap &properties, int method_epsg_code,
+ const std::vector<ParameterValueNNPtr> &values) {
+ const MethodMapping *mapping = getMapping(method_epsg_code);
+ assert(mapping);
+ return createConversion(properties, mapping, values);
+}
+
+// ---------------------------------------------------------------------------
+
+ConversionNNPtr
+Conversion::create(const util::PropertyMap &properties,
+ const char *method_wkt2_name,
+ const std::vector<ParameterValueNNPtr> &values) {
+ const MethodMapping *mapping = getMapping(method_wkt2_name);
+ assert(mapping);
+ return createConversion(properties, mapping, values);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+struct VectorOfParameters : public std::vector<OperationParameterNNPtr> {
+ VectorOfParameters() : std::vector<OperationParameterNNPtr>() {}
+ explicit VectorOfParameters(
+ std::initializer_list<OperationParameterNNPtr> list)
+ : std::vector<OperationParameterNNPtr>(list) {}
+ VectorOfParameters(const VectorOfParameters &) = delete;
+
+ ~VectorOfParameters();
+};
+
+// This way, we disable inlining of destruction, and save a lot of space
+VectorOfParameters::~VectorOfParameters() = default;
+
+struct VectorOfValues : public std::vector<ParameterValueNNPtr> {
+ VectorOfValues() : std::vector<ParameterValueNNPtr>() {}
+ explicit VectorOfValues(std::initializer_list<ParameterValueNNPtr> list)
+ : std::vector<ParameterValueNNPtr>(list) {}
+
+ explicit VectorOfValues(std::initializer_list<common::Measure> list);
+ VectorOfValues(const VectorOfValues &) = delete;
+ VectorOfValues(VectorOfValues &&) = default;
+
+ ~VectorOfValues();
+};
+
+static std::vector<ParameterValueNNPtr> buildParameterValueFromMeasure(
+ const std::initializer_list<common::Measure> &list) {
+ std::vector<ParameterValueNNPtr> res;
+ for (const auto &v : list) {
+ res.emplace_back(ParameterValue::create(v));
+ }
+ return res;
+}
+
+VectorOfValues::VectorOfValues(std::initializer_list<common::Measure> list)
+ : std::vector<ParameterValueNNPtr>(buildParameterValueFromMeasure(list)) {}
+
+// This way, we disable inlining of destruction, and save a lot of space
+VectorOfValues::~VectorOfValues() = default;
+
+PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3) {
+ return VectorOfValues{ParameterValue::create(m1),
+ ParameterValue::create(m2),
+ ParameterValue::create(m3)};
+}
+
+PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3,
+ const common::Measure &m4) {
+ return VectorOfValues{
+ ParameterValue::create(m1), ParameterValue::create(m2),
+ ParameterValue::create(m3), ParameterValue::create(m4)};
+}
+
+PROJ_NO_INLINE static VectorOfValues createParams(const common::Measure &m1,
+ const common::Measure &m2,
+ const common::Measure &m3,
+ const common::Measure &m4,
+ const common::Measure &m5) {
+ return VectorOfValues{
+ ParameterValue::create(m1), ParameterValue::create(m2),
+ ParameterValue::create(m3), ParameterValue::create(m4),
+ ParameterValue::create(m5),
+ };
+}
+
+PROJ_NO_INLINE static VectorOfValues
+createParams(const common::Measure &m1, const common::Measure &m2,
+ const common::Measure &m3, const common::Measure &m4,
+ const common::Measure &m5, const common::Measure &m6) {
+ return VectorOfValues{
+ ParameterValue::create(m1), ParameterValue::create(m2),
+ ParameterValue::create(m3), ParameterValue::create(m4),
+ ParameterValue::create(m5), ParameterValue::create(m6),
+ };
+}
+
+PROJ_NO_INLINE static VectorOfValues
+createParams(const common::Measure &m1, const common::Measure &m2,
+ const common::Measure &m3, const common::Measure &m4,
+ const common::Measure &m5, const common::Measure &m6,
+ const common::Measure &m7) {
+ return VectorOfValues{
+ ParameterValue::create(m1), ParameterValue::create(m2),
+ ParameterValue::create(m3), ParameterValue::create(m4),
+ ParameterValue::create(m5), ParameterValue::create(m6),
+ ParameterValue::create(m7),
+ };
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a [Universal Transverse Mercator]
+ *(https://proj4.org/operations/projections/utm.html) conversion.
+ *
+ * UTM is a family of conversions, of EPSG codes from 16001 to 16060 for the
+ * northern hemisphere, and 17001 t 17060 for the southern hemisphere,
+ * based on the Transverse Mercator projection method.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param zone UTM zone number between 1 and 60.
+ * @param north true for UTM northern hemisphere, false for UTM southern
+ * hemisphere.
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createUTM(const util::PropertyMap &properties,
+ int zone, bool north) {
+ return create(
+ getUTMConversionProperty(properties, zone, north),
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR,
+ createParams(common::Angle(UTM_LATITUDE_OF_NATURAL_ORIGIN),
+ common::Angle(zone * 6.0 - 183.0),
+ common::Scale(UTM_SCALE_FACTOR),
+ common::Length(UTM_FALSE_EASTING),
+ common::Length(north ? UTM_NORTH_FALSE_NORTHING
+ : UTM_SOUTH_FALSE_NORTHING)));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Transverse Mercator]
+ *(https://proj4.org/operations/projections/tmerc.html) projection method.
+ *
+ * This method is defined as [EPSG:9807]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9807)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createTransverseMercator(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Gauss Schreiber Transverse
+ *Mercator]
+ *(https://proj4.org/operations/projections/gstmerc.html) projection method.
+ *
+ * This method is also known as Gauss-Laborde Reunion.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createGaussSchreiberTransverseMercator(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties,
+ PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Transverse Mercator South
+ *Orientated]
+ *(https://proj4.org/operations/projections/tmerc.html) projection method.
+ *
+ * This method is defined as [EPSG:9808]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9808)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createTransverseMercatorSouthOriented(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties,
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Two Point Equidistant]
+ *(https://proj4.org/operations/projections/tpeqd.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFirstPoint Latitude of first point.
+ * @param longitudeFirstPoint Longitude of first point.
+ * @param latitudeSecondPoint Latitude of second point.
+ * @param longitudeSeconPoint Longitude of second point.
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr
+Conversion::createTwoPointEquidistant(const util::PropertyMap &properties,
+ const common::Angle &latitudeFirstPoint,
+ const common::Angle &longitudeFirstPoint,
+ const common::Angle &latitudeSecondPoint,
+ const common::Angle &longitudeSeconPoint,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT,
+ createParams(latitudeFirstPoint, longitudeFirstPoint,
+ latitudeSecondPoint, longitudeSeconPoint,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the Tunisia Mapping Grid projection
+ * method.
+ *
+ * This method is defined as [EPSG:9816]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9816)
+ *
+ * \note There is currently no implementation of the method formulas in PROJ.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createTunisiaMappingGrid(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, EPSG_CODE_METHOD_TUNISIA_MAPPING_GRID,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Albers Conic Equal Area]
+ *(https://proj4.org/operations/projections/aea.html) projection method.
+ *
+ * This method is defined as [EPSG:9822]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9822)
+ *
+ * @note the order of arguments is conformant with the corresponding EPSG
+ * mode and different than OGRSpatialReference::setACEA() of GDAL &lt;= 2.3
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFalseOrigin See \ref latitude_false_origin
+ * @param longitudeFalseOrigin See \ref longitude_false_origin
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel
+ * @param latitudeSecondParallel See \ref latitude_second_std_parallel
+ * @param eastingFalseOrigin See \ref easting_false_origin
+ * @param northingFalseOrigin See \ref northing_false_origin
+ * @return a new Conversion.
+ */
+ConversionNNPtr
+Conversion::createAlbersEqualArea(const util::PropertyMap &properties,
+ const common::Angle &latitudeFalseOrigin,
+ const common::Angle &longitudeFalseOrigin,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &latitudeSecondParallel,
+ const common::Length &eastingFalseOrigin,
+ const common::Length &northingFalseOrigin) {
+ return create(properties, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA,
+ createParams(latitudeFalseOrigin, longitudeFalseOrigin,
+ latitudeFirstParallel, latitudeSecondParallel,
+ eastingFalseOrigin, northingFalseOrigin));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Lambert Conic Conformal 1SP]
+ *(https://proj4.org/operations/projections/lcc.html) projection method.
+ *
+ * This method is defined as [EPSG:9801]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9801)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLambertConicConformal_1SP(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Lambert Conic Conformal (2SP)]
+ *(https://proj4.org/operations/projections/lcc.html) projection method.
+ *
+ * This method is defined as [EPSG:9802]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9802)
+ *
+ * @note the order of arguments is conformant with the corresponding EPSG
+ * mode and different than OGRSpatialReference::setLCC() of GDAL &lt;= 2.3
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFalseOrigin See \ref latitude_false_origin
+ * @param longitudeFalseOrigin See \ref longitude_false_origin
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel
+ * @param latitudeSecondParallel See \ref latitude_second_std_parallel
+ * @param eastingFalseOrigin See \ref easting_false_origin
+ * @param northingFalseOrigin See \ref northing_false_origin
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLambertConicConformal_2SP(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFalseOrigin,
+ const common::Angle &longitudeFalseOrigin,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &latitudeSecondParallel,
+ const common::Length &eastingFalseOrigin,
+ const common::Length &northingFalseOrigin) {
+ return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP,
+ createParams(latitudeFalseOrigin, longitudeFalseOrigin,
+ latitudeFirstParallel, latitudeSecondParallel,
+ eastingFalseOrigin, northingFalseOrigin));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Lambert Conic Conformal (2SP
+ *Michigan)]
+ *(https://proj4.org/operations/projections/lcc.html) projection method.
+ *
+ * This method is defined as [EPSG:1051]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1051)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFalseOrigin See \ref latitude_false_origin
+ * @param longitudeFalseOrigin See \ref longitude_false_origin
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel
+ * @param latitudeSecondParallel See \ref latitude_second_std_parallel
+ * @param eastingFalseOrigin See \ref easting_false_origin
+ * @param northingFalseOrigin See \ref northing_false_origin
+ * @param ellipsoidScalingFactor Ellipsoid scaling factor.
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLambertConicConformal_2SP_Michigan(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFalseOrigin,
+ const common::Angle &longitudeFalseOrigin,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &latitudeSecondParallel,
+ const common::Length &eastingFalseOrigin,
+ const common::Length &northingFalseOrigin,
+ const common::Scale &ellipsoidScalingFactor) {
+ return create(properties,
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN,
+ createParams(latitudeFalseOrigin, longitudeFalseOrigin,
+ latitudeFirstParallel, latitudeSecondParallel,
+ eastingFalseOrigin, northingFalseOrigin,
+ ellipsoidScalingFactor));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Lambert Conic Conformal (2SP
+ *Belgium)]
+ *(https://proj4.org/operations/projections/lcc.html) projection method.
+ *
+ * This method is defined as [EPSG:9803]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9803)
+ *
+ * \warning The formulas used currently in PROJ are, incorrectly, the ones of
+ * the regular LCC_2SP method.
+ *
+ * @note the order of arguments is conformant with the corresponding EPSG
+ * mode and different than OGRSpatialReference::setLCCB() of GDAL &lt;= 2.3
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFalseOrigin See \ref latitude_false_origin
+ * @param longitudeFalseOrigin See \ref longitude_false_origin
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel
+ * @param latitudeSecondParallel See \ref latitude_second_std_parallel
+ * @param eastingFalseOrigin See \ref easting_false_origin
+ * @param northingFalseOrigin See \ref northing_false_origin
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLambertConicConformal_2SP_Belgium(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFalseOrigin,
+ const common::Angle &longitudeFalseOrigin,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &latitudeSecondParallel,
+ const common::Length &eastingFalseOrigin,
+ const common::Length &northingFalseOrigin) {
+
+ return create(properties,
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM,
+ createParams(latitudeFalseOrigin, longitudeFalseOrigin,
+ latitudeFirstParallel, latitudeSecondParallel,
+ eastingFalseOrigin, northingFalseOrigin));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Modified Azimuthal
+ *Equidistant]
+ *(https://proj4.org/operations/projections/aeqd.html) projection method.
+ *
+ * This method is defined as [EPSG:9832]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9832)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeNatOrigin See \ref center_latitude
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createAzimuthalEquidistant(
+ const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin,
+ const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT,
+ createParams(latitudeNatOrigin, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Guam Projection]
+ *(https://proj4.org/operations/projections/aeqd.html) projection method.
+ *
+ * This method is defined as [EPSG:9831]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9831)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ *is
+ * not provided, it is automatically set.
+ * @param latitudeNatOrigin See \ref center_latitude
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createGuamProjection(
+ const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin,
+ const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_GUAM_PROJECTION,
+ createParams(latitudeNatOrigin, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Bonne]
+ *(https://proj4.org/operations/projections/bonne.html) projection method.
+ *
+ * This method is defined as [EPSG:9827]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9827)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeNatOrigin See \ref center_latitude . PROJ calls its the
+ * standard parallel 1.
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createBonne(const util::PropertyMap &properties,
+ const common::Angle &latitudeNatOrigin,
+ const common::Angle &longitudeNatOrigin,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_BONNE,
+ createParams(latitudeNatOrigin, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Lambert Cylindrical Equal Area
+ *(Spherical)]
+ *(https://proj4.org/operations/projections/cea.html) projection method.
+ *
+ * This method is defined as [EPSG:9834]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9834)
+ *
+ * \warning The PROJ cea computation code would select the ellipsoidal form if
+ * a non-spherical ellipsoid is used for the base GeographicalCRS.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLambertCylindricalEqualAreaSpherical(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties,
+ EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL,
+ createParams(latitudeFirstParallel, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Lambert Cylindrical Equal Area
+ *(ellipsoidal form)]
+ *(https://proj4.org/operations/projections/cea.html) projection method.
+ *
+ * This method is defined as [EPSG:9835]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9835)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLambertCylindricalEqualArea(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA,
+ createParams(latitudeFirstParallel, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Cassini-Soldner]
+ * (https://proj4.org/operations/projections/cass.html) projection method.
+ *
+ * This method is defined as [EPSG:9806]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9806)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createCassiniSoldner(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, EPSG_CODE_METHOD_CASSINI_SOLDNER,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Equidistant Conic]
+ *(https://proj4.org/operations/projections/eqdc.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @note Although not found in EPSG, the order of arguments is conformant with
+ * the "spirit" of EPSG and different than OGRSpatialReference::setEC() of GDAL
+ *&lt;= 2.3 * @param properties See \ref general_properties of the conversion.
+ *If the name
+ * is not provided, it is automatically set.
+ *
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel
+ * @param latitudeSecondParallel See \ref latitude_second_std_parallel
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEquidistantConic(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Angle &latitudeFirstParallel,
+ const common::Angle &latitudeSecondParallel,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC,
+ createParams(centerLat, centerLong, latitudeFirstParallel,
+ latitudeSecondParallel, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Eckert I]
+ * (https://proj4.org/operations/projections/eck1.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEckertI(const util::PropertyMap &properties,
+ const common::Angle &centerLong,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_I,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Eckert II]
+ * (https://proj4.org/operations/projections/eck2.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEckertII(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_II,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Eckert III]
+ * (https://proj4.org/operations/projections/eck3.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEckertIII(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_III,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Eckert IV]
+ * (https://proj4.org/operations/projections/eck4.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEckertIV(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_IV,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Eckert V]
+ * (https://proj4.org/operations/projections/eck5.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEckertV(const util::PropertyMap &properties,
+ const common::Angle &centerLong,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_V,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Eckert VI]
+ * (https://proj4.org/operations/projections/eck6.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEckertVI(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_VI,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Equidistant Cylindrical]
+ *(https://proj4.org/operations/projections/eqc.html) projection method.
+ *
+ * This is also known as the Equirectangular method, and in the particular case
+ * where the latitude of first parallel is 0.
+ *
+ * This method is defined as [EPSG:1028]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1028)
+ *
+ * @note This is the equivalent OGRSpatialReference::SetEquirectangular2(
+ * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL &lt;= 2.3,
+ * where the lat_0 / center_latitude parameter is forced to 0.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEquidistantCylindrical(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL,
+ createParams(latitudeFirstParallel, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Equidistant Cylindrical
+ *(Spherical)]
+ *(https://proj4.org/operations/projections/eqc.html) projection method.
+ *
+ * This is also known as the Equirectangular method, and in the particular case
+ * where the latitude of first parallel is 0.
+ *
+ * This method is defined as [EPSG:1029]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1029)
+ *
+ * @note This is the equivalent OGRSpatialReference::SetEquirectangular2(
+ * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL &lt;= 2.3,
+ * where the lat_0 / center_latitude parameter is forced to 0.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEquidistantCylindricalSpherical(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties,
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL,
+ createParams(latitudeFirstParallel, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Gall (Stereographic)]
+ * (https://proj4.org/operations/projections/gall.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createGall(const util::PropertyMap &properties,
+ const common::Angle &centerLong,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Goode Homolosine]
+ * (https://proj4.org/operations/projections/goode.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createGoodeHomolosine(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Interrupted Goode Homolosine]
+ * (https://proj4.org/operations/projections/igh.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @note OGRSpatialReference::SetIGH() of GDAL &lt;= 2.3 assumes the 3
+ * projection
+ * parameters to be zero and this is the nominal case.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createInterruptedGoodeHomolosine(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties,
+ PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Geostationary Satellite View]
+ * (https://proj4.org/operations/projections/geos.html) projection method,
+ * with the sweep angle axis of the viewing instrument being x
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param height Height of the view point above the Earth.
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createGeostationarySatelliteSweepX(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &height, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X,
+ createParams(centerLong, height, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Geostationary Satellite View]
+ * (https://proj4.org/operations/projections/geos.html) projection method,
+ * with the sweep angle axis of the viewing instrument being y.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param height Height of the view point above the Earth.
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createGeostationarySatelliteSweepY(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &height, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y,
+ createParams(centerLong, height, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Gnomonic]
+ *(https://proj4.org/operations/projections/gnom.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createGnomonic(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, PROJ_WKT2_NAME_METHOD_GNOMONIC,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Hotine Oblique Mercator
+ *(Variant A)]
+ *(https://proj4.org/operations/projections/omerc.html) projection method
+ *
+ * This is the variant with the no_uoff parameter, which corresponds to
+ * GDAL &gt;=2.3 Hotine_Oblique_Mercator projection.
+ * In this variant, the false grid coordinates are
+ * defined at the intersection of the initial line and the aposphere (the
+ * equator on one of the intermediate surfaces inherent in the method), that is
+ * at the natural origin of the coordinate system).
+ *
+ * This method is defined as [EPSG:9812]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9812)
+ *
+ * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid =
+ *90deg,
+ * this maps to the [Swiss Oblique Mercator]
+ *(https://proj4.org/operations/projections/somerc.html) formulas.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeProjectionCentre See \ref latitude_projection_centre
+ * @param longitudeProjectionCentre See \ref longitude_projection_centre
+ * @param azimuthInitialLine See \ref azimuth_initial_line
+ * @param angleFromRectifiedToSkrewGrid See
+ * \ref angle_from_recitfied_to_skrew_grid
+ * @param scale See \ref scale_factor_initial_line
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createHotineObliqueMercatorVariantA(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeProjectionCentre,
+ const common::Angle &longitudeProjectionCentre,
+ const common::Angle &azimuthInitialLine,
+ const common::Angle &angleFromRectifiedToSkrewGrid,
+ const common::Scale &scale, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A,
+ createParams(latitudeProjectionCentre, longitudeProjectionCentre,
+ azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Hotine Oblique Mercator
+ *(Variant B)]
+ *(https://proj4.org/operations/projections/omerc.html) projection method
+ *
+ * This is the variant without the no_uoff parameter, which corresponds to
+ * GDAL &gt;=2.3 Hotine_Oblique_Mercator_Azimuth_Center projection.
+ * In this variant, the false grid coordinates are defined at the projection
+ *centre.
+ *
+ * This method is defined as [EPSG:9815]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9815)
+ *
+ * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid =
+ *90deg,
+ * this maps to the [Swiss Oblique Mercator]
+ *(https://proj4.org/operations/projections/somerc.html) formulas.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeProjectionCentre See \ref latitude_projection_centre
+ * @param longitudeProjectionCentre See \ref longitude_projection_centre
+ * @param azimuthInitialLine See \ref azimuth_initial_line
+ * @param angleFromRectifiedToSkrewGrid See
+ * \ref angle_from_recitfied_to_skrew_grid
+ * @param scale See \ref scale_factor_initial_line
+ * @param eastingProjectionCentre See \ref easting_projection_centre
+ * @param northingProjectionCentre See \ref northing_projection_centre
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createHotineObliqueMercatorVariantB(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeProjectionCentre,
+ const common::Angle &longitudeProjectionCentre,
+ const common::Angle &azimuthInitialLine,
+ const common::Angle &angleFromRectifiedToSkrewGrid,
+ const common::Scale &scale, const common::Length &eastingProjectionCentre,
+ const common::Length &northingProjectionCentre) {
+ return create(
+ properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B,
+ createParams(latitudeProjectionCentre, longitudeProjectionCentre,
+ azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale,
+ eastingProjectionCentre, northingProjectionCentre));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Hotine Oblique Mercator Two
+ *Point Natural Origin]
+ *(https://proj4.org/operations/projections/omerc.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeProjectionCentre See \ref latitude_projection_centre
+ * @param latitudePoint1 Latitude of point 1.
+ * @param longitudePoint1 Latitude of point 1.
+ * @param latitudePoint2 Latitude of point 2.
+ * @param longitudePoint2 Longitude of point 2.
+ * @param scale See \ref scale_factor_initial_line
+ * @param eastingProjectionCentre See \ref easting_projection_centre
+ * @param northingProjectionCentre See \ref northing_projection_centre
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeProjectionCentre,
+ const common::Angle &latitudePoint1, const common::Angle &longitudePoint1,
+ const common::Angle &latitudePoint2, const common::Angle &longitudePoint2,
+ const common::Scale &scale, const common::Length &eastingProjectionCentre,
+ const common::Length &northingProjectionCentre) {
+ return create(
+ properties,
+ PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN,
+ {
+ ParameterValue::create(latitudeProjectionCentre),
+ ParameterValue::create(latitudePoint1),
+ ParameterValue::create(longitudePoint1),
+ ParameterValue::create(latitudePoint2),
+ ParameterValue::create(longitudePoint2),
+ ParameterValue::create(scale),
+ ParameterValue::create(eastingProjectionCentre),
+ ParameterValue::create(northingProjectionCentre),
+ });
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [International Map of the World
+ *Polyconic]
+ *(https://proj4.org/operations/projections/imw_p.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @note the order of arguments is conformant with the corresponding EPSG
+ * mode and different than OGRSpatialReference::SetIWMPolyconic() of GDAL &lt;=
+ *2.3
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel
+ * @param latitudeSecondParallel See \ref latitude_second_std_parallel
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createInternationalMapWorldPolyconic(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Angle &latitudeFirstParallel,
+ const common::Angle &latitudeSecondParallel,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC,
+ createParams(centerLong, latitudeFirstParallel,
+ latitudeSecondParallel, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Krovak (north oriented)]
+ *(https://proj4.org/operations/projections/krovak.html) projection method.
+ *
+ * This method is defined as [EPSG:1041]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1041)
+ *
+ * The coordinates are returned in the "GIS friendly" order: easting, northing.
+ * This method is similar to createKrovak(), except that the later returns
+ * projected values as southing, westing, where
+ * southing(Krovak) = -northing(Krovak_North) and
+ * westing(Krovak) = -easting(Krovak_North).
+ *
+ * @note The current PROJ implementation of Krovak hard-codes
+ * colatitudeConeAxis = 30deg17'17.30311"
+ * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for
+ * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221).
+ * It also hard-codes the parameters of the Bessel ellipsoid typically used for
+ * Krovak.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeProjectionCentre See \ref latitude_projection_centre
+ * @param longitudeOfOrigin See \ref longitude_of_origin
+ * @param colatitudeConeAxis See \ref colatitude_cone_axis
+ * @param latitudePseudoStandardParallel See \ref
+ *latitude_pseudo_standard_parallel
+ * @param scaleFactorPseudoStandardParallel See \ref
+ *scale_factor_pseudo_standard_parallel
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createKrovakNorthOriented(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeProjectionCentre,
+ const common::Angle &longitudeOfOrigin,
+ const common::Angle &colatitudeConeAxis,
+ const common::Angle &latitudePseudoStandardParallel,
+ const common::Scale &scaleFactorPseudoStandardParallel,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED,
+ createParams(latitudeProjectionCentre, longitudeOfOrigin,
+ colatitudeConeAxis,
+ latitudePseudoStandardParallel,
+ scaleFactorPseudoStandardParallel, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Krovak]
+ *(https://proj4.org/operations/projections/krovak.html) projection method.
+ *
+ * This method is defined as [EPSG:9819]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9819)
+ *
+ * The coordinates are returned in the historical order: southing, westing
+ * This method is similar to createKrovakNorthOriented(), except that the later
+ *returns
+ * projected values as easting, northing, where
+ * easting(Krovak_North) = -westing(Krovak) and
+ * northing(Krovak_North) = -southing(Krovak).
+ *
+ * @note The current PROJ implementation of Krovak hard-codes
+ * colatitudeConeAxis = 30deg17'17.30311"
+ * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for
+ * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221).
+ * It also hard-codes the parameters of the Bessel ellipsoid typically used for
+ * Krovak.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeProjectionCentre See \ref latitude_projection_centre
+ * @param longitudeOfOrigin See \ref longitude_of_origin
+ * @param colatitudeConeAxis See \ref colatitude_cone_axis
+ * @param latitudePseudoStandardParallel See \ref
+ *latitude_pseudo_standard_parallel
+ * @param scaleFactorPseudoStandardParallel See \ref
+ *scale_factor_pseudo_standard_parallel
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr
+Conversion::createKrovak(const util::PropertyMap &properties,
+ const common::Angle &latitudeProjectionCentre,
+ const common::Angle &longitudeOfOrigin,
+ const common::Angle &colatitudeConeAxis,
+ const common::Angle &latitudePseudoStandardParallel,
+ const common::Scale &scaleFactorPseudoStandardParallel,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_KROVAK,
+ createParams(latitudeProjectionCentre, longitudeOfOrigin,
+ colatitudeConeAxis,
+ latitudePseudoStandardParallel,
+ scaleFactorPseudoStandardParallel, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Lambert Azimuthal Equal Area]
+ *(https://proj4.org/operations/projections/laea.html) projection method.
+ *
+ * This method is defined as [EPSG:9820]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9820)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeNatOrigin See \ref center_latitude
+ * @param longitudeNatOrigin See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createLambertAzimuthalEqualArea(
+ const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin,
+ const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA,
+ createParams(latitudeNatOrigin, longitudeNatOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Miller Cylindrical]
+ *(https://proj4.org/operations/projections/mill.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createMillerCylindrical(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Mercator]
+ *(https://proj4.org/operations/projections/merc.html) projection method.
+ *
+ * This is the variant, also known as Mercator (1SP), defined with the scale
+ * factor. Note that latitude of natural origin (centerLat) is a parameter,
+ * but unused in the transformation formulas.
+ *
+ * This method is defined as [EPSG:9804]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9804)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude . Should be 0.
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createMercatorVariantA(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_A,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Mercator]
+ *(https://proj4.org/operations/projections/merc.html) projection method.
+ *
+ * This is the variant, also known as Mercator (2SP), defined with the latitude
+ * of the first standard parallel (the second standard parallel is implicitly
+ * the opposite value). The latitude of natural origin is fixed to zero.
+ *
+ * This method is defined as [EPSG:9805]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9805)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeFirstParallel See \ref latitude_first_std_parallel
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createMercatorVariantB(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeFirstParallel, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_B,
+ createParams(latitudeFirstParallel, centerLong, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Popular Visualisation Pseudo
+ *Mercator]
+ *(https://proj4.org/operations/projections/webmerc.html) projection method.
+ *
+ * Also known as WebMercator. Mostly/only used for Projected CRS EPSG:3857
+ * (WGS 84 / Pseudo-Mercator)
+ *
+ * This method is defined as [EPSG:1024]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1024)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude . Usually 0
+ * @param centerLong See \ref center_longitude . Usually 0
+ * @param falseEasting See \ref false_easting . Usually 0
+ * @param falseNorthing See \ref false_northing . Usually 0
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createPopularVisualisationPseudoMercator(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Mollweide]
+ * (https://proj4.org/operations/projections/moll.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createMollweide(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_MOLLWEIDE,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [New Zealand Map Grid]
+ * (https://proj4.org/operations/projections/nzmg.html) projection method.
+ *
+ * This method is defined as [EPSG:9811]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9811)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createNewZealandMappingGrid(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, EPSG_CODE_METHOD_NZMG,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Oblique Stereographic
+ *(Alternative)]
+ *(https://proj4.org/operations/projections/sterea.html) projection method.
+ *
+ * This method is defined as [EPSG:9809]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9809)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createObliqueStereographic(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Orthographic]
+ *(https://proj4.org/operations/projections/ortho.html) projection method.
+ *
+ * This method is defined as [EPSG:9840]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9840)
+ *
+ * \note At the time of writing, PROJ only implements the spherical formulation
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createOrthographic(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, EPSG_CODE_METHOD_ORTHOGRAPHIC,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [American Polyconic]
+ *(https://proj4.org/operations/projections/poly.html) projection method.
+ *
+ * This method is defined as [EPSG:9818]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9818)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createAmericanPolyconic(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, EPSG_CODE_METHOD_AMERICAN_POLYCONIC,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Polar Stereographic (Variant
+ *A)]
+ *(https://proj4.org/operations/projections/stere.html) projection method.
+ *
+ * This method is defined as [EPSG:9810]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9810)
+ *
+ * This is the variant of polar stereographic defined with a scale factor.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude . Should be 90 deg ou -90 deg.
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createPolarStereographicVariantA(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Polar Stereographic (Variant
+ *B)]
+ *(https://proj4.org/operations/projections/stere.html) projection method.
+ *
+ * This method is defined as [EPSG:9829]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9829)
+ *
+ * This is the variant of polar stereographic defined with a latitude of
+ * standard parallel.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeStandardParallel See \ref latitude_std_parallel
+ * @param longitudeOfOrigin See \ref longitude_of_origin
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createPolarStereographicVariantB(
+ const util::PropertyMap &properties,
+ const common::Angle &latitudeStandardParallel,
+ const common::Angle &longitudeOfOrigin, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B,
+ createParams(latitudeStandardParallel, longitudeOfOrigin,
+ falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Robinson]
+ * (https://proj4.org/operations/projections/robin.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createRobinson(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_ROBINSON,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Sinusoidal]
+ * (https://proj4.org/operations/projections/sinu.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createSinusoidal(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_SINUSOIDAL,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Stereographic]
+ *(https://proj4.org/operations/projections/stere.html) projection method.
+ *
+ * There is no equivalent in EPSG. This method implements the original "Oblique
+ * Stereographic" method described in "Snyder's Map Projections - A Working
+ *manual",
+ * which is different from the "Oblique Stereographic (alternative") method
+ * implemented in createObliqueStereographic().
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param scale See \ref scale
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createStereographic(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Scale &scale,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC,
+ createParams(centerLat, centerLong, scale, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Van der Grinten]
+ * (https://proj4.org/operations/projections/vandg.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createVanDerGrinten(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Wagner I]
+ * (https://proj4.org/operations/projections/wag1.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createWagnerI(const util::PropertyMap &properties,
+ const common::Angle &centerLong,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_I,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Wagner II]
+ * (https://proj4.org/operations/projections/wag2.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createWagnerII(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_II,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Wagner III]
+ * (https://proj4.org/operations/projections/wag3.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param latitudeTrueScale Latitude of true scale.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createWagnerIII(
+ const util::PropertyMap &properties, const common::Angle &latitudeTrueScale,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_III,
+ createParams(latitudeTrueScale, centerLong, falseEasting,
+ falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Wagner IV]
+ * (https://proj4.org/operations/projections/wag4.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createWagnerIV(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_IV,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Wagner V]
+ * (https://proj4.org/operations/projections/wag5.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createWagnerV(const util::PropertyMap &properties,
+ const common::Angle &centerLong,
+ const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_V,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Wagner VI]
+ * (https://proj4.org/operations/projections/wag6.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createWagnerVI(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VI,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Wagner VII]
+ * (https://proj4.org/operations/projections/wag7.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createWagnerVII(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VII,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Quadrilateralized Spherical
+ *Cube]
+ *(https://proj4.org/operations/projections/qsc.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLat See \ref center_latitude
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createQuadrilateralizedSphericalCube(
+ const util::PropertyMap &properties, const common::Angle &centerLat,
+ const common::Angle &centerLong, const common::Length &falseEasting,
+ const common::Length &falseNorthing) {
+ return create(
+ properties, PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE,
+ createParams(centerLat, centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Spherical Cross-Track Height]
+ *(https://proj4.org/operations/projections/sch.html) projection method.
+ *
+ * There is no equivalent in EPSG.
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param pegPointLat Peg point latitude.
+ * @param pegPointLong Peg point longitude.
+ * @param pegPointHeading Peg point heading.
+ * @param pegPointHeight Peg point height.
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createSphericalCrossTrackHeight(
+ const util::PropertyMap &properties, const common::Angle &pegPointLat,
+ const common::Angle &pegPointLong, const common::Angle &pegPointHeading,
+ const common::Length &pegPointHeight) {
+ return create(properties,
+ PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT,
+ createParams(pegPointLat, pegPointLong, pegPointHeading,
+ pegPointHeight));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the [Equal Earth]
+ * (https://proj4.org/operations/projections/eqearth.html) projection method.
+ *
+ * This method is defined as [EPSG:1078]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1078)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param centerLong See \ref center_longitude
+ * @param falseEasting See \ref false_easting
+ * @param falseNorthing See \ref false_northing
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createEqualEarth(
+ const util::PropertyMap &properties, const common::Angle &centerLong,
+ const common::Length &falseEasting, const common::Length &falseNorthing) {
+ return create(properties, EPSG_CODE_METHOD_EQUAL_EARTH,
+ createParams(centerLong, falseEasting, falseNorthing));
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
+ const char *name = OperationParameter::getNameForEPSGCode(code);
+ assert(name);
+ return OperationParameter::create(createMapNameEPSGCode(name, code));
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the Change of Vertical Unit
+ * method.
+ *
+ * This method is defined as [EPSG:1069]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1069)
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @param factor Conversion factor
+ * @return a new Conversion.
+ */
+ConversionNNPtr
+Conversion::createChangeVerticalUnit(const util::PropertyMap &properties,
+ const common::Scale &factor) {
+ return create(properties, createMethodMapNameEPSGCode(
+ EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR),
+ },
+ VectorOfValues{
+ factor,
+ });
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the Axis order reversal method
+ *
+ * This swaps the longitude, latitude axis.
+ *
+ * This method is defined as [EPSG:9843]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9843),
+ * or for 3D as [EPSG:9844]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9844)
+ *
+ * @param is3D Whether this should apply on 3D geographicCRS
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::createAxisOrderReversal(bool is3D) {
+ if (is3D) {
+ return create(createMapNameEPSGCode(
+ "axis order change (geographic3D horizontal)", 15499),
+ createMethodMapNameEPSGCode(
+ EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D),
+ {}, {});
+ } else {
+ return create(createMapNameEPSGCode("axis order change (2D)", 15498),
+ createMethodMapNameEPSGCode(
+ EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D),
+ {}, {});
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a conversion based on the Geographic/Geocentric method.
+ *
+ * This method is defined as [EPSG:9602]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9602),
+ *
+ * @param properties See \ref general_properties of the conversion. If the name
+ * is not provided, it is automatically set.
+ * @return a new Conversion.
+ */
+ConversionNNPtr
+Conversion::createGeographicGeocentric(const util::PropertyMap &properties) {
+ return create(properties, createMethodMapNameEPSGCode(
+ EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC),
+ {}, {});
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static util::PropertyMap &addDomains(util::PropertyMap &map,
+ const common::ObjectUsage *obj) {
+
+ auto ar = util::ArrayOfBaseObject::create();
+ for (const auto &domain : obj->domains()) {
+ ar->add(domain);
+ }
+ if (!ar->empty()) {
+ map.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, ar);
+ }
+ return map;
+}
+
+// ---------------------------------------------------------------------------
+
+static void addModifiedIdentifier(util::PropertyMap &map,
+ const common::IdentifiedObject *obj,
+ bool inverse, bool derivedFrom) {
+ // If original operation is AUTH:CODE, then assign INVERSE(AUTH):CODE
+ // as identifier.
+
+ auto ar = util::ArrayOfBaseObject::create();
+ for (const auto &idSrc : obj->identifiers()) {
+ auto authName = *(idSrc->codeSpace());
+ const auto &srcCode = idSrc->code();
+ if (derivedFrom) {
+ authName = concat("DERIVED_FROM(", authName, ")");
+ }
+ if (inverse) {
+ if (starts_with(authName, "INVERSE(") && authName.back() == ')') {
+ authName = authName.substr(strlen("INVERSE("));
+ authName.resize(authName.size() - 1);
+ } else {
+ authName = concat("INVERSE(", authName, ")");
+ }
+ }
+ auto idsProp = util::PropertyMap().set(
+ metadata::Identifier::CODESPACE_KEY, authName);
+ ar->add(metadata::Identifier::create(srcCode, idsProp));
+ }
+ if (!ar->empty()) {
+ map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static util::PropertyMap
+createPropertiesForInverse(const OperationMethodNNPtr &method) {
+ util::PropertyMap map;
+
+ const std::string &forwardName = method->nameStr();
+ if (!forwardName.empty()) {
+ if (starts_with(forwardName, INVERSE_OF)) {
+ map.set(common::IdentifiedObject::NAME_KEY,
+ forwardName.substr(INVERSE_OF.size()));
+ } else {
+ map.set(common::IdentifiedObject::NAME_KEY,
+ INVERSE_OF + forwardName);
+ }
+ }
+
+ addModifiedIdentifier(map, method.get(), true, false);
+
+ return map;
+}
+
+// ---------------------------------------------------------------------------
+
+InverseConversion::InverseConversion(const ConversionNNPtr &forward)
+ : Conversion(
+ OperationMethod::create(createPropertiesForInverse(forward->method()),
+ forward->method()->parameters()),
+ forward->parameterValues()),
+ InverseCoordinateOperation(forward, true) {
+ setPropertiesFromForward();
+}
+
+// ---------------------------------------------------------------------------
+
+InverseConversion::~InverseConversion() = default;
+
+// ---------------------------------------------------------------------------
+
+ConversionNNPtr InverseConversion::inverseAsConversion() const {
+ return NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<Conversion>(forwardOperation_));
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr
+InverseConversion::create(const ConversionNNPtr &forward) {
+ auto conv = util::nn_make_shared<InverseConversion>(forward);
+ conv->assignSelf(conv);
+ return conv;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static bool isAxisOrderReversal2D(int methodEPSGCode) {
+ return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D;
+}
+
+static bool isAxisOrderReversal3D(int methodEPSGCode) {
+ return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D;
+}
+
+bool isAxisOrderReversal(int methodEPSGCode) {
+ return isAxisOrderReversal2D(methodEPSGCode) ||
+ isAxisOrderReversal3D(methodEPSGCode);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr Conversion::inverse() const {
+ const int methodEPSGCode = method()->getEPSGCode();
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
+ const double convFactor = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
+ auto conv = createChangeVerticalUnit(
+ createPropertiesForInverse(this, false, false),
+ common::Scale(1.0 / convFactor));
+ conv->setCRSs(this, true);
+ return conv;
+ }
+
+ const bool l_isAxisOrderReversal2D = isAxisOrderReversal2D(methodEPSGCode);
+ const bool l_isAxisOrderReversal3D = isAxisOrderReversal3D(methodEPSGCode);
+ if (l_isAxisOrderReversal2D || l_isAxisOrderReversal3D) {
+ auto conv = createAxisOrderReversal(l_isAxisOrderReversal3D);
+ conv->setCRSs(this, true);
+ return conv;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) {
+
+ auto conv = createGeographicGeocentric(
+ createPropertiesForInverse(this, false, false));
+ conv->setCRSs(this, true);
+ return conv;
+ }
+
+ return InverseConversion::create(NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<Conversion>(shared_from_this())));
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static double msfn(double phi, double e2) {
+ const double sinphi = std::sin(phi);
+ const double cosphi = std::cos(phi);
+ return pj_msfn(sinphi, cosphi, e2);
+}
+
+// ---------------------------------------------------------------------------
+
+static double tsfn(double phi, double ec) {
+ const double sinphi = std::sin(phi);
+ return pj_tsfn(phi, sinphi, ec);
+}
+
+// ---------------------------------------------------------------------------
+
+// Function whose zeroes are the sin of the standard parallels of LCC_2SP
+static double lcc_1sp_to_2sp_f(double sinphi, double K, double ec, double n) {
+ const double x = sinphi;
+ const double ecx = ec * x;
+ return (1 - x * x) / (1 - ecx * ecx) -
+ K * K * std::pow((1.0 - x) / (1.0 + x) *
+ std::pow((1.0 + ecx) / (1.0 - ecx), ec),
+ n);
+}
+
+// ---------------------------------------------------------------------------
+
+// Find the sin of the standard parallels of LCC_2SP
+static double find_zero_lcc_1sp_to_2sp_f(double sinphi0, bool bNorth, double K,
+ double ec) {
+ double a, b;
+ double f_a;
+ if (bNorth) {
+ // Look for zero above phi0
+ a = sinphi0;
+ b = 1.0; // sin(North pole)
+ f_a = 1.0; // some positive value, but we only care about the sign
+ } else {
+ // Look for zero below phi0
+ a = -1.0; // sin(South pole)
+ b = sinphi0;
+ f_a = -1.0; // minus infinity in fact, but we only care about the sign
+ }
+ // We use dichotomy search. lcc_1sp_to_2sp_f() is positive at sinphi_init,
+ // has a zero in ]-1,sinphi0[ and ]sinphi0,1[ ranges
+ for (int N = 0; N < 100; N++) {
+ double c = (a + b) / 2;
+ double f_c = lcc_1sp_to_2sp_f(c, K, ec, sinphi0);
+ if (f_c == 0.0 || (b - a) < 1e-18) {
+ return c;
+ }
+ if ((f_c > 0 && f_a > 0) || (f_c < 0 && f_a < 0)) {
+ a = c;
+ f_a = f_c;
+ } else {
+ b = c;
+ }
+ }
+ return (a + b) / 2;
+}
+
+static inline double DegToRad(double x) { return x / 180.0 * M_PI; }
+static inline double RadToDeg(double x) { return x / M_PI * 180.0; }
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/**
+ * \brief Return an equivalent projection.
+ *
+ * Currently implemented:
+ * <ul>
+ * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP) to
+ * EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP)</li>
+ * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP) to
+ * EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP)</li>
+ * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP to
+ * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP</li>
+ * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP to
+ * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP</li>
+ * </ul>
+ *
+ * @param targetEPSGCode EPSG code of the target method.
+ * @return new conversion, or nullptr
+ */
+ConversionPtr Conversion::convertToOtherMethod(int targetEPSGCode) const {
+ const int current_epsg_code = method()->getEPSGCode();
+ if (current_epsg_code == targetEPSGCode) {
+ return util::nn_dynamic_pointer_cast<Conversion>(shared_from_this());
+ }
+
+ auto geogCRS = dynamic_cast<crs::GeodeticCRS *>(sourceCRS().get());
+ if (!geogCRS) {
+ return nullptr;
+ }
+
+ const double e2 = geogCRS->ellipsoid()->squaredEccentricity();
+ if (e2 < 0) {
+ return nullptr;
+ }
+
+ if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_A &&
+ targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
+ parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) {
+ const double k0 = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
+ if (!(k0 > 0 && k0 <= 1.0 + 1e-10))
+ return nullptr;
+ const double dfStdP1Lat =
+ (k0 >= 1.0)
+ ? 0.0
+ : std::acos(std::sqrt((1.0 - e2) / ((1.0 / (k0 * k0)) - e2)));
+ auto latitudeFirstParallel = common::Angle(
+ common::Angle(dfStdP1Lat, common::UnitOfMeasure::RADIAN)
+ .convertToUnit(common::UnitOfMeasure::DEGREE),
+ common::UnitOfMeasure::DEGREE);
+ auto conv = createMercatorVariantB(
+ util::PropertyMap(), latitudeFirstParallel,
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
+ conv->setCRSs(this, false);
+ return conv;
+ }
+
+ if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
+ targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
+ const double phi1 = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL);
+ if (!(std::fabs(phi1) < M_PI / 2))
+ return nullptr;
+ const double k0 = msfn(phi1, e2);
+ auto conv = createMercatorVariantA(
+ util::PropertyMap(),
+ common::Angle(0.0, common::UnitOfMeasure::DEGREE),
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ common::Scale(k0, common::UnitOfMeasure::SCALE_UNITY),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
+ conv->setCRSs(this, false);
+ return conv;
+ }
+
+ if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
+ targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
+ // Notations m0, t0, n, m1, t1, F are those of the EPSG guidance
+ // "1.3.1.1 Lambert Conic Conformal (2SP)" and
+ // "1.3.1.2 Lambert Conic Conformal (1SP)" and
+ // or Snyder pages 106-109
+ auto latitudeOfOrigin = common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN));
+ const double phi0 = latitudeOfOrigin.getSIValue();
+ const double k0 = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
+ if (!(std::fabs(phi0) < M_PI / 2))
+ return nullptr;
+ if (!(k0 > 0 && k0 <= 1.0 + 1e-10))
+ return nullptr;
+ const double ec = std::sqrt(e2);
+ const double m0 = msfn(phi0, e2);
+ const double t0 = tsfn(phi0, ec);
+ const double n = sin(phi0);
+ if (std::fabs(n) < 1e-10)
+ return nullptr;
+ if (fabs(k0 - 1.0) <= 1e-10) {
+ auto conv = createLambertConicConformal_2SP(
+ util::PropertyMap(), latitudeOfOrigin,
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ latitudeOfOrigin, latitudeOfOrigin,
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
+ conv->setCRSs(this, false);
+ return conv;
+ } else {
+ const double K = k0 * m0 / std::pow(t0, n);
+ const double phi1 =
+ std::asin(find_zero_lcc_1sp_to_2sp_f(n, true, K, ec));
+ const double phi2 =
+ std::asin(find_zero_lcc_1sp_to_2sp_f(n, false, K, ec));
+ double phi1Deg = RadToDeg(phi1);
+ double phi2Deg = RadToDeg(phi2);
+
+ // Try to round to hundreth of degree if very close to it
+ if (std::fabs(phi1Deg * 1000 - std::floor(phi1Deg * 1000 + 0.5)) <
+ 1e-8)
+ phi1Deg = floor(phi1Deg * 1000 + 0.5) / 1000;
+ if (std::fabs(phi2Deg * 1000 - std::floor(phi2Deg * 1000 + 0.5)) <
+ 1e-8)
+ phi2Deg = std::floor(phi2Deg * 1000 + 0.5) / 1000;
+
+ // The following improvement is too turn the LCC1SP equivalent of
+ // EPSG:2154 to the real LCC2SP
+ // If the computed latitude of origin is close to .0 or .5 degrees
+ // then check if rounding it to it will get a false northing
+ // close to an integer
+ const double FN =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
+ const double latitudeOfOriginDeg =
+ latitudeOfOrigin.convertToUnit(common::UnitOfMeasure::DEGREE);
+ if (std::fabs(latitudeOfOriginDeg * 2 -
+ std::floor(latitudeOfOriginDeg * 2 + 0.5)) < 0.2) {
+ const double dfRoundedLatOfOrig =
+ std::floor(latitudeOfOriginDeg * 2 + 0.5) / 2;
+ const double m1 = msfn(phi1, e2);
+ const double t1 = tsfn(phi1, ec);
+ const double F = m1 / (n * std::pow(t1, n));
+ const double a =
+ geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
+ const double tRoundedLatOfOrig =
+ tsfn(DegToRad(dfRoundedLatOfOrig), ec);
+ const double FN_correction =
+ a * F * (std::pow(tRoundedLatOfOrig, n) - std::pow(t0, n));
+ const double FN_corrected = FN - FN_correction;
+ const double FN_corrected_rounded =
+ std::floor(FN_corrected + 0.5);
+ if (std::fabs(FN_corrected - FN_corrected_rounded) < 1e-8) {
+ auto conv = createLambertConicConformal_2SP(
+ util::PropertyMap(),
+ common::Angle(dfRoundedLatOfOrig,
+ common::UnitOfMeasure::DEGREE),
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE),
+ common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE),
+ common::Length(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(FN_corrected_rounded));
+ conv->setCRSs(this, false);
+ return conv;
+ }
+ }
+
+ auto conv = createLambertConicConformal_2SP(
+ util::PropertyMap(), latitudeOfOrigin,
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
+ common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE),
+ common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE),
+ common::Length(
+ parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
+ common::Length(FN));
+ conv->setCRSs(this, false);
+ return conv;
+ }
+ }
+
+ if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
+ targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP) {
+ // Notations m0, t0, m1, t1, m2, t2 n, F are those of the EPSG guidance
+ // "1.3.1.1 Lambert Conic Conformal (2SP)" and
+ // "1.3.1.2 Lambert Conic Conformal (1SP)" and
+ // or Snyder pages 106-109
+ const double phiF =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN)
+ .getSIValue();
+ const double phi1 =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL)
+ .getSIValue();
+ const double phi2 =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL)
+ .getSIValue();
+ if (!(std::fabs(phiF) < M_PI / 2))
+ return nullptr;
+ if (!(std::fabs(phi1) < M_PI / 2))
+ return nullptr;
+ if (!(std::fabs(phi2) < M_PI / 2))
+ return nullptr;
+ const double ec = std::sqrt(e2);
+ const double m1 = msfn(phi1, e2);
+ const double m2 = msfn(phi2, e2);
+ const double t1 = tsfn(phi1, ec);
+ const double t2 = tsfn(phi2, ec);
+ const double n_denom = std::log(t1) - std::log(t2);
+ const double n = (std::fabs(n_denom) < 1e-10)
+ ? std::sin(phi1)
+ : (std::log(m1) - std::log(m2)) / n_denom;
+ if (std::fabs(n) < 1e-10)
+ return nullptr;
+ const double F = m1 / (n * std::pow(t1, n));
+ const double phi0 = std::asin(n);
+ const double m0 = msfn(phi0, e2);
+ const double t0 = tsfn(phi0, ec);
+ const double F0 = m0 / (n * std::pow(t0, n));
+ const double k0 = F / F0;
+ const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
+ const double tF = tsfn(phiF, ec);
+ const double FN_correction =
+ a * F * (std::pow(tF, n) - std::pow(t0, n));
+
+ double phi0Deg = RadToDeg(phi0);
+ // Try to round to thousandth of degree if very close to it
+ if (std::fabs(phi0Deg * 1000 - std::floor(phi0Deg * 1000 + 0.5)) < 1e-8)
+ phi0Deg = std::floor(phi0Deg * 1000 + 0.5) / 1000;
+
+ auto conv = createLambertConicConformal_1SP(
+ util::PropertyMap(),
+ common::Angle(phi0Deg, common::UnitOfMeasure::DEGREE),
+ common::Angle(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN)),
+ common::Scale(k0), common::Length(parameterValueMeasure(
+ EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN)),
+ common::Length(
+ parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN) +
+ (std::fabs(FN_correction) > 1e-8 ? FN_correction : 0)));
+ conv->setCRSs(this, false);
+ return conv;
+ }
+
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static void getESRIMethodNameAndParams(const Conversion *conv,
+ const std::string &methodName,
+ int methodEPSGCode,
+ const char *&esriMethodName,
+ const ESRIParamMapping *&esriParams) {
+ esriParams = nullptr;
+ esriMethodName = nullptr;
+ const auto *esriMapping = getESRIMapping(methodName, methodEPSGCode);
+ const auto l_targetCRS = conv->targetCRS();
+ if (esriMapping) {
+ esriParams = esriMapping->params;
+ esriMethodName = esriMapping->esri_name;
+ if (esriMapping->epsg_code ==
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL ||
+ esriMapping->epsg_code ==
+ EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) {
+ if (l_targetCRS &&
+ ci_find(l_targetCRS->nameStr(), "Plate Carree") !=
+ std::string::npos &&
+ conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) {
+ esriParams = paramsESRI_Plate_Carree;
+ esriMethodName = "Plate_Carree";
+ } else {
+ esriParams = paramsESRI_Equidistant_Cylindrical;
+ esriMethodName = "Equidistant_Cylindrical";
+ }
+ } else if (esriMapping->epsg_code ==
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
+ if (l_targetCRS &&
+ (ci_find(l_targetCRS->nameStr(), "Gauss") !=
+ std::string::npos ||
+ ci_find(l_targetCRS->nameStr(), "GK_") != std::string::npos)) {
+ esriParams = paramsESRI_Gauss_Kruger;
+ esriMethodName = "Gauss_Kruger";
+ } else {
+ esriParams = paramsESRI_Transverse_Mercator;
+ esriMethodName = "Transverse_Mercator";
+ }
+ } else if (esriMapping->epsg_code ==
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) {
+ if (conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) ==
+ conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) {
+ esriParams =
+ paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin;
+ esriMethodName =
+ "Hotine_Oblique_Mercator_Azimuth_Natural_Origin";
+ } else {
+ esriParams =
+ paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin;
+ esriMethodName = "Rectified_Skew_Orthomorphic_Natural_Origin";
+ }
+ } else if (esriMapping->epsg_code ==
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) {
+ if (conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) ==
+ conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) {
+ esriParams = paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center;
+ esriMethodName = "Hotine_Oblique_Mercator_Azimuth_Center";
+ } else {
+ esriParams = paramsESRI_Rectified_Skew_Orthomorphic_Center;
+ esriMethodName = "Rectified_Skew_Orthomorphic_Center";
+ }
+ } else if (esriMapping->epsg_code ==
+ EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
+ if (conv->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL) > 0) {
+ esriMethodName = "Stereographic_North_Pole";
+ } else {
+ esriMethodName = "Stereographic_South_Pole";
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+const char *Conversion::getESRIMethodName() const {
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const auto methodEPSGCode = l_method->getEPSGCode();
+ const ESRIParamMapping *esriParams = nullptr;
+ const char *esriMethodName = nullptr;
+ getESRIMethodNameAndParams(this, methodName, methodEPSGCode, esriMethodName,
+ esriParams);
+ return esriMethodName;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+const char *Conversion::getWKT1GDALMethodName() const {
+ const auto &l_method = method();
+ const auto methodEPSGCode = l_method->getEPSGCode();
+ if (methodEPSGCode ==
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
+ return "Mercator_1SP";
+ }
+ const MethodMapping *mapping = getMapping(l_method.get());
+ return mapping ? mapping->wkt1_name : nullptr;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void Conversion::_exportToWKT(io::WKTFormatter *formatter) const {
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const auto methodEPSGCode = l_method->getEPSGCode();
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+
+ if (!isWKT2 && formatter->useESRIDialect()) {
+ if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
+ auto eqConv =
+ convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
+ if (eqConv) {
+ eqConv->_exportToWKT(formatter);
+ return;
+ }
+ }
+ }
+
+ if (isWKT2) {
+ formatter->startNode(formatter->useDerivingConversion()
+ ? io::WKTConstants::DERIVINGCONVERSION
+ : io::WKTConstants::CONVERSION,
+ !identifiers().empty());
+ formatter->addQuotedString(nameStr());
+ } else {
+ formatter->enter();
+ formatter->pushOutputUnit(false);
+ formatter->pushOutputId(false);
+ }
+
+ bool bAlreadyWritten = false;
+ if (!isWKT2 && formatter->useESRIDialect()) {
+ const ESRIParamMapping *esriParams = nullptr;
+ const char *esriMethodName = nullptr;
+ getESRIMethodNameAndParams(this, methodName, methodEPSGCode,
+ esriMethodName, esriParams);
+ if (esriMethodName && esriParams) {
+ formatter->startNode(io::WKTConstants::PROJECTION, false);
+ formatter->addQuotedString(esriMethodName);
+ formatter->endNode();
+
+ for (int i = 0; esriParams[i].esri_name != nullptr; i++) {
+ const auto &esriParam = esriParams[i];
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString(esriParam.esri_name);
+ if (esriParam.wkt2_name) {
+ const auto &pv = parameterValue(esriParam.wkt2_name,
+ esriParam.epsg_code);
+ if (pv && pv->type() == ParameterValue::Type::MEASURE) {
+ const auto &v = pv->value();
+ // as we don't output the natural unit, output
+ // to the registered linear / angular unit.
+ const auto &unitType = v.unit().type();
+ if (unitType == common::UnitOfMeasure::Type::LINEAR) {
+ formatter->add(v.convertToUnit(
+ *(formatter->axisLinearUnit())));
+ } else if (unitType ==
+ common::UnitOfMeasure::Type::ANGULAR) {
+ const auto &angUnit =
+ *(formatter->axisAngularUnit());
+ double val = v.convertToUnit(angUnit);
+ if (angUnit == common::UnitOfMeasure::DEGREE) {
+ if (val > 180.0) {
+ val -= 360.0;
+ } else if (val < -180.0) {
+ val += 360.0;
+ }
+ }
+ formatter->add(val);
+ } else {
+ formatter->add(v.getSIValue());
+ }
+ } else if (ci_find(esriParam.esri_name, "scale") !=
+ std::string::npos) {
+ formatter->add(1.0);
+ } else {
+ formatter->add(0.0);
+ }
+ } else {
+ formatter->add(esriParam.fixed_value);
+ }
+ formatter->endNode();
+ }
+ bAlreadyWritten = true;
+ }
+ } else if (!isWKT2) {
+ if (methodEPSGCode ==
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
+ const double latitudeOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ if (latitudeOrigin != 0) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
+ }
+
+ bAlreadyWritten = true;
+ formatter->startNode(io::WKTConstants::PROJECTION, false);
+ formatter->addQuotedString("Mercator_1SP");
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString("central_meridian");
+ const double centralMeridian = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ formatter->add(centralMeridian);
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString("scale_factor");
+ formatter->add(1.0);
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString("false_easting");
+ const double falseEasting =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING);
+ formatter->add(falseEasting);
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::PARAMETER, false);
+ formatter->addQuotedString("false_northing");
+ const double falseNorthing =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
+ formatter->add(falseNorthing);
+ formatter->endNode();
+ } else if (starts_with(methodName, "PROJ ")) {
+ bAlreadyWritten = true;
+ formatter->startNode(io::WKTConstants::PROJECTION, false);
+ formatter->addQuotedString("custom_proj4");
+ formatter->endNode();
+ }
+ }
+
+ if (!bAlreadyWritten) {
+ l_method->_exportToWKT(formatter);
+
+ const MethodMapping *mapping =
+ !isWKT2 ? getMapping(l_method.get()) : nullptr;
+ for (const auto &paramValue : parameterValues()) {
+ paramValue->_exportToWKT(formatter, mapping);
+ }
+ }
+
+ if (isWKT2) {
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+ formatter->endNode();
+ } else {
+ formatter->popOutputUnit();
+ formatter->popOutputId();
+ formatter->leave();
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static bool createPROJ4WebMercator(const Conversion *conv,
+ io::PROJStringFormatter *formatter) {
+ const double centralMeridian = conv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+
+ const double falseEasting =
+ conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING);
+
+ const double falseNorthing =
+ conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
+
+ auto sourceCRS = conv->sourceCRS();
+ auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
+ if (!geogCRS) {
+ return false;
+ }
+
+ formatter->addStep("merc");
+ const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
+ formatter->addParam("a", a);
+ formatter->addParam("b", a);
+ formatter->addParam("lat_ts", 0.0);
+ formatter->addParam("lon_0", centralMeridian);
+ formatter->addParam("x_0", falseEasting);
+ formatter->addParam("y_0", falseNorthing);
+ formatter->addParam("k", 1.0);
+ formatter->addParam("units", "m");
+ formatter->addParam("nadgrids", "@null");
+ formatter->addParam("wktext");
+ formatter->addParam("no_defs");
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+static bool
+createPROJExtensionFromCustomProj(const Conversion *conv,
+ io::PROJStringFormatter *formatter,
+ bool forExtensionNode) {
+ const auto &methodName = conv->method()->nameStr();
+ assert(starts_with(methodName, "PROJ "));
+ auto tokens = split(methodName, ' ');
+
+ formatter->addStep(tokens[1]);
+
+ if (forExtensionNode) {
+ auto sourceCRS = conv->sourceCRS();
+ auto geogCRS =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
+ if (!geogCRS) {
+ return false;
+ }
+ geogCRS->addDatumInfoToPROJString(formatter);
+ }
+
+ for (size_t i = 2; i < tokens.size(); i++) {
+ auto kv = split(tokens[i], '=');
+ if (kv.size() == 2) {
+ formatter->addParam(kv[0], kv[1]);
+ } else {
+ formatter->addParam(tokens[i]);
+ }
+ }
+
+ for (const auto &genOpParamvalue : conv->parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &paramValue = opParamvalue->parameterValue();
+ if (paramValue->type() == ParameterValue::Type::MEASURE) {
+ const auto &measure = paramValue->value();
+ const auto unitType = measure.unit().type();
+ if (unitType == common::UnitOfMeasure::Type::LINEAR) {
+ formatter->addParam(paramName, measure.getSIValue());
+ } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) {
+ formatter->addParam(
+ paramName,
+ measure.convertToUnit(common::UnitOfMeasure::DEGREE));
+ } else {
+ formatter->addParam(paramName, measure.value());
+ }
+ }
+ }
+ }
+
+ if (forExtensionNode) {
+ formatter->addParam("wktext");
+ formatter->addParam("no_defs");
+ }
+ return true;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+void Conversion::addWKTExtensionNode(io::WKTFormatter *formatter) const {
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ if (!isWKT2) {
+ const auto &methodName = method()->nameStr();
+ const int methodEPSGCode = method()->getEPSGCode();
+ if (methodEPSGCode ==
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR ||
+ nameStr() == "Popular Visualisation Mercator") {
+
+ auto projFormatter = io::PROJStringFormatter::create(
+ io::PROJStringFormatter::Convention::PROJ_4);
+ if (createPROJ4WebMercator(this, projFormatter.get())) {
+ formatter->startNode(io::WKTConstants::EXTENSION, false);
+ formatter->addQuotedString("PROJ4");
+ formatter->addQuotedString(projFormatter->toString());
+ formatter->endNode();
+ }
+ } else if (starts_with(methodName, "PROJ ")) {
+ auto projFormatter = io::PROJStringFormatter::create(
+ io::PROJStringFormatter::Convention::PROJ_4);
+ if (createPROJExtensionFromCustomProj(this, projFormatter.get(),
+ true)) {
+ formatter->startNode(io::WKTConstants::EXTENSION, false);
+ formatter->addQuotedString("PROJ4");
+ formatter->addQuotedString(projFormatter->toString());
+ formatter->endNode();
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void Conversion::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const // throw(FormattingException)
+{
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const int methodEPSGCode = l_method->getEPSGCode();
+ const bool isZUnitConversion =
+ methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT;
+ const bool isAffineParametric =
+ methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION;
+ const bool isGeographicGeocentric =
+ methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC;
+ const bool applySourceCRSModifiers =
+ !isZUnitConversion && !isAffineParametric &&
+ !isAxisOrderReversal(methodEPSGCode) && !isGeographicGeocentric;
+ const bool applyTargetCRSModifiers = applySourceCRSModifiers;
+
+ auto l_sourceCRS = sourceCRS();
+ if (l_sourceCRS && applySourceCRSModifiers &&
+ formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_5) {
+ auto geogCRS =
+ dynamic_cast<const crs::GeographicCRS *>(l_sourceCRS.get());
+ if (geogCRS) {
+ formatter->setOmitProjLongLatIfPossible(true);
+ formatter->startInversion();
+ geogCRS->_exportToPROJString(formatter);
+ formatter->stopInversion();
+ formatter->setOmitProjLongLatIfPossible(false);
+ }
+
+ auto projCRS =
+ dynamic_cast<const crs::ProjectedCRS *>(l_sourceCRS.get());
+ if (projCRS) {
+ formatter->startInversion();
+ projCRS->addUnitConvertAndAxisSwap(formatter, false);
+ formatter->stopInversion();
+ }
+ }
+
+ const auto &convName = nameStr();
+ bool bConversionDone = false;
+ bool bEllipsoidParametersDone = false;
+ bool useETMerc = false;
+ if (methodEPSGCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
+ // Check for UTM
+ int zone = 0;
+ bool north = true;
+ if (isUTM(zone, north)) {
+ bConversionDone = true;
+ formatter->addStep("utm");
+ formatter->addParam("zone", zone);
+ if (!north) {
+ formatter->addParam("south");
+ }
+ } else {
+ useETMerc = formatter->getUseETMercForTMerc();
+ }
+ } else if (methodEPSGCode ==
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) {
+ const double azimuth =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE,
+ common::UnitOfMeasure::DEGREE);
+ const double angleRectifiedToSkewGrid = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
+ common::UnitOfMeasure::DEGREE);
+ // Map to Swiss Oblique Mercator / somerc
+ if (std::fabs(azimuth - 90) < 1e-4 &&
+ std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) {
+ bConversionDone = true;
+ formatter->addStep("somerc");
+ formatter->addParam(
+ "lat_0", parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ common::UnitOfMeasure::DEGREE));
+ formatter->addParam(
+ "lon_0", parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ common::UnitOfMeasure::DEGREE));
+ formatter->addParam(
+ "k_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE));
+ formatter->addParam("x_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_FALSE_EASTING));
+ formatter->addParam("y_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_FALSE_NORTHING));
+ }
+ } else if (methodEPSGCode ==
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) {
+ const double azimuth =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE,
+ common::UnitOfMeasure::DEGREE);
+ const double angleRectifiedToSkewGrid = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
+ common::UnitOfMeasure::DEGREE);
+ // Map to Swiss Oblique Mercator / somerc
+ if (std::fabs(azimuth - 90) < 1e-4 &&
+ std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) {
+ bConversionDone = true;
+ formatter->addStep("somerc");
+ formatter->addParam(
+ "lat_0", parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE,
+ common::UnitOfMeasure::DEGREE));
+ formatter->addParam(
+ "lon_0", parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
+ common::UnitOfMeasure::DEGREE));
+ formatter->addParam(
+ "k_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE));
+ formatter->addParam(
+ "x_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE));
+ formatter->addParam(
+ "y_0", parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE));
+ }
+ } else if (methodEPSGCode == EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED) {
+ double colatitude =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS,
+ common::UnitOfMeasure::DEGREE);
+ double latitudePseudoStandardParallel = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL,
+ common::UnitOfMeasure::DEGREE);
+ if (std::fabs(colatitude - 30.28813972222222) > 1e-8) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS);
+ }
+ if (std::fabs(latitudePseudoStandardParallel - 78.5) > 1e-8) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL);
+ }
+ } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
+ double latitudeOrigin = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ if (latitudeOrigin != 0) {
+ throw io::FormattingException(
+ std::string("Unsupported value for ") +
+ EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
+ }
+ // PROJ.4 specific hack for webmercator
+ } else if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4 &&
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
+ if (!createPROJ4WebMercator(this, formatter)) {
+ throw io::FormattingException(
+ std::string("Cannot export ") +
+ EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR +
+ " as PROJ.4 string outside of a ProjectedCRS context");
+ }
+ bConversionDone = true;
+ bEllipsoidParametersDone = true;
+ } else if (ci_equal(convName, "Popular Visualisation Mercator")) {
+ if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ if (!createPROJ4WebMercator(this, formatter)) {
+ throw io::FormattingException(concat(
+ "Cannot export ", convName,
+ " as PROJ.4 string outside of a ProjectedCRS context"));
+ }
+ } else {
+ formatter->addStep("webmerc");
+ if (l_sourceCRS) {
+ datum::Ellipsoid::WGS84->_exportToPROJString(formatter);
+ }
+ }
+ bConversionDone = true;
+ bEllipsoidParametersDone = true;
+ } else if (starts_with(methodName, "PROJ ")) {
+ bConversionDone = true;
+ createPROJExtensionFromCustomProj(this, formatter, false);
+ } else if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_5 &&
+ isZUnitConversion) {
+ double convFactor = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
+ auto uom = common::UnitOfMeasure(std::string(), convFactor,
+ common::UnitOfMeasure::Type::LINEAR)
+ .exportToPROJString();
+ auto reverse_uom =
+ common::UnitOfMeasure(std::string(), 1.0 / convFactor,
+ common::UnitOfMeasure::Type::LINEAR)
+ .exportToPROJString();
+ if (!uom.empty()) {
+ formatter->addStep("unitconvert");
+ formatter->addParam("z_in", uom);
+ formatter->addParam("z_out", "m");
+ } else if (!reverse_uom.empty()) {
+ formatter->addStep("unitconvert");
+ formatter->addParam("z_in", "m");
+ formatter->addParam("z_out", reverse_uom);
+ } else {
+ formatter->addStep("affine");
+ formatter->addParam("s33", convFactor);
+ }
+ bConversionDone = true;
+ bEllipsoidParametersDone = true;
+ }
+
+ bool bAxisSpecFound = false;
+ if (!bConversionDone) {
+ const MethodMapping *mapping = getMapping(l_method.get());
+ if (mapping && mapping->proj_name_main) {
+ formatter->addStep(useETMerc ? "etmerc" : mapping->proj_name_main);
+ if (mapping->proj_name_aux) {
+ if (internal::starts_with(mapping->proj_name_aux, "axis=")) {
+ bAxisSpecFound = true;
+ }
+ auto kv = split(mapping->proj_name_aux, '=');
+ if (kv.size() == 2) {
+ formatter->addParam(kv[0], kv[1]);
+ } else {
+ formatter->addParam(mapping->proj_name_aux);
+ }
+ }
+
+ if (mapping->epsg_code ==
+ EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
+ double latitudeStdParallel = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL,
+ common::UnitOfMeasure::DEGREE);
+ formatter->addParam("lat_0",
+ (latitudeStdParallel >= 0) ? 90.0 : -90.0);
+ }
+
+ for (int i = 0; mapping->params[i] != nullptr; i++) {
+ const auto *param = mapping->params[i];
+ if (!param->proj_name) {
+ continue;
+ }
+ auto value =
+ parameterValueMeasure(param->wkt2_name, param->epsg_code);
+ if (mapping->epsg_code ==
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
+ strcmp(param->proj_name, "lat_1") == 0) {
+ formatter->addParam(
+ param->proj_name,
+ value.convertToUnit(common::UnitOfMeasure::DEGREE));
+ formatter->addParam(
+ "lat_0",
+ value.convertToUnit(common::UnitOfMeasure::DEGREE));
+ } else if (param->unit_type ==
+ common::UnitOfMeasure::Type::ANGULAR) {
+ formatter->addParam(
+ param->proj_name,
+ value.convertToUnit(common::UnitOfMeasure::DEGREE));
+ } else {
+ formatter->addParam(param->proj_name, value.getSIValue());
+ }
+ }
+
+ } else {
+ if (!exportToPROJStringGeneric(formatter)) {
+ throw io::FormattingException(
+ concat("Unsupported conversion method: ", methodName));
+ }
+ }
+ }
+
+ auto l_targetCRS = targetCRS();
+ if (l_targetCRS && applyTargetCRSModifiers) {
+ if (!bEllipsoidParametersDone) {
+ auto targetGeogCRS = l_targetCRS->extractGeographicCRS();
+ if (targetGeogCRS) {
+ if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ targetGeogCRS->addDatumInfoToPROJString(formatter);
+ } else {
+ targetGeogCRS->ellipsoid()->_exportToPROJString(formatter);
+ targetGeogCRS->primeMeridian()->_exportToPROJString(
+ formatter);
+ }
+ }
+ }
+
+ auto projCRS =
+ dynamic_cast<const crs::ProjectedCRS *>(l_targetCRS.get());
+ if (projCRS) {
+ projCRS->addUnitConvertAndAxisSwap(formatter, bAxisSpecFound);
+ }
+
+ auto derivedGeographicCRS =
+ dynamic_cast<const crs::DerivedGeographicCRS *>(l_targetCRS.get());
+ if (derivedGeographicCRS) {
+ if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_5) {
+ auto geogCRS = derivedGeographicCRS->baseCRS();
+ formatter->setOmitProjLongLatIfPossible(true);
+ geogCRS->_exportToPROJString(formatter);
+ formatter->setOmitProjLongLatIfPossible(false);
+ }
+ }
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether a conversion is a [Universal Transverse Mercator]
+ * (https://proj4.org/operations/projections/utm.html) conversion.
+ *
+ * @param[out] zone UTM zone number between 1 and 60.
+ * @param[out] north true for UTM northern hemisphere, false for UTM southern
+ * hemisphere.
+ * @return true if it is a UTM conversion.
+ */
+bool Conversion::isUTM(int &zone, bool &north) const {
+ zone = 0;
+ north = true;
+
+ if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
+ // Check for UTM
+
+ bool bLatitudeNatOriginUTM = false;
+ bool bScaleFactorUTM = false;
+ bool bFalseEastingUTM = false;
+ bool bFalseNorthingUTM = false;
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto epsg_code = opParamvalue->parameter()->getEPSGCode();
+ const auto &l_parameterValue = opParamvalue->parameterValue();
+ if (l_parameterValue->type() == ParameterValue::Type::MEASURE) {
+ const auto &measure = l_parameterValue->value();
+ if (epsg_code ==
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN &&
+ measure.value() == UTM_LATITUDE_OF_NATURAL_ORIGIN) {
+ bLatitudeNatOriginUTM = true;
+ } else if (
+ epsg_code ==
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN &&
+ measure.unit() == common::UnitOfMeasure::DEGREE) {
+ double dfZone = (measure.value() + 183.0) / 6.0;
+ if (dfZone > 0.9 && dfZone < 60.1 &&
+ std::abs(dfZone - std::round(dfZone)) < 1e-10) {
+ zone = static_cast<int>(std::lround(dfZone));
+ }
+ } else if (
+ epsg_code ==
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN &&
+ measure.value() == UTM_SCALE_FACTOR) {
+ bScaleFactorUTM = true;
+ } else if (epsg_code == EPSG_CODE_PARAMETER_FALSE_EASTING &&
+ measure.value() == UTM_FALSE_EASTING &&
+ measure.unit() == common::UnitOfMeasure::METRE) {
+ bFalseEastingUTM = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_FALSE_NORTHING &&
+ measure.unit() == common::UnitOfMeasure::METRE) {
+ if (measure.value() == UTM_NORTH_FALSE_NORTHING) {
+ bFalseNorthingUTM = true;
+ north = true;
+ } else if (measure.value() ==
+ UTM_SOUTH_FALSE_NORTHING) {
+ bFalseNorthingUTM = true;
+ north = false;
+ }
+ }
+ }
+ }
+ }
+ if (bLatitudeNatOriginUTM && zone > 0 && bScaleFactorUTM &&
+ bFalseEastingUTM && bFalseNorthingUTM) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a Conversion object where some parameters are better
+ * identified.
+ *
+ * @return a new Conversion.
+ */
+ConversionNNPtr Conversion::identify() const {
+ auto newConversion = Conversion::nn_make_shared<Conversion>(*this);
+ newConversion->assignSelf(newConversion);
+
+ if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
+ // Check for UTM
+ int zone = 0;
+ bool north = true;
+ if (isUTM(zone, north)) {
+ newConversion->setProperties(
+ getUTMConversionProperty(util::PropertyMap(), zone, north));
+ }
+ }
+
+ return newConversion;
+}
+
+//! @cond Doxygen_Suppress
+// ---------------------------------------------------------------------------
+
+InvalidOperation::InvalidOperation(const char *message) : Exception(message) {}
+
+// ---------------------------------------------------------------------------
+
+InvalidOperation::InvalidOperation(const std::string &message)
+ : Exception(message) {}
+
+// ---------------------------------------------------------------------------
+
+InvalidOperation::InvalidOperation(const InvalidOperation &) = default;
+
+// ---------------------------------------------------------------------------
+
+InvalidOperation::~InvalidOperation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct Transformation::Private {
+
+ TransformationPtr forwardOperation_{};
+
+ TransformationNNPtr registerInv(util::BaseObjectNNPtr thisIn,
+ TransformationNNPtr invTransform);
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+Transformation::Transformation(
+ const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
+ const crs::CRSPtr &interpolationCRSIn, const OperationMethodNNPtr &methodIn,
+ const std::vector<GeneralParameterValueNNPtr> &values,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies)
+ : SingleOperation(methodIn), d(internal::make_unique<Private>()) {
+ setParameterValues(values);
+ setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn);
+ setAccuracies(accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+Transformation::~Transformation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the source crs::CRS of the transformation.
+ *
+ * @return the source CRS.
+ */
+const crs::CRSNNPtr &Transformation::sourceCRS() PROJ_CONST_DEFN {
+ return CoordinateOperation::getPrivate()->strongRef_->sourceCRS_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the target crs::CRS of the transformation.
+ *
+ * @return the target CRS.
+ */
+const crs::CRSNNPtr &Transformation::targetCRS() PROJ_CONST_DEFN {
+ return CoordinateOperation::getPrivate()->strongRef_->targetCRS_;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+/** \brief Return the TOWGS84 parameters of the transformation.
+ *
+ * If this transformation uses Coordinate Frame Rotation, Position Vector
+ * transformation or Geocentric translations, a vector of 7 double values
+ * using the Position Vector convention (EPSG:9606) is returned. Those values
+ * can be used as the value of the WKT1 TOWGS84 parameter or
+ * PROJ +towgs84 parameter.
+ *
+ * @return a vector of 7 values if valid, otherwise a io::FormattingException
+ * is thrown.
+ * @throws io::FormattingException
+ */
+std::vector<double>
+Transformation::getTOWGS84Parameters() const // throw(io::FormattingException)
+{
+ // GDAL WKT1 assumes EPSG:9606 / Position Vector convention
+
+ bool sevenParamsTransform = false;
+ bool threeParamsTransform = false;
+ bool invertRotSigns = false;
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const int methodEPSGCode = l_method->getEPSGCode();
+ const auto paramCount = parameterValues().size();
+ if ((paramCount == 7 &&
+ ci_find(methodName, "Coordinate Frame") != std::string::npos) ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) {
+ sevenParamsTransform = true;
+ invertRotSigns = true;
+ } else if ((paramCount == 7 &&
+ ci_find(methodName, "Position Vector") != std::string::npos) ||
+ methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
+ sevenParamsTransform = true;
+ invertRotSigns = false;
+ } else if ((paramCount == 3 &&
+ ci_find(methodName, "Geocentric translations") !=
+ std::string::npos) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
+ threeParamsTransform = true;
+ }
+
+ if (threeParamsTransform || sevenParamsTransform) {
+ std::vector<double> params(7, 0.0);
+ bool foundX = false;
+ bool foundY = false;
+ bool foundZ = false;
+ bool foundRotX = false;
+ bool foundRotY = false;
+ bool foundRotZ = false;
+ bool foundScale = false;
+ const double rotSign = invertRotSigns ? -1.0 : 1.0;
+ for (const auto &genOpParamvalue : parameterValues()) {
+ auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
+ genOpParamvalue.get());
+ if (opParamvalue) {
+ const auto &parameter = opParamvalue->parameter();
+ const auto epsg_code = parameter->getEPSGCode();
+ const auto &l_parameterValue = opParamvalue->parameterValue();
+ if (l_parameterValue->type() == ParameterValue::Type::MEASURE) {
+ const auto &measure = l_parameterValue->value();
+ if (epsg_code == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) {
+ params[0] = measure.getSIValue();
+ foundX = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) {
+ params[1] = measure.getSIValue();
+ foundY = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) {
+ params[2] = measure.getSIValue();
+ foundZ = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_X_AXIS_ROTATION) {
+ params[3] = rotSign *
+ measure.convertToUnit(
+ common::UnitOfMeasure::ARC_SECOND);
+ foundRotX = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) {
+ params[4] = rotSign *
+ measure.convertToUnit(
+ common::UnitOfMeasure::ARC_SECOND);
+ foundRotY = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) {
+ params[5] = rotSign *
+ measure.convertToUnit(
+ common::UnitOfMeasure::ARC_SECOND);
+ foundRotZ = true;
+ } else if (epsg_code ==
+ EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) {
+ params[6] = measure.convertToUnit(
+ common::UnitOfMeasure::PARTS_PER_MILLION);
+ foundScale = true;
+ }
+ }
+ }
+ }
+ if (foundX && foundY && foundZ &&
+ (threeParamsTransform ||
+ (foundRotX && foundRotY && foundRotZ && foundScale))) {
+ return params;
+ } else {
+ throw io::FormattingException(
+ "Missing required parameter values in transformation");
+ }
+ }
+
+#if 0
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS ||
+ methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
+ auto offsetLat =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
+ auto offsetLong =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
+
+ auto offsetHeight =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
+
+ if (offsetLat.getSIValue() == 0.0 && offsetLong.getSIValue() == 0.0 &&
+ offsetHeight.getSIValue() == 0.0) {
+ std::vector<double> params(7, 0.0);
+ return params;
+ }
+ }
+#endif
+
+ throw io::FormattingException(
+ "Transformation cannot be formatted as WKT1 TOWGS84 parameters");
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation from a vector of GeneralParameterValue.
+ *
+ * @param properties See \ref general_properties. At minimum the name should be
+ * defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param interpolationCRSIn Interpolation CRS (might be null)
+ * @param methodIn Operation method.
+ * @param values Vector of GeneralOperationParameterNNPtr.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ * @throws InvalidOperation
+ */
+TransformationNNPtr Transformation::create(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn,
+ const OperationMethodNNPtr &methodIn,
+ const std::vector<GeneralParameterValueNNPtr> &values,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ if (methodIn->parameters().size() != values.size()) {
+ throw InvalidOperation(
+ "Inconsistent number of parameters and parameter values");
+ }
+ auto conv = Transformation::nn_make_shared<Transformation>(
+ sourceCRSIn, targetCRSIn, interpolationCRSIn, methodIn, values,
+ accuracies);
+ conv->assignSelf(conv);
+ conv->setProperties(properties);
+ return conv;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation ands its OperationMethod.
+ *
+ * @param propertiesTransformation The \ref general_properties of the
+ * Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param interpolationCRSIn Interpolation CRS (might be null)
+ * @param propertiesOperationMethod The \ref general_properties of the
+ * OperationMethod.
+ * At minimum the name should be defined.
+ * @param parameters Vector of parameters of the operation method.
+ * @param values Vector of ParameterValueNNPtr. Constraint:
+ * values.size() == parameters.size()
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ * @throws InvalidOperation
+ */
+TransformationNNPtr
+Transformation::create(const util::PropertyMap &propertiesTransformation,
+ const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn,
+ const crs::CRSPtr &interpolationCRSIn,
+ const util::PropertyMap &propertiesOperationMethod,
+ const std::vector<OperationParameterNNPtr> &parameters,
+ const std::vector<ParameterValueNNPtr> &values,
+ const std::vector<metadata::PositionalAccuracyNNPtr>
+ &accuracies) // throw InvalidOperation
+{
+ OperationMethodNNPtr op(
+ OperationMethod::create(propertiesOperationMethod, parameters));
+
+ if (parameters.size() != values.size()) {
+ throw InvalidOperation(
+ "Inconsistent number of parameters and parameter values");
+ }
+ std::vector<GeneralParameterValueNNPtr> generalParameterValues;
+ generalParameterValues.reserve(values.size());
+ for (size_t i = 0; i < values.size(); i++) {
+ generalParameterValues.push_back(
+ OperationParameterValue::create(parameters[i], values[i]));
+ }
+ return create(propertiesTransformation, sourceCRSIn, targetCRSIn,
+ interpolationCRSIn, op, generalParameterValues, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+static TransformationNNPtr createSevenParamsTransform(
+ const util::PropertyMap &properties,
+ const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double rotationXArcSecond, double rotationYArcSecond,
+ double rotationZArcSecond, double scaleDifferencePPM,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return Transformation::create(
+ properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties,
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE),
+ },
+ createParams(common::Length(translationXMetre),
+ common::Length(translationYMetre),
+ common::Length(translationZMetre),
+ common::Angle(rotationXArcSecond,
+ common::UnitOfMeasure::ARC_SECOND),
+ common::Angle(rotationYArcSecond,
+ common::UnitOfMeasure::ARC_SECOND),
+ common::Angle(rotationZArcSecond,
+ common::UnitOfMeasure::ARC_SECOND),
+ common::Scale(scaleDifferencePPM,
+ common::UnitOfMeasure::PARTS_PER_MILLION)),
+ accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+static void getTransformationType(const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn,
+ bool &isGeocentric, bool &isGeog2D,
+ bool &isGeog3D) {
+ auto sourceCRSGeod =
+ dynamic_cast<const crs::GeodeticCRS *>(sourceCRSIn.get());
+ auto targetCRSGeod =
+ dynamic_cast<const crs::GeodeticCRS *>(targetCRSIn.get());
+ isGeocentric = sourceCRSGeod && sourceCRSGeod->isGeocentric() &&
+ targetCRSGeod && targetCRSGeod->isGeocentric();
+ if (isGeocentric) {
+ isGeog2D = false;
+ isGeog3D = false;
+ return;
+ }
+ isGeocentric = false;
+
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRSIn.get());
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRSIn.get());
+ if (!sourceCRSGeog || !targetCRSGeog) {
+ throw InvalidOperation("Inconsistent CRS type");
+ }
+ const auto nSrcAxisCount =
+ sourceCRSGeog->coordinateSystem()->axisList().size();
+ const auto nTargetAxisCount =
+ targetCRSGeog->coordinateSystem()->axisList().size();
+ isGeog2D = nSrcAxisCount == 2 && nTargetAxisCount == 2;
+ isGeog3D = !isGeog2D && nSrcAxisCount >= 2 && nTargetAxisCount >= 2;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation with Geocentric Translations method.
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param translationXMetre Value of the Translation_X parameter (in metre).
+ * @param translationYMetre Value of the Translation_Y parameter (in metre).
+ * @param translationZMetre Value of the Translation_Z parameter (in metre).
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createGeocentricTranslations(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ bool isGeocentric;
+ bool isGeog2D;
+ bool isGeog3D;
+ getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
+ isGeog3D);
+ return create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(
+ isGeocentric
+ ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC
+ : isGeog2D
+ ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D
+ : EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
+ },
+ createParams(common::Length(translationXMetre),
+ common::Length(translationYMetre),
+ common::Length(translationZMetre)),
+ accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation with Position vector transformation
+ * method.
+ *
+ * This is similar to createCoordinateFrameRotation(), except that the sign of
+ * the rotation terms is inverted.
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param translationXMetre Value of the Translation_X parameter (in metre).
+ * @param translationYMetre Value of the Translation_Y parameter (in metre).
+ * @param translationZMetre Value of the Translation_Z parameter (in metre).
+ * @param rotationXArcSecond Value of the Rotation_X parameter (in
+ * arc-second).
+ * @param rotationYArcSecond Value of the Rotation_Y parameter (in
+ * arc-second).
+ * @param rotationZArcSecond Value of the Rotation_Z parameter (in
+ * arc-second).
+ * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
+ * parts-per-million).
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createPositionVector(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double rotationXArcSecond, double rotationYArcSecond,
+ double rotationZArcSecond, double scaleDifferencePPM,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+
+ bool isGeocentric;
+ bool isGeog2D;
+ bool isGeog3D;
+ getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
+ isGeog3D);
+ return createSevenParamsTransform(
+ properties,
+ createMethodMapNameEPSGCode(
+ isGeocentric
+ ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC
+ : isGeog2D ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
+ : EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D),
+ sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
+ translationZMetre, rotationXArcSecond, rotationYArcSecond,
+ rotationZArcSecond, scaleDifferencePPM, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation with Coordinate Frame Rotation method.
+ *
+ * This is similar to createPositionVector(), except that the sign of
+ * the rotation terms is inverted.
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param translationXMetre Value of the Translation_X parameter (in metre).
+ * @param translationYMetre Value of the Translation_Y parameter (in metre).
+ * @param translationZMetre Value of the Translation_Z parameter (in metre).
+ * @param rotationXArcSecond Value of the Rotation_X parameter (in
+ * arc-second).
+ * @param rotationYArcSecond Value of the Rotation_Y parameter (in
+ * arc-second).
+ * @param rotationZArcSecond Value of the Rotation_Z parameter (in
+ * arc-second).
+ * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
+ * parts-per-million).
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createCoordinateFrameRotation(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double rotationXArcSecond, double rotationYArcSecond,
+ double rotationZArcSecond, double scaleDifferencePPM,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ bool isGeocentric;
+ bool isGeog2D;
+ bool isGeog3D;
+ getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
+ isGeog3D);
+ return createSevenParamsTransform(
+ properties,
+ createMethodMapNameEPSGCode(
+ isGeocentric
+ ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC
+ : isGeog2D ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D
+ : EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D),
+ sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
+ translationZMetre, rotationXArcSecond, rotationYArcSecond,
+ rotationZArcSecond, scaleDifferencePPM, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static TransformationNNPtr createFifteenParamsTransform(
+ const util::PropertyMap &properties,
+ const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double rotationXArcSecond, double rotationYArcSecond,
+ double rotationZArcSecond, double scaleDifferencePPM,
+ double rateTranslationX, double rateTranslationY, double rateTranslationZ,
+ double rateRotationX, double rateRotationY, double rateRotationZ,
+ double rateScaleDifference, double referenceEpochYear,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return Transformation::create(
+ properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties,
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE),
+
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION),
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE),
+
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_REFERENCE_EPOCH),
+ },
+ VectorOfValues{
+ common::Length(translationXMetre),
+ common::Length(translationYMetre),
+ common::Length(translationZMetre),
+ common::Angle(rotationXArcSecond,
+ common::UnitOfMeasure::ARC_SECOND),
+ common::Angle(rotationYArcSecond,
+ common::UnitOfMeasure::ARC_SECOND),
+ common::Angle(rotationZArcSecond,
+ common::UnitOfMeasure::ARC_SECOND),
+ common::Scale(scaleDifferencePPM,
+ common::UnitOfMeasure::PARTS_PER_MILLION),
+ common::Measure(rateTranslationX,
+ common::UnitOfMeasure::METRE_PER_YEAR),
+ common::Measure(rateTranslationY,
+ common::UnitOfMeasure::METRE_PER_YEAR),
+ common::Measure(rateTranslationZ,
+ common::UnitOfMeasure::METRE_PER_YEAR),
+ common::Measure(rateRotationX,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
+ common::Measure(rateRotationY,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
+ common::Measure(rateRotationZ,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
+ common::Measure(rateScaleDifference,
+ common::UnitOfMeasure::PPM_PER_YEAR),
+ common::Measure(referenceEpochYear, common::UnitOfMeasure::YEAR),
+ },
+ accuracies);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation with Time Dependent position vector
+ * transformation method.
+ *
+ * This is similar to createTimeDependentCoordinateFrameRotation(), except that
+ * the sign of
+ * the rotation terms is inverted.
+ *
+ * This method is defined as [EPSG:1053]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1053)
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param translationXMetre Value of the Translation_X parameter (in metre).
+ * @param translationYMetre Value of the Translation_Y parameter (in metre).
+ * @param translationZMetre Value of the Translation_Z parameter (in metre).
+ * @param rotationXArcSecond Value of the Rotation_X parameter (in
+ * arc-second).
+ * @param rotationYArcSecond Value of the Rotation_Y parameter (in
+ * arc-second).
+ * @param rotationZArcSecond Value of the Rotation_Z parameter (in
+ * arc-second).
+ * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
+ * parts-per-million).
+ * @param rateTranslationX Value of the rate of change of X-axis translation (in
+ * metre/year)
+ * @param rateTranslationY Value of the rate of change of Y-axis translation (in
+ * metre/year)
+ * @param rateTranslationZ Value of the rate of change of Z-axis translation (in
+ * metre/year)
+ * @param rateRotationX Value of the rate of change of X-axis rotation (in
+ * arc-second/year)
+ * @param rateRotationY Value of the rate of change of Y-axis rotation (in
+ * arc-second/year)
+ * @param rateRotationZ Value of the rate of change of Z-axis rotation (in
+ * arc-second/year)
+ * @param rateScaleDifference Value of the rate of change of scale difference
+ * (in PPM/year)
+ * @param referenceEpochYear Parameter reference epoch (in decimal year)
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createTimeDependentPositionVector(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double rotationXArcSecond, double rotationYArcSecond,
+ double rotationZArcSecond, double scaleDifferencePPM,
+ double rateTranslationX, double rateTranslationY, double rateTranslationZ,
+ double rateRotationX, double rateRotationY, double rateRotationZ,
+ double rateScaleDifference, double referenceEpochYear,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ bool isGeocentric;
+ bool isGeog2D;
+ bool isGeog3D;
+ getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
+ isGeog3D);
+ return createFifteenParamsTransform(
+ properties,
+ createMethodMapNameEPSGCode(
+ isGeocentric
+ ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC
+ : isGeog2D
+ ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D
+ : EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D),
+ sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
+ translationZMetre, rotationXArcSecond, rotationYArcSecond,
+ rotationZArcSecond, scaleDifferencePPM, rateTranslationX,
+ rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY,
+ rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation with Time Dependent Position coordinate
+ * frame rotation transformation method.
+ *
+ * This is similar to createTimeDependentPositionVector(), except that the sign
+ * of
+ * the rotation terms is inverted.
+ *
+ * This method is defined as [EPSG:1056]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1056)
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param translationXMetre Value of the Translation_X parameter (in metre).
+ * @param translationYMetre Value of the Translation_Y parameter (in metre).
+ * @param translationZMetre Value of the Translation_Z parameter (in metre).
+ * @param rotationXArcSecond Value of the Rotation_X parameter (in
+ * arc-second).
+ * @param rotationYArcSecond Value of the Rotation_Y parameter (in
+ * arc-second).
+ * @param rotationZArcSecond Value of the Rotation_Z parameter (in
+ * arc-second).
+ * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
+ * parts-per-million).
+ * @param rateTranslationX Value of the rate of change of X-axis translation (in
+ * metre/year)
+ * @param rateTranslationY Value of the rate of change of Y-axis translation (in
+ * metre/year)
+ * @param rateTranslationZ Value of the rate of change of Z-axis translation (in
+ * metre/year)
+ * @param rateRotationX Value of the rate of change of X-axis rotation (in
+ * arc-second/year)
+ * @param rateRotationY Value of the rate of change of Y-axis rotation (in
+ * arc-second/year)
+ * @param rateRotationZ Value of the rate of change of Z-axis rotation (in
+ * arc-second/year)
+ * @param rateScaleDifference Value of the rate of change of scale difference
+ * (in PPM/year)
+ * @param referenceEpochYear Parameter reference epoch (in decimal year)
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ * @throws InvalidOperation
+ */
+TransformationNNPtr Transformation::createTimeDependentCoordinateFrameRotation(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double rotationXArcSecond, double rotationYArcSecond,
+ double rotationZArcSecond, double scaleDifferencePPM,
+ double rateTranslationX, double rateTranslationY, double rateTranslationZ,
+ double rateRotationX, double rateRotationY, double rateRotationZ,
+ double rateScaleDifference, double referenceEpochYear,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+
+ bool isGeocentric;
+ bool isGeog2D;
+ bool isGeog3D;
+ getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
+ isGeog3D);
+ return createFifteenParamsTransform(
+ properties,
+ createMethodMapNameEPSGCode(
+ isGeocentric
+ ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC
+ : isGeog2D
+ ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D
+ : EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D),
+ sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
+ translationZMetre, rotationXArcSecond, rotationYArcSecond,
+ rotationZArcSecond, scaleDifferencePPM, rateTranslationX,
+ rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY,
+ rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static TransformationNNPtr _createMolodensky(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, int methodEPSGCode,
+ double translationXMetre, double translationYMetre,
+ double translationZMetre, double semiMajorAxisDifferenceMetre,
+ double flattingDifference,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return Transformation::create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(methodEPSGCode),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE),
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE),
+ },
+ createParams(
+ common::Length(translationXMetre),
+ common::Length(translationYMetre),
+ common::Length(translationZMetre),
+ common::Length(semiMajorAxisDifferenceMetre),
+ common::Measure(flattingDifference, common::UnitOfMeasure::NONE)),
+ accuracies);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation with Molodensky method.
+ *
+ * @see createAbridgedMolodensky() for a related method.
+ *
+ * This method is defined as [EPSG:9604]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9604)
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param translationXMetre Value of the Translation_X parameter (in metre).
+ * @param translationYMetre Value of the Translation_Y parameter (in metre).
+ * @param translationZMetre Value of the Translation_Z parameter (in metre).
+ * @param semiMajorAxisDifferenceMetre The difference between the semi-major
+ * axis values of the ellipsoids used in the target and source CRS (in metre).
+ * @param flattingDifference The difference between the flattening values of
+ * the ellipsoids used in the target and source CRS.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ * @throws InvalidOperation
+ */
+TransformationNNPtr Transformation::createMolodensky(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double semiMajorAxisDifferenceMetre, double flattingDifference,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return _createMolodensky(
+ properties, sourceCRSIn, targetCRSIn, EPSG_CODE_METHOD_MOLODENSKY,
+ translationXMetre, translationYMetre, translationZMetre,
+ semiMajorAxisDifferenceMetre, flattingDifference, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation with Abridged Molodensky method.
+ *
+ * @see createdMolodensky() for a related method.
+ *
+ * This method is defined as [EPSG:9605]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9605)
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param translationXMetre Value of the Translation_X parameter (in metre).
+ * @param translationYMetre Value of the Translation_Y parameter (in metre).
+ * @param translationZMetre Value of the Translation_Z parameter (in metre).
+ * @param semiMajorAxisDifferenceMetre The difference between the semi-major
+ * axis values of the ellipsoids used in the target and source CRS (in metre).
+ * @param flattingDifference The difference between the flattening values of
+ * the ellipsoids used in the target and source CRS.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ * @throws InvalidOperation
+ */
+TransformationNNPtr Transformation::createAbridgedMolodensky(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
+ double translationYMetre, double translationZMetre,
+ double semiMajorAxisDifferenceMetre, double flattingDifference,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return _createMolodensky(properties, sourceCRSIn, targetCRSIn,
+ EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY,
+ translationXMetre, translationYMetre,
+ translationZMetre, semiMajorAxisDifferenceMetre,
+ flattingDifference, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation from TOWGS84 parameters.
+ *
+ * This is a helper of createPositionVector() with the source CRS being the
+ * GeographicCRS of sourceCRSIn, and the target CRS being EPSG:4326
+ *
+ * @param sourceCRSIn Source CRS.
+ * @param TOWGS84Parameters The vector of 3 double values (Translation_X,_Y,_Z)
+ * or 7 double values (Translation_X,_Y,_Z, Rotation_X,_Y,_Z, Scale_Difference)
+ * passed to createPositionVector()
+ * @return new Transformation.
+ * @throws InvalidOperation
+ */
+TransformationNNPtr Transformation::createTOWGS84(
+ const crs::CRSNNPtr &sourceCRSIn,
+ const std::vector<double> &TOWGS84Parameters) // throw InvalidOperation
+{
+ if (TOWGS84Parameters.size() != 3 && TOWGS84Parameters.size() != 7) {
+ throw InvalidOperation(
+ "Invalid number of elements in TOWGS84Parameters");
+ }
+
+ crs::CRSPtr transformSourceCRS = sourceCRSIn->extractGeographicCRS();
+ if (!transformSourceCRS) {
+ throw InvalidOperation(
+ "Cannot find GeographicCRS in sourceCRS of TOWGS84 transformation");
+ }
+
+ if (TOWGS84Parameters.size() == 3) {
+ return createGeocentricTranslations(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ concat("Transformation from ",
+ transformSourceCRS->nameStr(),
+ " to WGS84")),
+ NN_NO_CHECK(transformSourceCRS), crs::GeographicCRS::EPSG_4326,
+ TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2],
+ {});
+ }
+
+ return createPositionVector(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ concat("Transformation from ",
+ transformSourceCRS->nameStr(),
+ " to WGS84")),
+ NN_NO_CHECK(transformSourceCRS), crs::GeographicCRS::EPSG_4326,
+ TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2],
+ TOWGS84Parameters[3], TOWGS84Parameters[4], TOWGS84Parameters[5],
+ TOWGS84Parameters[6], {});
+}
+
+// ---------------------------------------------------------------------------
+/** \brief Instanciate a transformation with NTv2 method.
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param filename NTv2 filename.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createNTv2(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const std::string &filename,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+
+ return create(properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV2),
+ VectorOfParameters{createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)},
+ VectorOfValues{ParameterValue::createFilename(filename)},
+ accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static TransformationNNPtr _createGravityRelatedHeightToGeographic3D(
+ const util::PropertyMap &properties, bool inverse,
+ const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
+ const std::string &filename,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+
+ return Transformation::create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ inverse ? INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D
+ : PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D),
+ VectorOfParameters{createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)},
+ VectorOfValues{ParameterValue::createFilename(filename)}, accuracies);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+/** \brief Instanciate a transformation from GravityRelatedHeight to
+ * Geographic3D
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param filename GRID filename.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createGravityRelatedHeightToGeographic3D(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const std::string &filename,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+
+ return _createGravityRelatedHeightToGeographic3D(
+ properties, false, sourceCRSIn, targetCRSIn, filename, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation with method VERTCON
+ *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param filename GRID filename.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createVERTCON(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const std::string &filename,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+
+ return create(properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTCON),
+ VectorOfParameters{createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)},
+ VectorOfValues{ParameterValue::createFilename(filename)},
+ accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static inline std::vector<metadata::PositionalAccuracyNNPtr>
+buildAccuracyZero() {
+ return std::vector<metadata::PositionalAccuracyNNPtr>{
+ metadata::PositionalAccuracy::create("0")};
+}
+
+// ---------------------------------------------------------------------------
+
+//! @endcond
+
+/** \brief Instanciate a transformation with method Longitude rotation
+ *
+ * This method is defined as [EPSG:9601]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9601)
+ * *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param offset Longitude offset to add.
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createLongitudeRotation(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const common::Angle &offset) {
+
+ return create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_LONGITUDE_ROTATION),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)},
+ VectorOfValues{ParameterValue::create(offset)}, buildAccuracyZero());
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool Transformation::isLongitudeRotation() const {
+ return method()->getEPSGCode() == EPSG_CODE_METHOD_LONGITUDE_ROTATION;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation with method Geographic 2D offsets
+ *
+ * This method is defined as [EPSG:9619]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9619)
+ * *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param offsetLat Latitude offset to add.
+ * @param offsetLon Longitude offset to add.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createGeographic2DOffsets(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
+ const common::Angle &offsetLon,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)},
+ VectorOfValues{offsetLat, offsetLon}, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation with method Geographic 3D offsets
+ *
+ * This method is defined as [EPSG:9660]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9660)
+ * *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param offsetLat Latitude offset to add.
+ * @param offsetLon Longitude offset to add.
+ * @param offsetHeight Height offset to add.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createGeographic3DOffsets(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
+ const common::Angle &offsetLon, const common::Length &offsetHeight,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
+ VectorOfValues{offsetLat, offsetLon, offsetHeight}, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation with method Geographic 2D with
+ * height
+ * offsets
+ *
+ * This method is defined as [EPSG:9618]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9618)
+ * *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param offsetLat Latitude offset to add.
+ * @param offsetLon Longitude offset to add.
+ * @param offsetHeight Geoid undulation to add.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createGeographic2DWithHeightOffsets(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
+ const common::Angle &offsetLon, const common::Length &offsetHeight,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(
+ EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET),
+ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_GEOID_UNDULATION)},
+ VectorOfValues{offsetLat, offsetLon, offsetHeight}, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a transformation with method Vertical Offset.
+ *
+ * This method is defined as [EPSG:9616]
+ * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9616)
+ * *
+ * @param properties See \ref general_properties of the Transformation.
+ * At minimum the name should be defined.
+ * @param sourceCRSIn Source CRS.
+ * @param targetCRSIn Target CRS.
+ * @param offsetHeight Geoid undulation to add.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ */
+TransformationNNPtr Transformation::createVerticalOffset(
+ const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn, const common::Length &offsetHeight,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return create(properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTICAL_OFFSET),
+ VectorOfParameters{createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
+ VectorOfValues{offsetHeight}, accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+static const char *getCRSQualifierStr(const crs::CRSPtr &crs) {
+ auto geod = dynamic_cast<crs::GeodeticCRS *>(crs.get());
+ if (geod) {
+ if (geod->isGeocentric()) {
+ return " (geocentric)";
+ }
+ auto geog = dynamic_cast<crs::GeographicCRS *>(geod);
+ if (geog) {
+ if (geog->coordinateSystem()->axisList().size() == 2) {
+ return " (geog2D)";
+ } else {
+ return " (geog3D)";
+ }
+ }
+ }
+ return "";
+}
+
+// ---------------------------------------------------------------------------
+
+static std::string buildOpName(const char *opType, const crs::CRSPtr &source,
+ const crs::CRSPtr &target) {
+ std::string res(opType);
+ const auto &srcName = source->nameStr();
+ const auto &targetName = target->nameStr();
+ const char *srcQualifier = "";
+ const char *targetQualifier = "";
+ if (srcName == targetName) {
+ srcQualifier = getCRSQualifierStr(source);
+ targetQualifier = getCRSQualifierStr(target);
+ if (strcmp(srcQualifier, targetQualifier) == 0) {
+ srcQualifier = "";
+ targetQualifier = "";
+ }
+ }
+ res += " from ";
+ res += srcName;
+ res += srcQualifier;
+ res += " to ";
+ res += targetName;
+ res += targetQualifier;
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+static util::PropertyMap
+createPropertiesForInverse(const CoordinateOperation *op, bool derivedFrom,
+ bool approximateInversion) {
+ assert(op);
+ util::PropertyMap map;
+
+ // The domain(s) are unchanged by the inverse operation
+ addDomains(map, op);
+
+ const std::string &forwardName = op->nameStr();
+
+ // Forge a name for the inverse, either from the forward name, or
+ // from the source and target CRS names
+ const char *opType;
+ if (starts_with(forwardName, NULL_GEOCENTRIC_TRANSLATION)) {
+ opType = NULL_GEOCENTRIC_TRANSLATION;
+ } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) {
+ opType = NULL_GEOGRAPHIC_OFFSET;
+ } else if (dynamic_cast<const Transformation *>(op) ||
+ starts_with(forwardName, "Transformation from ")) {
+ opType = "Transformation";
+ } else if (dynamic_cast<const Conversion *>(op)) {
+ opType = "Conversion";
+ } else {
+ opType = "Operation";
+ }
+
+ auto sourceCRS = op->sourceCRS();
+ auto targetCRS = op->targetCRS();
+ std::string name;
+ if (!forwardName.empty()) {
+ if (starts_with(forwardName, INVERSE_OF)) {
+ name = forwardName.substr(INVERSE_OF.size());
+ } else if (!sourceCRS || !targetCRS ||
+ forwardName != buildOpName(opType, sourceCRS, targetCRS)) {
+ name = INVERSE_OF + forwardName;
+ }
+ }
+ if (name.empty() && sourceCRS && targetCRS) {
+ name = buildOpName(opType, targetCRS, sourceCRS);
+ }
+ if (approximateInversion) {
+ name += " (approx. inversion)";
+ }
+
+ if (!name.empty()) {
+ map.set(common::IdentifiedObject::NAME_KEY, name);
+ }
+
+ addModifiedIdentifier(map, op, true, derivedFrom);
+
+ return map;
+}
+
+// ---------------------------------------------------------------------------
+
+static bool isTimeDependent(const std::string &methodName) {
+ return ci_find(methodName, "Time dependent") != std::string::npos ||
+ ci_find(methodName, "Time-dependent") != std::string::npos;
+}
+
+// ---------------------------------------------------------------------------
+
+// to avoid -0...
+static double negate(double val) {
+ if (val != 0) {
+ return -val;
+ }
+ return 0.0;
+}
+
+// ---------------------------------------------------------------------------
+
+static CoordinateOperationPtr
+createApproximateInverseIfPossible(const Transformation *op) {
+ bool sevenParamsTransform = false;
+ bool fifteenParamsTransform = false;
+ const auto &method = op->method();
+ const auto &methodName = method->nameStr();
+ const int methodEPSGCode = method->getEPSGCode();
+ const auto paramCount = op->parameterValues().size();
+ const bool isPositionVector =
+ ci_find(methodName, "Position Vector") != std::string::npos;
+ const bool isCoordinateFrame =
+ ci_find(methodName, "Coordinate Frame") != std::string::npos;
+
+ // See end of "2.4.3.3 Helmert 7-parameter transformations"
+ // in EPSG 7-2 guidance
+ // For practical purposes, the inverse of 7- or 15-parameters Helmert
+ // can be obtained by using the forward method with all parameters
+ // negated
+ // (except reference epoch!)
+ // So for WKT export use that. But for PROJ string, we use the +inv flag
+ // so as to get "perfect" round-tripability.
+ if ((paramCount == 7 && isCoordinateFrame &&
+ !isTimeDependent(methodName)) ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) {
+ sevenParamsTransform = true;
+ } else if (
+ (paramCount == 15 && isCoordinateFrame &&
+ isTimeDependent(methodName)) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) {
+ fifteenParamsTransform = true;
+ } else if ((paramCount == 7 && isPositionVector &&
+ !isTimeDependent(methodName)) ||
+ methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
+ sevenParamsTransform = true;
+ } else if (
+ (paramCount == 15 && isPositionVector && isTimeDependent(methodName)) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) {
+ fifteenParamsTransform = true;
+ }
+ if (sevenParamsTransform || fifteenParamsTransform) {
+ double neg_x = negate(op->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION));
+ double neg_y = negate(op->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION));
+ double neg_z = negate(op->parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION));
+ double neg_rx = negate(
+ op->parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND));
+ double neg_ry = negate(
+ op->parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND));
+ double neg_rz = negate(
+ op->parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND));
+ double neg_scaleDiff = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
+ common::UnitOfMeasure::PARTS_PER_MILLION));
+ auto methodProperties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, methodName);
+ int method_epsg_code = method->getEPSGCode();
+ if (method_epsg_code) {
+ methodProperties
+ .set(metadata::Identifier::CODESPACE_KEY,
+ metadata::Identifier::EPSG)
+ .set(metadata::Identifier::CODE_KEY, method_epsg_code);
+ }
+ if (fifteenParamsTransform) {
+ double neg_rate_x = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION,
+ common::UnitOfMeasure::METRE_PER_YEAR));
+ double neg_rate_y = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION,
+ common::UnitOfMeasure::METRE_PER_YEAR));
+ double neg_rate_z = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION,
+ common::UnitOfMeasure::METRE_PER_YEAR));
+ double neg_rate_rx = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
+ double neg_rate_ry = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
+ double neg_rate_rz = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
+ double neg_rate_scaleDiff = negate(op->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE,
+ common::UnitOfMeasure::PPM_PER_YEAR));
+ double referenceEpochYear =
+ op->parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH,
+ common::UnitOfMeasure::YEAR);
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ createFifteenParamsTransform(
+ createPropertiesForInverse(op, false, true),
+ methodProperties, op->targetCRS(), op->sourceCRS(),
+ neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz,
+ neg_scaleDiff, neg_rate_x, neg_rate_y, neg_rate_z,
+ neg_rate_rx, neg_rate_ry, neg_rate_rz,
+ neg_rate_scaleDiff, referenceEpochYear,
+ op->coordinateOperationAccuracies()))
+ .as_nullable();
+ } else {
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ createSevenParamsTransform(
+ createPropertiesForInverse(op, false, true),
+ methodProperties, op->targetCRS(), op->sourceCRS(),
+ neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz,
+ neg_scaleDiff, op->coordinateOperationAccuracies()))
+ .as_nullable();
+ }
+ }
+
+ return nullptr;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+TransformationNNPtr
+Transformation::Private::registerInv(util::BaseObjectNNPtr thisIn,
+ TransformationNNPtr invTransform) {
+ invTransform->d->forwardOperation_ =
+ util::nn_dynamic_pointer_cast<Transformation>(thisIn);
+ return invTransform;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr Transformation::inverse() const {
+ return inverseAsTransformation();
+}
+
+// ---------------------------------------------------------------------------
+
+TransformationNNPtr Transformation::inverseAsTransformation() const {
+
+ if (d->forwardOperation_) {
+ return NN_NO_CHECK(d->forwardOperation_);
+ }
+ const auto &l_method = method();
+ const auto &methodName = l_method->nameStr();
+ const int methodEPSGCode = l_method->getEPSGCode();
+ const auto &l_sourceCRS = sourceCRS();
+ const auto &l_targetCRS = targetCRS();
+
+ // For geocentric translation, the inverse is exactly the negation of
+ // the parameters.
+ if (ci_find(methodName, "Geocentric translations") != std::string::npos ||
+ methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
+ double x =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
+ double y =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
+ double z =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
+ return d->registerInv(
+ shared_from_this(),
+ createGeocentricTranslations(
+ createPropertiesForInverse(this, false, false), l_targetCRS,
+ l_sourceCRS, negate(x), negate(y), negate(z),
+ coordinateOperationAccuracies()));
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY ||
+ methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
+ double x =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
+ double y =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
+ double z =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
+ double da = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE);
+ double df = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE);
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
+ return d->registerInv(
+ shared_from_this(),
+ createAbridgedMolodensky(
+ createPropertiesForInverse(this, false, false), l_targetCRS,
+ l_sourceCRS, negate(x), negate(y), negate(z), negate(da),
+ negate(df), coordinateOperationAccuracies()));
+ } else {
+ return d->registerInv(
+ shared_from_this(),
+ createMolodensky(createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, negate(x), negate(y),
+ negate(z), negate(da), negate(df),
+ coordinateOperationAccuracies()));
+ }
+ }
+
+ if (isLongitudeRotation()) {
+ auto offset =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
+ const common::Angle newOffset(negate(offset.value()), offset.unit());
+ return d->registerInv(
+ shared_from_this(),
+ createLongitudeRotation(
+ createPropertiesForInverse(this, false, false), l_targetCRS,
+ l_sourceCRS, newOffset));
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) {
+ auto offsetLat =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
+ const common::Angle newOffsetLat(negate(offsetLat.value()),
+ offsetLat.unit());
+
+ auto offsetLong =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
+ const common::Angle newOffsetLong(negate(offsetLong.value()),
+ offsetLong.unit());
+
+ return d->registerInv(
+ shared_from_this(),
+ createGeographic2DOffsets(
+ createPropertiesForInverse(this, false, false), l_targetCRS,
+ l_sourceCRS, newOffsetLat, newOffsetLong,
+ coordinateOperationAccuracies()));
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
+ auto offsetLat =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
+ const common::Angle newOffsetLat(negate(offsetLat.value()),
+ offsetLat.unit());
+
+ auto offsetLong =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
+ const common::Angle newOffsetLong(negate(offsetLong.value()),
+ offsetLong.unit());
+
+ auto offsetHeight =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
+ const common::Length newOffsetHeight(negate(offsetHeight.value()),
+ offsetHeight.unit());
+
+ return d->registerInv(
+ shared_from_this(),
+ createGeographic3DOffsets(
+ createPropertiesForInverse(this, false, false), l_targetCRS,
+ l_sourceCRS, newOffsetLat, newOffsetLong, newOffsetHeight,
+ coordinateOperationAccuracies()));
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) {
+ auto offsetLat =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
+ const common::Angle newOffsetLat(negate(offsetLat.value()),
+ offsetLat.unit());
+
+ auto offsetLong =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
+ const common::Angle newOffsetLong(negate(offsetLong.value()),
+ offsetLong.unit());
+
+ auto offsetHeight =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_GEOID_UNDULATION);
+ const common::Length newOffsetHeight(negate(offsetHeight.value()),
+ offsetHeight.unit());
+
+ return d->registerInv(
+ shared_from_this(),
+ createGeographic2DWithHeightOffsets(
+ createPropertiesForInverse(this, false, false), l_targetCRS,
+ l_sourceCRS, newOffsetLat, newOffsetLong, newOffsetHeight,
+ coordinateOperationAccuracies()));
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) {
+
+ auto offsetHeight =
+ parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
+ const common::Length newOffsetHeight(negate(offsetHeight.value()),
+ offsetHeight.unit());
+
+ return d->registerInv(
+ shared_from_this(),
+ createVerticalOffset(createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, newOffsetHeight,
+ coordinateOperationAccuracies()));
+ }
+
+ return InverseTransformation::create(NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<Transformation>(shared_from_this())));
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+InverseTransformation::InverseTransformation(const TransformationNNPtr &forward)
+ : Transformation(
+ forward->targetCRS(), forward->sourceCRS(),
+ forward->interpolationCRS(),
+ OperationMethod::create(createPropertiesForInverse(forward->method()),
+ forward->method()->parameters()),
+ forward->parameterValues(), forward->coordinateOperationAccuracies()),
+ InverseCoordinateOperation(forward, true) {
+ setPropertiesFromForward();
+}
+
+// ---------------------------------------------------------------------------
+
+InverseTransformation::~InverseTransformation() = default;
+
+// ---------------------------------------------------------------------------
+
+TransformationNNPtr
+InverseTransformation::create(const TransformationNNPtr &forward) {
+ auto conv = util::nn_make_shared<InverseTransformation>(forward);
+ conv->assignSelf(conv);
+ return conv;
+}
+
+// ---------------------------------------------------------------------------
+
+void InverseTransformation::_exportToWKT(io::WKTFormatter *formatter) const {
+
+ auto approxInverse = createApproximateInverseIfPossible(
+ util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_).get());
+ if (approxInverse) {
+ approxInverse->_exportToWKT(formatter);
+ } else {
+ Transformation::_exportToWKT(formatter);
+ }
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void Transformation::_exportToWKT(io::WKTFormatter *formatter) const {
+ exportTransformationToWKT(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+void SingleOperation::exportTransformationToWKT(
+ io::WKTFormatter *formatter) const {
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ if (!isWKT2) {
+ throw io::FormattingException(
+ "Transformation can only be exported to WKT2");
+ }
+
+ auto l_sourceCRS = sourceCRS();
+ assert(l_sourceCRS);
+ auto l_targetCRS = targetCRS();
+ assert(l_targetCRS);
+
+ if (formatter->abridgedTransformation()) {
+ formatter->startNode(io::WKTConstants::ABRIDGEDTRANSFORMATION,
+ !identifiers().empty());
+ } else {
+ formatter->startNode(io::WKTConstants::COORDINATEOPERATION,
+ !identifiers().empty());
+ }
+
+ formatter->addQuotedString(nameStr());
+
+ if (!formatter->abridgedTransformation()) {
+ formatter->startNode(io::WKTConstants::SOURCECRS, false);
+ l_sourceCRS->_exportToWKT(formatter);
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::TARGETCRS, false);
+ l_targetCRS->_exportToWKT(formatter);
+ formatter->endNode();
+ }
+
+ method()->_exportToWKT(formatter);
+
+ const MethodMapping *mapping =
+ !isWKT2 ? getMapping(method().get()) : nullptr;
+ for (const auto &paramValue : parameterValues()) {
+ paramValue->_exportToWKT(formatter, mapping);
+ }
+
+ if (!formatter->abridgedTransformation()) {
+ if (interpolationCRS()) {
+ formatter->startNode(io::WKTConstants::INTERPOLATIONCRS, false);
+ interpolationCRS()->_exportToWKT(formatter);
+ formatter->endNode();
+ }
+
+ if (!coordinateOperationAccuracies().empty()) {
+ formatter->startNode(io::WKTConstants::OPERATIONACCURACY, false);
+ formatter->add(coordinateOperationAccuracies()[0]->value());
+ formatter->endNode();
+ }
+ }
+
+ ObjectUsage::baseExportToWKT(formatter);
+ formatter->endNode();
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const std::string nullString;
+
+static const std::string &_getNTv2Filename(const Transformation *op,
+ bool allowInverse) {
+
+ const auto &l_method = op->method();
+ if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV2 ||
+ (allowInverse &&
+ ci_equal(l_method->nameStr(), INVERSE_OF + EPSG_NAME_METHOD_NTV2))) {
+ const auto &fileParameter = op->parameterValue(
+ EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ return fileParameter->valueFile();
+ }
+ }
+ return nullString;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+//! @cond Doxygen_Suppress
+const std::string &Transformation::getNTv2Filename() const {
+
+ return _getNTv2Filename(this, false);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const std::string &_getNTv1Filename(const Transformation *op,
+ bool allowInverse) {
+
+ const auto &l_method = op->method();
+ const auto &methodName = l_method->nameStr();
+ if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV1 ||
+ (allowInverse &&
+ ci_equal(methodName, INVERSE_OF + EPSG_NAME_METHOD_NTV1))) {
+ const auto &fileParameter = op->parameterValue(
+ EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ return fileParameter->valueFile();
+ }
+ }
+ return nullString;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const std::string &_getCTABLE2Filename(const Transformation *op,
+ bool allowInverse) {
+ const auto &l_method = op->method();
+ const auto &methodName = l_method->nameStr();
+ if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_CTABLE2) ||
+ (allowInverse &&
+ ci_equal(methodName, INVERSE_OF + PROJ_WKT2_NAME_METHOD_CTABLE2))) {
+ const auto &fileParameter = op->parameterValue(
+ EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ return fileParameter->valueFile();
+ }
+ }
+ return nullString;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const std::string &
+_getHeightToGeographic3DFilename(const Transformation *op, bool allowInverse) {
+
+ const auto &methodName = op->method()->nameStr();
+
+ if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D) ||
+ (allowInverse &&
+ ci_equal(methodName,
+ INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D))) {
+ const auto &fileParameter =
+ op->parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
+ EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ return fileParameter->valueFile();
+ }
+ }
+ return nullString;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+const std::string &Transformation::getHeightToGeographic3DFilename() const {
+
+ return _getHeightToGeographic3DFilename(this, false);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static bool
+isGeographic3DToGravityRelatedHeight(const OperationMethodNNPtr &method,
+ bool allowInverse) {
+ const auto &methodName = method->nameStr();
+ static const char *const methodCodes[] = {
+ "1025", // Geographic3D to GravityRelatedHeight (EGM2008)
+ "1030", // Geographic3D to GravityRelatedHeight (NZgeoid)
+ "1045", // Geographic3D to GravityRelatedHeight (OSGM02-Ire)
+ "1047", // Geographic3D to GravityRelatedHeight (Gravsoft)
+ "1048", // Geographic3D to GravityRelatedHeight (Ausgeoid v2)
+ "1050", // Geographic3D to GravityRelatedHeight (CI)
+ "1059", // Geographic3D to GravityRelatedHeight (PNG)
+ "1060", // Geographic3D to GravityRelatedHeight (CGG2013)
+ "1072", // Geographic3D to GravityRelatedHeight (OSGM15-Ire)
+ "1073", // Geographic3D to GravityRelatedHeight (IGN2009)
+ "9661", // Geographic3D to GravityRelatedHeight (EGM)
+ "9662", // Geographic3D to GravityRelatedHeight (Ausgeoid98)
+ "9663", // Geographic3D to GravityRelatedHeight (OSGM-GB)
+ "9664", // Geographic3D to GravityRelatedHeight (IGN1997)
+ "9665", // Geographic3D to GravityRelatedHeight (US .gtx)
+ };
+
+ if (ci_find(methodName, "Geographic3D to GravityRelatedHeight") == 0) {
+ return true;
+ }
+ if (allowInverse &&
+ ci_find(methodName,
+ INVERSE_OF + "Geographic3D to GravityRelatedHeight") == 0) {
+ return true;
+ }
+
+ for (const auto &code : methodCodes) {
+ for (const auto &idSrc : method->identifiers()) {
+ const auto &srcAuthName = *(idSrc->codeSpace());
+ const auto &srcCode = idSrc->code();
+ if (ci_equal(srcAuthName, "EPSG") && srcCode == code) {
+ return true;
+ }
+ if (allowInverse && ci_equal(srcAuthName, "INVERSE(EPSG)") &&
+ srcCode == code) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static util::PropertyMap
+createSimilarPropertiesMethod(common::IdentifiedObjectNNPtr obj) {
+ util::PropertyMap map;
+
+ const std::string &forwardName = obj->nameStr();
+ if (!forwardName.empty()) {
+ map.set(common::IdentifiedObject::NAME_KEY, forwardName);
+ }
+
+ {
+ auto ar = util::ArrayOfBaseObject::create();
+ for (const auto &idSrc : obj->identifiers()) {
+ const auto &srcAuthName = *(idSrc->codeSpace());
+ const auto &srcCode = idSrc->code();
+ auto idsProp = util::PropertyMap().set(
+ metadata::Identifier::CODESPACE_KEY, srcAuthName);
+ ar->add(metadata::Identifier::create(srcCode, idsProp));
+ }
+ if (!ar->empty()) {
+ map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar);
+ }
+ }
+
+ return map;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static util::PropertyMap
+createSimilarPropertiesTransformation(TransformationNNPtr obj) {
+ util::PropertyMap map;
+
+ // The domain(s) are unchanged
+ addDomains(map, obj.get());
+
+ std::string forwardName = obj->nameStr();
+ if (!forwardName.empty()) {
+ map.set(common::IdentifiedObject::NAME_KEY, forwardName);
+ }
+
+ addModifiedIdentifier(map, obj.get(), false, true);
+
+ return map;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static TransformationNNPtr
+createNTv1(const util::PropertyMap &properties,
+ const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
+ const std::string &filename,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ return Transformation::create(
+ properties, sourceCRSIn, targetCRSIn, nullptr,
+ createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV1),
+ {OperationParameter::create(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY,
+ EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)
+ .set(metadata::Identifier::CODESPACE_KEY,
+ metadata::Identifier::EPSG)
+ .set(metadata::Identifier::CODE_KEY,
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE))},
+ {ParameterValue::createFilename(filename)}, accuracies);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return an equivalent transformation to the current one, but using
+ * PROJ alternative grid names.
+ */
+TransformationNNPtr Transformation::substitutePROJAlternativeGridNames(
+ io::DatabaseContextNNPtr databaseContext) const {
+ auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>(
+ shared_from_this().as_nullable()));
+
+ const auto &l_method = method();
+ const int methodEPSGCode = l_method->getEPSGCode();
+
+ std::string projFilename;
+ std::string projGridFormat;
+ bool inverseDirection = false;
+
+ const auto &NTv1Filename = _getNTv1Filename(this, false);
+ const auto &NTv2Filename = _getNTv2Filename(this, false);
+ std::string lasFilename;
+ if (methodEPSGCode == EPSG_CODE_METHOD_NADCON) {
+ const auto &latitudeFileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE);
+ const auto &longitudeFileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE);
+ if (latitudeFileParameter &&
+ latitudeFileParameter->type() == ParameterValue::Type::FILENAME &&
+ longitudeFileParameter &&
+ longitudeFileParameter->type() == ParameterValue::Type::FILENAME) {
+ lasFilename = latitudeFileParameter->valueFile();
+ }
+ }
+ const auto &horizontalGridName =
+ !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty()
+ ? NTv2Filename
+ : lasFilename;
+
+ if (!horizontalGridName.empty() &&
+ databaseContext->lookForGridAlternative(horizontalGridName,
+ projFilename, projGridFormat,
+ inverseDirection)) {
+
+ if (horizontalGridName == projFilename) {
+ assert(!inverseDirection);
+ return self;
+ }
+
+ const auto &l_sourceCRS = sourceCRS();
+ const auto &l_targetCRS = targetCRS();
+ const auto &l_accuracies = coordinateOperationAccuracies();
+ if (projGridFormat == "NTv1") {
+ if (inverseDirection) {
+ return createNTv1(createPropertiesForInverse(
+ self.as_nullable().get(), true, false),
+ l_targetCRS, l_sourceCRS, projFilename,
+ l_accuracies)
+ ->inverseAsTransformation();
+ } else {
+ return createNTv1(createSimilarPropertiesTransformation(self),
+ l_sourceCRS, l_targetCRS, projFilename,
+ l_accuracies);
+ }
+ } else if (projGridFormat == "NTv2") {
+ if (inverseDirection) {
+ return createNTv2(createPropertiesForInverse(
+ self.as_nullable().get(), true, false),
+ l_targetCRS, l_sourceCRS, projFilename,
+ l_accuracies)
+ ->inverseAsTransformation();
+ } else {
+ return createNTv2(createSimilarPropertiesTransformation(self),
+ l_sourceCRS, l_targetCRS, projFilename,
+ l_accuracies);
+ }
+ } else if (projGridFormat == "CTable2") {
+ auto parameters =
+ std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)};
+ auto methodProperties =
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ PROJ_WKT2_NAME_METHOD_CTABLE2);
+ auto values = std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename(projFilename)};
+ if (inverseDirection) {
+ return create(createPropertiesForInverse(
+ self.as_nullable().get(), true, false),
+ l_targetCRS, l_sourceCRS, nullptr,
+ methodProperties, parameters, values,
+ l_accuracies)
+ ->inverseAsTransformation();
+
+ } else {
+ return create(createSimilarPropertiesTransformation(self),
+ l_sourceCRS, l_targetCRS, nullptr,
+ methodProperties, parameters, values,
+ l_accuracies);
+ }
+ }
+ }
+
+ const auto &heightFilename = getHeightToGeographic3DFilename();
+ if (!heightFilename.empty() && !projFilename.empty()) {
+ if (databaseContext->lookForGridAlternative(
+ heightFilename, projFilename, projGridFormat,
+ inverseDirection)) {
+
+ if (heightFilename == projFilename) {
+ assert(!inverseDirection);
+ return self;
+ }
+
+ if (inverseDirection) {
+ return createGravityRelatedHeightToGeographic3D(
+ createPropertiesForInverse(self.as_nullable().get(),
+ true, false),
+ targetCRS(), sourceCRS(), projFilename,
+ coordinateOperationAccuracies())
+ ->inverseAsTransformation();
+ } else {
+ return createGravityRelatedHeightToGeographic3D(
+ createSimilarPropertiesTransformation(self), sourceCRS(),
+ targetCRS(), projFilename, coordinateOperationAccuracies());
+ }
+ }
+ }
+
+ if (isGeographic3DToGravityRelatedHeight(method(), false)) {
+ const auto &fileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
+ EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ auto filename = fileParameter->valueFile();
+ if (databaseContext->lookForGridAlternative(
+ filename, projFilename, projGridFormat, inverseDirection)) {
+
+ if (filename == projFilename) {
+ assert(!inverseDirection);
+ return self;
+ }
+
+ auto parameters = std::vector<OperationParameterNNPtr>{
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)};
+ if (inverseDirection) {
+ return create(createPropertiesForInverse(
+ self.as_nullable().get(), true, false),
+ targetCRS(), sourceCRS(), nullptr,
+ createSimilarPropertiesMethod(method()),
+ parameters, {ParameterValue::createFilename(
+ projFilename)},
+ coordinateOperationAccuracies())
+ ->inverseAsTransformation();
+ } else {
+ return create(
+ createSimilarPropertiesTransformation(self),
+ sourceCRS(), targetCRS(), nullptr,
+ createSimilarPropertiesMethod(method()), parameters,
+ {ParameterValue::createFilename(projFilename)},
+ coordinateOperationAccuracies());
+ }
+ }
+ }
+ }
+
+ return self;
+}
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static void ThrowExpectionNotGeodeticGeographic(const char *trfrm_name) {
+ throw io::FormattingException(concat("Can apply ", std::string(trfrm_name),
+ " only to GeodeticCRS / "
+ "GeographicCRS"));
+}
+
+// ---------------------------------------------------------------------------
+
+static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter,
+ const crs::CRSNNPtr &crs,
+ const char *trfrm_name) {
+ auto sourceCRSGeog = dynamic_cast<const crs::GeographicCRS *>(crs.get());
+ if (sourceCRSGeog) {
+ formatter->startInversion();
+ sourceCRSGeog->_exportToPROJString(formatter);
+ formatter->stopInversion();
+
+ formatter->addStep("cart");
+ sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+ } else {
+ auto sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get());
+ if (!sourceCRSGeod) {
+ ThrowExpectionNotGeodeticGeographic(trfrm_name);
+ }
+ formatter->startInversion();
+ sourceCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter);
+ formatter->stopInversion();
+ }
+}
+// ---------------------------------------------------------------------------
+
+static void setupPROJGeodeticTargetCRS(io::PROJStringFormatter *formatter,
+ const crs::CRSNNPtr &crs,
+ const char *trfrm_name) {
+ auto targetCRSGeog = dynamic_cast<const crs::GeographicCRS *>(crs.get());
+ if (targetCRSGeog) {
+ formatter->addStep("cart");
+ formatter->setCurrentStepInverted(true);
+ targetCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+
+ targetCRSGeog->_exportToPROJString(formatter);
+ } else {
+ auto targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get());
+ if (!targetCRSGeod) {
+ ThrowExpectionNotGeodeticGeographic(trfrm_name);
+ }
+ targetCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter);
+ }
+}
+
+inline static void consume_unused(const std::string &) {}
+
+//! @endcond
+// ---------------------------------------------------------------------------
+
+void Transformation::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const // throw(FormattingException)
+{
+ if (formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4) {
+ throw io::FormattingException(
+ "Transformation cannot be exported as a PROJ.4 string");
+ }
+
+ bool positionVectorConvention = true;
+ bool sevenParamsTransform = false;
+ bool threeParamsTransform = false;
+ bool fifteenParamsTransform = false;
+ const auto &l_method = method();
+ const int methodEPSGCode = l_method->getEPSGCode();
+ const auto &methodName = l_method->nameStr();
+ const auto paramCount = parameterValues().size();
+ const bool l_isTimeDependent = isTimeDependent(methodName);
+ const bool isPositionVector =
+ ci_find(methodName, "Position Vector") != std::string::npos ||
+ ci_find(methodName, "PV") != std::string::npos;
+ const bool isCoordinateFrame =
+ ci_find(methodName, "Coordinate Frame") != std::string::npos ||
+ ci_find(methodName, "CF") != std::string::npos;
+ if ((paramCount == 7 && isCoordinateFrame && !l_isTimeDependent) ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) {
+ positionVectorConvention = false;
+ sevenParamsTransform = true;
+ } else if (
+ (paramCount == 15 && isCoordinateFrame && l_isTimeDependent) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) {
+ positionVectorConvention = false;
+ fifteenParamsTransform = true;
+ } else if ((paramCount == 7 && isPositionVector && !l_isTimeDependent) ||
+ methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
+ sevenParamsTransform = true;
+ } else if (
+ (paramCount == 15 && isPositionVector && l_isTimeDependent) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) {
+ fifteenParamsTransform = true;
+ } else if ((paramCount == 3 &&
+ ci_find(methodName, "Geocentric translations") !=
+ std::string::npos) ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
+ threeParamsTransform = true;
+ }
+ if (threeParamsTransform || sevenParamsTransform ||
+ fifteenParamsTransform) {
+ double x =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
+ double y =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
+ double z =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
+
+ setupPROJGeodeticSourceCRS(formatter, sourceCRS(), "Helmert");
+
+ formatter->addStep("helmert");
+ formatter->addParam("x", x);
+ formatter->addParam("y", y);
+ formatter->addParam("z", z);
+ if (sevenParamsTransform || fifteenParamsTransform) {
+ double rx =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND);
+ double ry =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND);
+ double rz =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND);
+ double scaleDiff =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
+ common::UnitOfMeasure::PARTS_PER_MILLION);
+ formatter->addParam("rx", rx);
+ formatter->addParam("ry", ry);
+ formatter->addParam("rz", rz);
+ formatter->addParam("s", scaleDiff);
+ if (fifteenParamsTransform) {
+ double rate_x = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION,
+ common::UnitOfMeasure::METRE_PER_YEAR);
+ double rate_y = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION,
+ common::UnitOfMeasure::METRE_PER_YEAR);
+ double rate_z = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION,
+ common::UnitOfMeasure::METRE_PER_YEAR);
+ double rate_rx = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
+ double rate_ry = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
+ double rate_rz = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
+ double rate_scaleDiff = parameterValueNumeric(
+ EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE,
+ common::UnitOfMeasure::PPM_PER_YEAR);
+ double referenceEpochYear =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH,
+ common::UnitOfMeasure::YEAR);
+ formatter->addParam("dx", rate_x);
+ formatter->addParam("dy", rate_y);
+ formatter->addParam("dz", rate_z);
+ formatter->addParam("drx", rate_rx);
+ formatter->addParam("dry", rate_ry);
+ formatter->addParam("drz", rate_rz);
+ formatter->addParam("ds", rate_scaleDiff);
+ formatter->addParam("t_epoch", referenceEpochYear);
+ }
+ if (positionVectorConvention) {
+ formatter->addParam("convention", "position_vector");
+ } else {
+ formatter->addParam("convention", "coordinate_frame");
+ }
+ }
+
+ setupPROJGeodeticTargetCRS(formatter, targetCRS(), "Helmert");
+
+ return;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC ||
+ methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D) {
+ consume_unused(EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC);
+ consume_unused(EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC);
+ consume_unused(EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D);
+ consume_unused(EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D);
+ consume_unused(EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D);
+ consume_unused(EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D);
+ positionVectorConvention =
+ isPositionVector ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D ||
+ methodEPSGCode ==
+ EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D;
+
+ double x =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
+ double y =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
+ double z =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
+ double rx = parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND);
+ double ry = parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND);
+ double rz = parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
+ common::UnitOfMeasure::ARC_SECOND);
+ double scaleDiff =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
+ common::UnitOfMeasure::PARTS_PER_MILLION);
+
+ double px = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT);
+ double py = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT);
+ double pz = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT);
+
+ setupPROJGeodeticSourceCRS(formatter, sourceCRS(),
+ "Molodensky-Badekas");
+
+ formatter->addStep("molobadekas");
+ formatter->addParam("x", x);
+ formatter->addParam("y", y);
+ formatter->addParam("z", z);
+ formatter->addParam("rx", rx);
+ formatter->addParam("ry", ry);
+ formatter->addParam("rz", rz);
+ formatter->addParam("s", scaleDiff);
+ formatter->addParam("px", px);
+ formatter->addParam("py", py);
+ formatter->addParam("pz", pz);
+ if (positionVectorConvention) {
+ formatter->addParam("convention", "position_vector");
+ } else {
+ formatter->addParam("convention", "coordinate_frame");
+ }
+
+ setupPROJGeodeticTargetCRS(formatter, targetCRS(),
+ "Molodensky-Badekas");
+
+ return;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY ||
+ methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
+ double x =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
+ double y =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
+ double z =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
+ double da = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE);
+ double df = parameterValueNumericAsSI(
+ EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE);
+
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ if (!sourceCRSGeog) {
+ throw io::FormattingException(
+ "Can apply Molodensky only to GeographicCRS");
+ }
+
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ if (!targetCRSGeog) {
+ throw io::FormattingException(
+ "Can apply Molodensky only to GeographicCRS");
+ }
+
+ formatter->startInversion();
+ sourceCRSGeog->_exportToPROJString(formatter);
+ formatter->stopInversion();
+
+ formatter->addStep("molodensky");
+ sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+ formatter->addParam("dx", x);
+ formatter->addParam("dy", y);
+ formatter->addParam("dz", z);
+ formatter->addParam("da", da);
+ formatter->addParam("df", df);
+
+ if (ci_find(methodName, "Abridged") != std::string::npos ||
+ methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
+ formatter->addParam("abridged");
+ }
+
+ targetCRSGeog->_exportToPROJString(formatter);
+
+ return;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) {
+ double offsetLat =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
+ common::UnitOfMeasure::ARC_SECOND);
+ double offsetLong =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
+ common::UnitOfMeasure::ARC_SECOND);
+
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ if (!sourceCRSGeog) {
+ throw io::FormattingException(
+ "Can apply Geographic 2D offsets only to GeographicCRS");
+ }
+
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ if (!targetCRSGeog) {
+ throw io::FormattingException(
+ "Can apply Geographic 2D offsets only to GeographicCRS");
+ }
+
+ formatter->startInversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ if (offsetLat != 0.0 || offsetLong != 0.0) {
+ formatter->addStep("geogoffset");
+ formatter->addParam("dlat", offsetLat);
+ formatter->addParam("dlon", offsetLong);
+ }
+
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+
+ return;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
+ double offsetLat =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
+ common::UnitOfMeasure::ARC_SECOND);
+ double offsetLong =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
+ common::UnitOfMeasure::ARC_SECOND);
+ double offsetHeight =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
+
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ if (!sourceCRSGeog) {
+ throw io::FormattingException(
+ "Can apply Geographic 3D offsets only to GeographicCRS");
+ }
+
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ if (!targetCRSGeog) {
+ throw io::FormattingException(
+ "Can apply Geographic 3D offsets only to GeographicCRS");
+ }
+
+ formatter->startInversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) {
+ formatter->addStep("geogoffset");
+ formatter->addParam("dlat", offsetLat);
+ formatter->addParam("dlon", offsetLong);
+ formatter->addParam("dh", offsetHeight);
+ }
+
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+
+ return;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) {
+ double offsetLat =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
+ common::UnitOfMeasure::ARC_SECOND);
+ double offsetLong =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
+ common::UnitOfMeasure::ARC_SECOND);
+ double offsetHeight =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_UNDULATION);
+
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ if (!sourceCRSGeog) {
+ auto sourceCRSCompound =
+ dynamic_cast<const crs::CompoundCRS *>(sourceCRS().get());
+ if (sourceCRSCompound) {
+ sourceCRSGeog = sourceCRSCompound->extractGeographicCRS().get();
+ }
+ if (!sourceCRSGeog) {
+ throw io::FormattingException("Can apply Geographic 2D with "
+ "height offsets only to "
+ "GeographicCRS / CompoundCRS");
+ }
+ }
+
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ if (!targetCRSGeog) {
+ auto targetCRSCompound =
+ dynamic_cast<const crs::CompoundCRS *>(targetCRS().get());
+ if (targetCRSCompound) {
+ targetCRSGeog = targetCRSCompound->extractGeographicCRS().get();
+ }
+ if (!targetCRSGeog) {
+ throw io::FormattingException("Can apply Geographic 2D with "
+ "height offsets only to "
+ "GeographicCRS / CompoundCRS");
+ }
+ }
+
+ formatter->startInversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) {
+ formatter->addStep("geogoffset");
+ formatter->addParam("dlat", offsetLat);
+ formatter->addParam("dlon", offsetLong);
+ formatter->addParam("dh", offsetHeight);
+ }
+
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+
+ return;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) {
+
+ auto sourceCRSVert =
+ dynamic_cast<const crs::VerticalCRS *>(sourceCRS().get());
+ if (!sourceCRSVert) {
+ throw io::FormattingException(
+ "Can apply Vertical offset only to VerticalCRS");
+ }
+
+ auto targetCRSVert =
+ dynamic_cast<const crs::VerticalCRS *>(targetCRS().get());
+ if (!targetCRSVert) {
+ throw io::FormattingException(
+ "Can apply Vertical offset only to VerticalCRS");
+ }
+
+ auto offsetHeight =
+ parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
+
+ formatter->startInversion();
+ sourceCRSVert->addLinearUnitConvert(formatter);
+ formatter->stopInversion();
+
+ formatter->addStep("geogoffset");
+ formatter->addParam("dh", offsetHeight);
+
+ targetCRSVert->addLinearUnitConvert(formatter);
+
+ return;
+ }
+
+ // Substitute grid names with PROJ friendly names.
+ if (formatter->databaseContext()) {
+ auto alternate = substitutePROJAlternativeGridNames(
+ NN_NO_CHECK(formatter->databaseContext()));
+ auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>(
+ shared_from_this().as_nullable()));
+
+ if (alternate != self) {
+ alternate->_exportToPROJString(formatter);
+ return;
+ }
+ }
+
+ const bool isMethodInverseOf = starts_with(methodName, INVERSE_OF);
+
+ const auto &NTv1Filename = _getNTv1Filename(this, true);
+ const auto &NTv2Filename = _getNTv2Filename(this, true);
+ const auto &CTABLE2Filename = _getCTABLE2Filename(this, true);
+ const auto &hGridShiftFilename =
+ !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty()
+ ? NTv2Filename
+ : CTABLE2Filename;
+ if (!hGridShiftFilename.empty()) {
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ if (!sourceCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName, " only to GeographicCRS"));
+ }
+
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ if (!targetCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName, " only to GeographicCRS"));
+ }
+
+ formatter->startInversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ if (isMethodInverseOf) {
+ formatter->startInversion();
+ }
+ formatter->addStep("hgridshift");
+ formatter->addParam("grids", hGridShiftFilename);
+ if (isMethodInverseOf) {
+ formatter->stopInversion();
+ }
+
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+
+ return;
+ }
+
+ const auto &heightFilename = _getHeightToGeographic3DFilename(this, true);
+ if (!heightFilename.empty()) {
+ if (isMethodInverseOf) {
+ formatter->startInversion();
+ }
+ formatter->addStep("vgridshift");
+ formatter->addParam("grids", heightFilename);
+ if (isMethodInverseOf) {
+ formatter->stopInversion();
+ }
+ return;
+ }
+
+ if (isGeographic3DToGravityRelatedHeight(method(), true)) {
+ auto fileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
+ EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ auto filename = fileParameter->valueFile();
+ if (isMethodInverseOf) {
+ formatter->startInversion();
+ }
+ formatter->addStep("vgridshift");
+ formatter->addParam("grids", filename);
+ if (isMethodInverseOf) {
+ formatter->stopInversion();
+ }
+ return;
+ }
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON) {
+ auto fileParameter =
+ parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE,
+ EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ formatter->addStep("vgridshift");
+ // The vertcon grids go from NGVD 29 to NAVD 88, with units
+ // in millimeter (see
+ // https://github.com/OSGeo/proj.4/issues/1071)
+ formatter->addParam("grids", fileParameter->valueFile());
+ formatter->addParam("multiplier", 0.001);
+ return;
+ }
+ }
+
+ if (isLongitudeRotation()) {
+ double offsetDeg =
+ parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
+ common::UnitOfMeasure::DEGREE);
+
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ if (!sourceCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName, " only to GeographicCRS"));
+ }
+
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ if (!targetCRSGeog) {
+ throw io::FormattingException(
+ concat("Can apply ", methodName + " only to GeographicCRS"));
+ }
+
+ if (!sourceCRSGeog->ellipsoid()->_isEquivalentTo(
+ targetCRSGeog->ellipsoid().get())) {
+ // This is arguable if we should check this...
+ throw io::FormattingException("Can apply Longitude rotation "
+ "only to SRS with same "
+ "ellipsoid");
+ }
+
+ formatter->startInversion();
+ sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ bool done = false;
+ if (offsetDeg != 0.0) {
+ // Optimization: as we are doing nominally a +step=inv,
+ // if the negation of the offset value is a well-known name,
+ // then use forward case with this name.
+ auto projPMName = datum::PrimeMeridian::getPROJStringWellKnownName(
+ common::Angle(-offsetDeg));
+ if (!projPMName.empty()) {
+ done = true;
+ formatter->addStep("longlat");
+ sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+ formatter->addParam("pm", projPMName);
+ }
+ }
+ if (!done) {
+ // To actually add the offset, we must use the reverse longlat
+ // operation.
+ formatter->startInversion();
+ formatter->addStep("longlat");
+ sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
+ datum::PrimeMeridian::create(util::PropertyMap(),
+ common::Angle(offsetDeg))
+ ->_exportToPROJString(formatter);
+ formatter->stopInversion();
+ }
+
+ targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
+
+ return;
+ }
+
+ if (exportToPROJStringGeneric(formatter)) {
+ return;
+ }
+
+ throw io::FormattingException("Unimplemented");
+}
+
+// ---------------------------------------------------------------------------
+
+bool SingleOperation::exportToPROJStringGeneric(
+ io::PROJStringFormatter *formatter) const {
+ const int methodEPSGCode = method()->getEPSGCode();
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION) {
+ const double A0 = parameterValueMeasure(EPSG_CODE_PARAMETER_A0).value();
+ const double A1 = parameterValueMeasure(EPSG_CODE_PARAMETER_A1).value();
+ const double A2 = parameterValueMeasure(EPSG_CODE_PARAMETER_A2).value();
+ const double B0 = parameterValueMeasure(EPSG_CODE_PARAMETER_B0).value();
+ const double B1 = parameterValueMeasure(EPSG_CODE_PARAMETER_B1).value();
+ const double B2 = parameterValueMeasure(EPSG_CODE_PARAMETER_B2).value();
+
+ // Do not mess with axis unit and order for that transformation
+
+ formatter->addStep("affine");
+ formatter->addParam("xoff", A0);
+ formatter->addParam("s11", A1);
+ formatter->addParam("s12", A2);
+ formatter->addParam("yoff", B0);
+ formatter->addParam("s21", B1);
+ formatter->addParam("s22", B2);
+
+ return true;
+ }
+
+ if (isAxisOrderReversal(methodEPSGCode)) {
+ formatter->addStep("axisswap");
+ formatter->addParam("order", "2,1");
+ return true;
+ }
+
+ if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) {
+
+ auto sourceCRSGeod =
+ dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get());
+ auto targetCRSGeod =
+ dynamic_cast<const crs::GeodeticCRS *>(targetCRS().get());
+ if (sourceCRSGeod && targetCRSGeod) {
+ auto sourceCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRSGeod);
+ auto targetCRSGeog =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRSGeod);
+ bool isSrcGeocentric = sourceCRSGeod->isGeocentric();
+ bool isSrcGeographic = sourceCRSGeog != nullptr;
+ bool isTargetGeocentric = targetCRSGeod->isGeocentric();
+ bool isTargetGeographic = targetCRSGeog != nullptr;
+ if ((isSrcGeocentric && isTargetGeographic) ||
+ (isSrcGeographic && isTargetGeocentric)) {
+
+ formatter->startInversion();
+ sourceCRSGeod->_exportToPROJString(formatter);
+ formatter->stopInversion();
+
+ targetCRSGeod->_exportToPROJString(formatter);
+
+ return true;
+ }
+ }
+
+ throw io::FormattingException("Invalid nature of source and/or "
+ "targetCRS for Geographic/Geocentric "
+ "conversion");
+ }
+
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+PointMotionOperation::~PointMotionOperation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct ConcatenatedOperation::Private {
+ std::vector<CoordinateOperationNNPtr> operations_{};
+ bool computedName_ = false;
+
+ explicit Private(const std::vector<CoordinateOperationNNPtr> &operationsIn)
+ : operations_(operationsIn) {}
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+ConcatenatedOperation::~ConcatenatedOperation() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+ConcatenatedOperation::ConcatenatedOperation(
+ const std::vector<CoordinateOperationNNPtr> &operationsIn)
+ : CoordinateOperation(), d(internal::make_unique<Private>(operationsIn)) {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the operation steps of the concatenated operation.
+ *
+ * @return the operation steps.
+ */
+const std::vector<CoordinateOperationNNPtr> &
+ConcatenatedOperation::operations() const {
+ return d->operations_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ConcatenatedOperation
+ *
+ * @param properties See \ref general_properties. At minimum the name should
+ * be
+ * defined.
+ * @param operationsIn Vector of the CoordinateOperation steps.
+ * @param accuracies Vector of positional accuracy (might be empty).
+ * @return new Transformation.
+ * @throws InvalidOperation
+ */
+ConcatenatedOperationNNPtr ConcatenatedOperation::create(
+ const util::PropertyMap &properties,
+ const std::vector<CoordinateOperationNNPtr> &operationsIn,
+ const std::vector<metadata::PositionalAccuracyNNPtr>
+ &accuracies) // throw InvalidOperation
+{
+ if (operationsIn.size() < 2) {
+ throw InvalidOperation(
+ "ConcatenatedOperation must have at least 2 operations");
+ }
+ crs::CRSPtr lastTargetCRS;
+ for (size_t i = 0; i < operationsIn.size(); i++) {
+ auto l_sourceCRS = operationsIn[i]->sourceCRS();
+ auto l_targetCRS = operationsIn[i]->targetCRS();
+ if (l_sourceCRS == nullptr || l_targetCRS == nullptr) {
+ throw InvalidOperation("At least one of the operation lacks a "
+ "source and/or target CRS");
+ }
+ if (i >= 1) {
+ const auto &sourceCRSIds = l_sourceCRS->identifiers();
+ const auto &targetCRSIds = lastTargetCRS->identifiers();
+ if (sourceCRSIds.size() == 1 && targetCRSIds.size() == 1 &&
+ sourceCRSIds[0]->code() == targetCRSIds[0]->code() &&
+ *sourceCRSIds[0]->codeSpace() ==
+ *targetCRSIds[0]->codeSpace()) {
+ // same id --> ok
+ } else if (!l_sourceCRS->_isEquivalentTo(
+ lastTargetCRS.get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ throw InvalidOperation(
+ "Inconsistent chaining of CRS in operations");
+ }
+ }
+ lastTargetCRS = l_targetCRS;
+ }
+ auto op = ConcatenatedOperation::nn_make_shared<ConcatenatedOperation>(
+ operationsIn);
+ op->assignSelf(op);
+ op->setProperties(properties);
+ op->setCRSs(NN_NO_CHECK(operationsIn[0]->sourceCRS()),
+ NN_NO_CHECK(operationsIn.back()->targetCRS()), nullptr);
+ op->setAccuracies(accuracies);
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static std::string computeConcatenatedName(
+ const std::vector<CoordinateOperationNNPtr> &flattenOps) {
+ std::string name;
+ for (const auto &subOp : flattenOps) {
+ if (!name.empty()) {
+ name += " + ";
+ }
+ const auto &l_name = subOp->nameStr();
+ if (l_name.empty()) {
+ name += "unnamed";
+ } else {
+ name += l_name;
+ }
+ }
+ return name;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a ConcatenatedOperation, or return a single
+ * coordinate
+ * operation.
+ *
+ * This computes its accuracy from the sum of its member operations, its
+ * extent
+ *
+ * @param operationsIn Vector of the CoordinateOperation steps.
+ * @param checkExtent Whether we should check the non-emptyness of the
+ * intersection
+ * of the extents of the operations
+ * @throws InvalidOperation
+ */
+CoordinateOperationNNPtr ConcatenatedOperation::createComputeMetadata(
+ const std::vector<CoordinateOperationNNPtr> &operationsIn,
+ bool checkExtent) // throw InvalidOperation
+{
+ util::PropertyMap properties;
+
+ if (operationsIn.size() == 1) {
+ return operationsIn[0];
+ }
+
+ std::vector<CoordinateOperationNNPtr> flattenOps;
+ for (const auto &subOp : operationsIn) {
+ auto subOpConcat =
+ dynamic_cast<const ConcatenatedOperation *>(subOp.get());
+ if (subOpConcat) {
+ auto subOps = subOpConcat->operations();
+ for (const auto &subSubOp : subOps) {
+ flattenOps.emplace_back(subSubOp);
+ }
+ } else {
+ flattenOps.emplace_back(subOp);
+ }
+ }
+ if (flattenOps.size() == 1) {
+ return flattenOps[0];
+ }
+
+ properties.set(common::IdentifiedObject::NAME_KEY,
+ computeConcatenatedName(flattenOps));
+
+ bool emptyIntersection = false;
+ auto extent = getExtent(flattenOps, false, emptyIntersection);
+ if (checkExtent && emptyIntersection) {
+ std::string msg(
+ "empty intersection of area of validity of concantenated "
+ "operations");
+ throw InvalidOperationEmptyIntersection(msg);
+ }
+ if (extent) {
+ properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ NN_NO_CHECK(extent));
+ }
+
+ std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
+ const double accuracy = getAccuracy(flattenOps);
+ if (accuracy >= 0.0) {
+ accuracies.emplace_back(
+ metadata::PositionalAccuracy::create(toString(accuracy)));
+ }
+
+ auto op = create(properties, flattenOps, accuracies);
+ op->d->computedName_ = true;
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr ConcatenatedOperation::inverse() const {
+ std::vector<CoordinateOperationNNPtr> inversedOperations;
+ auto l_operations = operations();
+ inversedOperations.reserve(l_operations.size());
+ for (const auto &operation : l_operations) {
+ inversedOperations.emplace_back(operation->inverse());
+ }
+ std::reverse(inversedOperations.begin(), inversedOperations.end());
+
+ auto properties = createPropertiesForInverse(this, false, false);
+ if (d->computedName_) {
+ properties.set(common::IdentifiedObject::NAME_KEY,
+ computeConcatenatedName(inversedOperations));
+ }
+
+ auto op =
+ create(properties, inversedOperations, coordinateOperationAccuracies());
+ op->d->computedName_ = d->computedName_;
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void ConcatenatedOperation::_exportToWKT(io::WKTFormatter *formatter) const {
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ if (!isWKT2 || !formatter->use2018Keywords()) {
+ throw io::FormattingException(
+ "Transformation can only be exported to WKT2:2018");
+ }
+
+ formatter->startNode(io::WKTConstants::CONCATENATEDOPERATION,
+ !identifiers().empty());
+ formatter->addQuotedString(nameStr());
+
+ formatter->startNode(io::WKTConstants::SOURCECRS, false);
+ sourceCRS()->_exportToWKT(formatter);
+ formatter->endNode();
+
+ formatter->startNode(io::WKTConstants::TARGETCRS, false);
+ targetCRS()->_exportToWKT(formatter);
+ formatter->endNode();
+
+ for (const auto &operation : operations()) {
+ formatter->startNode(io::WKTConstants::STEP, false);
+ operation->_exportToWKT(formatter);
+ formatter->endNode();
+ }
+
+ ObjectUsage::baseExportToWKT(formatter);
+ formatter->endNode();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+void ConcatenatedOperation::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const // throw(FormattingException)
+{
+ for (const auto &operation : operations()) {
+ operation->_exportToPROJString(formatter);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+bool ConcatenatedOperation::_isEquivalentTo(
+ const util::IComparable *other,
+ util::IComparable::Criterion criterion) const {
+ auto otherCO = dynamic_cast<const ConcatenatedOperation *>(other);
+ if (otherCO == nullptr || !ObjectUsage::_isEquivalentTo(other, criterion)) {
+ return false;
+ }
+ const auto &steps = operations();
+ const auto &otherSteps = otherCO->operations();
+ if (steps.size() != otherSteps.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < steps.size(); i++) {
+ if (!steps[i]->_isEquivalentTo(otherSteps[i].get(), criterion)) {
+ return false;
+ }
+ }
+ return true;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+std::set<GridDescription> ConcatenatedOperation::gridsNeeded(
+ const io::DatabaseContextPtr &databaseContext) const {
+ std::set<GridDescription> res;
+ for (const auto &operation : operations()) {
+ const auto l_gridsNeeded = operation->gridsNeeded(databaseContext);
+ for (const auto &gridDesc : l_gridsNeeded) {
+ res.insert(gridDesc);
+ }
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct CoordinateOperationContext::Private {
+ io::AuthorityFactoryPtr authorityFactory_{};
+ metadata::ExtentPtr extent_{};
+ double accuracy_ = 0.0;
+ SourceTargetCRSExtentUse sourceAndTargetCRSExtentUse_ =
+ CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST;
+ SpatialCriterion spatialCriterion_ =
+ CoordinateOperationContext::SpatialCriterion::STRICT_CONTAINMENT;
+ bool usePROJNames_ = true;
+ GridAvailabilityUse gridAvailabilityUse_ =
+ GridAvailabilityUse::USE_FOR_SORTING;
+ bool allowUseIntermediateCRS_ = true;
+ std::vector<std::pair<std::string, std::string>>
+ intermediateCRSAuthCodes_{};
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+CoordinateOperationContext::~CoordinateOperationContext() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationContext::CoordinateOperationContext()
+ : d(internal::make_unique<Private>()) {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the authority factory, or null */
+const io::AuthorityFactoryPtr &
+CoordinateOperationContext::getAuthorityFactory() const {
+ return d->authorityFactory_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the desired area of interest, or null */
+const metadata::ExtentPtr &
+CoordinateOperationContext::getAreaOfInterest() const {
+ return d->extent_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set the desired area of interest, or null */
+void CoordinateOperationContext::setAreaOfInterest(
+ const metadata::ExtentPtr &extent) {
+ d->extent_ = extent;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the desired accuracy (in metre), or 0 */
+double CoordinateOperationContext::getDesiredAccuracy() const {
+ return d->accuracy_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set the desired accuracy (in metre), or 0 */
+void CoordinateOperationContext::setDesiredAccuracy(double accuracy) {
+ d->accuracy_ = accuracy;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set how source and target CRS extent should be used
+ * when considering if a transformation can be used (only takes effect if
+ * no area of interest is explicitly defined).
+ *
+ * The default is
+ * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST.
+ */
+void CoordinateOperationContext::setSourceAndTargetCRSExtentUse(
+ SourceTargetCRSExtentUse use) {
+ d->sourceAndTargetCRSExtentUse_ = use;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return how source and target CRS extent should be used
+ * when considering if a transformation can be used (only takes effect if
+ * no area of interest is explicitly defined).
+ *
+ * The default is
+ * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST.
+ */
+CoordinateOperationContext::SourceTargetCRSExtentUse
+CoordinateOperationContext::getSourceAndTargetCRSExtentUse() const {
+ return d->sourceAndTargetCRSExtentUse_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set the spatial criterion to use when comparing the area of
+ * validity
+ * of coordinate operations with the area of interest / area of validity of
+ * source and target CRS.
+ *
+ * The default is STRICT_CONTAINMENT.
+ */
+void CoordinateOperationContext::setSpatialCriterion(
+ SpatialCriterion criterion) {
+ d->spatialCriterion_ = criterion;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the spatial criterion to use when comparing the area of
+ * validity
+ * of coordinate operations with the area of interest / area of validity of
+ * source and target CRS.
+ *
+ * The default is STRICT_CONTAINMENT.
+ */
+CoordinateOperationContext::SpatialCriterion
+CoordinateOperationContext::getSpatialCriterion() const {
+ return d->spatialCriterion_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether PROJ alternative grid names should be substituted to
+ * the official authority names.
+ *
+ * This only has effect is an authority factory with a non-null database context
+ * has been attached to this context.
+ *
+ * If set to false, it is still possible to
+ * obtain later the substitution by using io::PROJStringFormatter::create()
+ * with a non-null database context.
+ *
+ * The default is true.
+ */
+void CoordinateOperationContext::setUsePROJAlternativeGridNames(
+ bool usePROJNames) {
+ d->usePROJNames_ = usePROJNames;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether PROJ alternative grid names should be substituted to
+ * the official authority names.
+ *
+ * The default is true.
+ */
+bool CoordinateOperationContext::getUsePROJAlternativeGridNames() const {
+ return d->usePROJNames_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set how grid availability is used.
+ *
+ * The default is USE_FOR_SORTING.
+ */
+void CoordinateOperationContext::setGridAvailabilityUse(
+ GridAvailabilityUse use) {
+ d->gridAvailabilityUse_ = use;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return how grid availability is used.
+ *
+ * The default is USE_FOR_SORTING.
+ */
+CoordinateOperationContext::GridAvailabilityUse
+CoordinateOperationContext::getGridAvailabilityUse() const {
+ return d->gridAvailabilityUse_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether an intermediate pivot CRS can be used for researching
+ * coordinate operations between a source and target CRS.
+ *
+ * Concretely if in the database there is an operation from A to C
+ * (or C to A), and another one from C to B (or B to C), but no direct
+ * operation between A and B, setting this parameter to true, allow
+ * chaining both operations.
+ *
+ * The current implementation is limited to researching one intermediate
+ * step.
+ *
+ * By default, all potential C candidates will be used. setIntermediateCRS()
+ * can be used to restrict them.
+ *
+ * The default is true.
+ */
+void CoordinateOperationContext::setAllowUseIntermediateCRS(bool use) {
+ d->allowUseIntermediateCRS_ = use;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether an intermediate pivot CRS can be used for researching
+ * coordinate operations between a source and target CRS.
+ *
+ * Concretely if in the database there is an operation from A to C
+ * (or C to A), and another one from C to B (or B to C), but no direct
+ * operation between A and B, setting this parameter to true, allow
+ * chaining both operations.
+ *
+ * The default is true.
+ */
+bool CoordinateOperationContext::getAllowUseIntermediateCRS() const {
+ return d->allowUseIntermediateCRS_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Restrict the potential pivot CRSs that can be used when trying to
+ * build a coordinate operation between two CRS that have no direct operation.
+ *
+ * @param intermediateCRSAuthCodes a vector of (auth_name, code) that can be
+ * used as potential pivot RS
+ */
+void CoordinateOperationContext::setIntermediateCRS(
+ const std::vector<std::pair<std::string, std::string>>
+ &intermediateCRSAuthCodes) {
+ d->intermediateCRSAuthCodes_ = intermediateCRSAuthCodes;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the potential pivot CRSs that can be used when trying to
+ * build a coordinate operation between two CRS that have no direct operation.
+ *
+ */
+const std::vector<std::pair<std::string, std::string>> &
+CoordinateOperationContext::getIntermediateCRS() const {
+ return d->intermediateCRSAuthCodes_;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Creates a context for a coordinate operation.
+ *
+ * If a non null authorityFactory is provided, the resulting context should
+ * not be used simultaneously by more than one thread.
+ *
+ * @param authorityFactory Authority factory, or null if no database lookup
+ * is allowed.
+ * Use io::authorityFactory::create(context, std::string()) to allow all
+ * authorities to be used.
+ * @param extent Area of interest, or null if none is known.
+ * @param accuracy Maximum allowed accuracy in metre, as specified in or
+ * 0 to get best accuracy.
+ * @return a new context.
+ */
+CoordinateOperationContextNNPtr CoordinateOperationContext::create(
+ const io::AuthorityFactoryPtr &authorityFactory,
+ const metadata::ExtentPtr &extent, double accuracy) {
+ auto ctxt = NN_NO_CHECK(
+ CoordinateOperationContext::make_unique<CoordinateOperationContext>());
+ ctxt->d->authorityFactory_ = authorityFactory;
+ ctxt->d->extent_ = extent;
+ ctxt->d->accuracy_ = accuracy;
+ return ctxt;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct CoordinateOperationFactory::Private {
+
+ struct Context {
+ // This is the source CRS and target CRS of the initial
+ // CoordinateOperationFactory::createOperations() public call, not
+ // necessarily the ones of intermediate
+ // CoordinateOperationFactory::Private::createOperations() calls.
+ // This is used to compare transformations area of use against the
+ // area of use of the source & target CRS.
+ const crs::CRSNNPtr &sourceCRS;
+ const crs::CRSNNPtr &targetCRS;
+ const CoordinateOperationContextNNPtr &context;
+ bool inCreateOperationsWithDatumPivotAntiRecursion = false;
+
+ Context(const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn,
+ const CoordinateOperationContextNNPtr &contextIn)
+ : sourceCRS(sourceCRSIn), targetCRS(targetCRSIn),
+ context(contextIn) {}
+ };
+
+ static std::vector<CoordinateOperationNNPtr>
+ createOperations(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS, Context &context);
+
+ private:
+ static std::vector<CoordinateOperationNNPtr> createOperationsGeogToGeog(
+ std::vector<CoordinateOperationNNPtr> &res,
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst);
+
+ static void createOperationsWithDatumPivot(
+ std::vector<CoordinateOperationNNPtr> &res,
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::GeodeticCRS *geodSrc, const crs::GeodeticCRS *geodDst,
+ Context &context);
+
+ static bool
+ hasPerfectAccuracyResult(const std::vector<CoordinateOperationNNPtr> &res,
+ const Context &context);
+
+ static ConversionNNPtr
+ createGeographicGeocentric(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS);
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+CoordinateOperationFactory::~CoordinateOperationFactory() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationFactory::CoordinateOperationFactory() : d(nullptr) {}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Find a CoordinateOperation from sourceCRS to targetCRS.
+ *
+ * This is a helper of createOperations(), using a coordinate operation
+ * context
+ * with no authority factory (so no catalog searching is done), no desired
+ * accuracy and no area of interest.
+ * This returns the first operation of the result set of createOperations(),
+ * or null if none found.
+ *
+ * @param sourceCRS source CRS.
+ * @param targetCRS source CRS.
+ * @return a CoordinateOperation or nullptr.
+ */
+CoordinateOperationPtr CoordinateOperationFactory::createOperation(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) const {
+ auto res = createOperations(
+ sourceCRS, targetCRS,
+ CoordinateOperationContext::create(nullptr, nullptr, 0.0));
+ if (!res.empty()) {
+ return res[0];
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+struct PrecomputedOpCharacteristics {
+ double area_{};
+ double accuracy_{};
+ bool hasGrids_ = false;
+ bool gridsAvailable_ = false;
+ bool gridsKnown_ = false;
+ size_t stepCount_ = 0;
+
+ PrecomputedOpCharacteristics() = default;
+ PrecomputedOpCharacteristics(double area, double accuracy, bool hasGrids,
+ bool gridsAvailable, bool gridsKnown,
+ size_t stepCount)
+ : area_(area), accuracy_(accuracy), hasGrids_(hasGrids),
+ gridsAvailable_(gridsAvailable), gridsKnown_(gridsKnown),
+ stepCount_(stepCount) {}
+};
+
+// ---------------------------------------------------------------------------
+
+// We could have used a lambda instead of this old-school way, but
+// filterAndSort() is already huge.
+struct SortFunction {
+
+ const std::map<CoordinateOperation *, PrecomputedOpCharacteristics> &map;
+
+ explicit SortFunction(const std::map<CoordinateOperation *,
+ PrecomputedOpCharacteristics> &mapIn)
+ : map(mapIn) {}
+
+ // Sorting function
+ // Return true if a < b
+ bool operator()(const CoordinateOperationNNPtr &a,
+ const CoordinateOperationNNPtr &b) const {
+ auto iterA = map.find(a.get());
+ assert(iterA != map.end());
+ auto iterB = map.find(b.get());
+ assert(iterB != map.end());
+
+ // CAUTION: the order of the comparisons is extremely important
+ // to get the intended result.
+
+ if (iterA->second.hasGrids_ && iterB->second.hasGrids_) {
+ // Operations where grids are all available go before other
+ if (iterA->second.gridsAvailable_ &&
+ !iterB->second.gridsAvailable_) {
+ return true;
+ }
+ if (iterB->second.gridsAvailable_ &&
+ !iterA->second.gridsAvailable_) {
+ return false;
+ }
+ }
+
+ // Operations where grids are all known in our DB go before other
+ if (iterA->second.gridsKnown_ && !iterB->second.gridsKnown_) {
+ return true;
+ }
+ if (iterB->second.gridsKnown_ && !iterA->second.gridsKnown_) {
+ return false;
+ }
+
+ // Operations with known accuracy go before those with unknown accuracy
+ const double accuracyA = iterA->second.accuracy_;
+ const double accuracyB = iterB->second.accuracy_;
+ if (accuracyA >= 0 && accuracyB < 0) {
+ return true;
+ }
+ if (accuracyB >= 0 && accuracyA < 0) {
+ return false;
+ }
+
+ // Operations with larger non-zero area of use go before those with
+ // lower one
+ const double areaA = iterA->second.area_;
+ const double areaB = iterB->second.area_;
+ if (areaA > 0) {
+ if (areaA > areaB) {
+ return true;
+ }
+ if (areaA < areaB) {
+ return false;
+ }
+ } else if (areaB > 0) {
+ return false;
+ }
+
+ // Operations with better accuracy go before those with worse one
+ if (accuracyA >= 0 && accuracyA < accuracyB) {
+ return true;
+ }
+ if (accuracyB >= 0 && accuracyB < accuracyA) {
+ return false;
+ }
+
+ if (accuracyA >= 0 && accuracyA == accuracyB) {
+ // same accuracy ? then prefer operations without grids
+ if (!iterA->second.hasGrids_ && iterB->second.hasGrids_) {
+ return true;
+ }
+ if (iterA->second.hasGrids_ && !iterB->second.hasGrids_) {
+ return false;
+ }
+ } else if (accuracyA < 0 && accuracyB < 0) {
+ // unknown accuracy ? then prefer operations with grids, which
+ // are likely to have best practical accuracy
+ if (iterA->second.hasGrids_ && !iterB->second.hasGrids_) {
+ return true;
+ }
+ if (!iterA->second.hasGrids_ && iterB->second.hasGrids_) {
+ return false;
+ }
+ }
+
+ // The less intermediate steps, the better
+ if (iterA->second.stepCount_ < iterB->second.stepCount_) {
+ return true;
+ }
+ if (iterB->second.stepCount_ < iterA->second.stepCount_) {
+ return false;
+ }
+
+ const auto &a_name = a->nameStr();
+ const auto &b_name = b->nameStr();
+ // The shorter name, the better ?
+ if (a_name.size() < b_name.size()) {
+ return true;
+ }
+ if (b_name.size() < a_name.size()) {
+ return false;
+ }
+
+ // Arbitrary final criterion
+ return a_name < b_name;
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+static size_t getStepCount(const CoordinateOperationNNPtr &op) {
+ auto concat = dynamic_cast<const ConcatenatedOperation *>(op.get());
+ size_t stepCount = 1;
+ if (concat) {
+ stepCount = concat->operations().size();
+ }
+ return stepCount;
+}
+
+// ---------------------------------------------------------------------------
+
+struct FilterAndSort {
+
+ FilterAndSort(const std::vector<CoordinateOperationNNPtr> &sourceListIn,
+ const CoordinateOperationContextNNPtr &contextIn,
+ const crs::CRSNNPtr &sourceCRSIn,
+ const crs::CRSNNPtr &targetCRSIn,
+ bool forceStrictContainmentTest)
+ : sourceList(sourceListIn), context(contextIn), sourceCRS(sourceCRSIn),
+ targetCRS(targetCRSIn), sourceCRSExtent(getExtent(sourceCRS)),
+ targetCRSExtent(getExtent(targetCRS)),
+ areaOfInterest(context->getAreaOfInterest()),
+ desiredAccuracy(context->getDesiredAccuracy()),
+ sourceAndTargetCRSExtentUse(
+ context->getSourceAndTargetCRSExtentUse()) {
+
+ computeAreaOfIntest();
+ filterOut(forceStrictContainmentTest);
+ sort();
+
+ // And now that we have a sorted list, we can remove uninteresting
+ // results
+ // ...
+ removeSyntheticNullTransforms();
+ removeUninterestingOps();
+ removeDuplicateOps();
+ removeSyntheticNullTransforms();
+ }
+
+ // ----------------------------------------------------------------------
+
+ // cppcheck-suppress functionStatic
+ const std::vector<CoordinateOperationNNPtr> &getRes() { return res; }
+
+ // ----------------------------------------------------------------------
+ private:
+ const std::vector<CoordinateOperationNNPtr> &sourceList;
+ const CoordinateOperationContextNNPtr &context;
+ const crs::CRSNNPtr &sourceCRS;
+ const crs::CRSNNPtr &targetCRS;
+ const metadata::ExtentPtr &sourceCRSExtent;
+ const metadata::ExtentPtr &targetCRSExtent;
+ metadata::ExtentPtr areaOfInterest;
+ const double desiredAccuracy = context->getDesiredAccuracy();
+ const CoordinateOperationContext::SourceTargetCRSExtentUse
+ sourceAndTargetCRSExtentUse;
+
+ bool hasOpThatContainsAreaOfInterest = false;
+ std::vector<CoordinateOperationNNPtr> res{};
+
+ // ----------------------------------------------------------------------
+ void computeAreaOfIntest() {
+
+ // Compute an area of interest from the CRS extent if the user did
+ // not specify one
+ if (!areaOfInterest) {
+ if (sourceAndTargetCRSExtentUse ==
+ CoordinateOperationContext::SourceTargetCRSExtentUse::
+ INTERSECTION) {
+ if (sourceCRSExtent && targetCRSExtent) {
+ areaOfInterest = sourceCRSExtent->intersection(
+ NN_NO_CHECK(targetCRSExtent));
+ }
+ } else if (sourceAndTargetCRSExtentUse ==
+ CoordinateOperationContext::SourceTargetCRSExtentUse::
+ SMALLEST) {
+ if (sourceCRSExtent && targetCRSExtent) {
+ if (getPseudoArea(sourceCRSExtent) <
+ getPseudoArea(targetCRSExtent)) {
+ areaOfInterest = sourceCRSExtent;
+ } else {
+ areaOfInterest = targetCRSExtent;
+ }
+ } else if (sourceCRSExtent) {
+ areaOfInterest = sourceCRSExtent;
+ } else {
+ areaOfInterest = targetCRSExtent;
+ }
+ }
+ }
+ }
+
+ // ---------------------------------------------------------------------------
+
+ void filterOut(bool forceStrictContainmentTest) {
+
+ // Filter out operations that do not match the expected accuracy
+ // and area of use.
+ const auto spatialCriterion =
+ forceStrictContainmentTest
+ ? CoordinateOperationContext::SpatialCriterion::
+ STRICT_CONTAINMENT
+ : context->getSpatialCriterion();
+ for (const auto &op : sourceList) {
+ if (desiredAccuracy != 0) {
+ const double accuracy = getAccuracy(op);
+ if (accuracy < 0 || accuracy > desiredAccuracy) {
+ continue;
+ }
+ }
+ if (areaOfInterest) {
+ bool emptyIntersection = false;
+ auto extent = getExtent(op, true, emptyIntersection);
+ if (!extent)
+ continue;
+ bool extentContains =
+ extent->contains(NN_NO_CHECK(areaOfInterest));
+ if (extentContains) {
+ hasOpThatContainsAreaOfInterest = true;
+ }
+ if (spatialCriterion ==
+ CoordinateOperationContext::SpatialCriterion::
+ STRICT_CONTAINMENT &&
+ !extentContains) {
+ continue;
+ }
+ if (spatialCriterion ==
+ CoordinateOperationContext::SpatialCriterion::
+ PARTIAL_INTERSECTION &&
+ !extent->intersects(NN_NO_CHECK(areaOfInterest))) {
+ continue;
+ }
+ } else if (sourceAndTargetCRSExtentUse ==
+ CoordinateOperationContext::SourceTargetCRSExtentUse::
+ BOTH) {
+ bool emptyIntersection = false;
+ auto extent = getExtent(op, true, emptyIntersection);
+ if (!extent)
+ continue;
+ bool extentContainsSource =
+ !sourceCRSExtent ||
+ extent->contains(NN_NO_CHECK(sourceCRSExtent));
+ bool extentContainsTarget =
+ !targetCRSExtent ||
+ extent->contains(NN_NO_CHECK(targetCRSExtent));
+ if (extentContainsSource && extentContainsTarget) {
+ hasOpThatContainsAreaOfInterest = true;
+ }
+ if (spatialCriterion ==
+ CoordinateOperationContext::SpatialCriterion::
+ STRICT_CONTAINMENT) {
+ if (!extentContainsSource || !extentContainsTarget) {
+ continue;
+ }
+ } else if (spatialCriterion ==
+ CoordinateOperationContext::SpatialCriterion::
+ PARTIAL_INTERSECTION) {
+ bool extentIntersectsSource =
+ !sourceCRSExtent ||
+ extent->intersects(NN_NO_CHECK(sourceCRSExtent));
+ bool extentIntersectsTarget =
+ targetCRSExtent &&
+ extent->intersects(NN_NO_CHECK(targetCRSExtent));
+ if (!extentIntersectsSource || !extentIntersectsTarget) {
+ continue;
+ }
+ }
+ }
+ res.emplace_back(op);
+ }
+ }
+
+ // ----------------------------------------------------------------------
+
+ void sort() {
+
+ // Precompute a number of parameters for each operation that will be
+ // useful for the sorting.
+ std::map<CoordinateOperation *, PrecomputedOpCharacteristics> map;
+ for (const auto &op : res) {
+ bool dummy = false;
+ auto extentOp = getExtent(op, true, dummy);
+ double area = 0.0;
+ if (extentOp) {
+ if (areaOfInterest) {
+ area = getPseudoArea(
+ extentOp->intersection(NN_NO_CHECK(areaOfInterest)));
+ } else if (sourceCRSExtent && targetCRSExtent) {
+ auto x =
+ extentOp->intersection(NN_NO_CHECK(sourceCRSExtent));
+ auto y =
+ extentOp->intersection(NN_NO_CHECK(targetCRSExtent));
+ area = getPseudoArea(x) + getPseudoArea(y) -
+ ((x && y)
+ ? getPseudoArea(x->intersection(NN_NO_CHECK(y)))
+ : 0.0);
+ } else if (sourceCRSExtent) {
+ area = getPseudoArea(
+ extentOp->intersection(NN_NO_CHECK(sourceCRSExtent)));
+ } else if (targetCRSExtent) {
+ area = getPseudoArea(
+ extentOp->intersection(NN_NO_CHECK(targetCRSExtent)));
+ } else {
+ area = getPseudoArea(extentOp);
+ }
+ }
+
+ bool hasGrids = false;
+ bool gridsAvailable = true;
+ bool gridsKnown = true;
+ if (context->getAuthorityFactory() &&
+ context->getGridAvailabilityUse() ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ USE_FOR_SORTING) {
+ const auto gridsNeeded = op->gridsNeeded(
+ context->getAuthorityFactory()->databaseContext());
+ for (const auto &gridDesc : gridsNeeded) {
+ hasGrids = true;
+ if (!gridDesc.available) {
+ gridsAvailable = false;
+ }
+ if (gridDesc.packageName.empty()) {
+ gridsKnown = false;
+ }
+ }
+ }
+
+ const auto stepCount = getStepCount(op);
+
+ map[op.get()] = PrecomputedOpCharacteristics(
+ area, getAccuracy(op), hasGrids, gridsAvailable, gridsKnown,
+ stepCount);
+ }
+
+ // Sort !
+ std::sort(res.begin(), res.end(), SortFunction(map));
+ }
+
+ // ----------------------------------------------------------------------
+
+ void removeSyntheticNullTransforms() {
+
+ // If we have more than one result, and than the last result is the
+ // default "Null geographic offset" or "Null geocentric translation"
+ // operations we have synthetized, remove it as
+ // all previous results are necessarily better
+ if (hasOpThatContainsAreaOfInterest && res.size() > 1) {
+ const std::string &name = res.back()->nameStr();
+ if (name.find(NULL_GEOGRAPHIC_OFFSET) != std::string::npos ||
+ name.find(NULL_GEOCENTRIC_TRANSLATION) != std::string::npos) {
+ std::vector<CoordinateOperationNNPtr> resTemp;
+ for (size_t i = 0; i < res.size() - 1; i++) {
+ resTemp.emplace_back(res[i]);
+ }
+ res = std::move(resTemp);
+ }
+ }
+ }
+
+ // ----------------------------------------------------------------------
+
+ void removeUninterestingOps() {
+
+ // Eliminate operations that bring nothing, ie for a given area of use,
+ // do not keep operations that have greater accuracy. Actually we must
+ // be a bit more subtle than that, and take into account grid
+ // availability
+ std::vector<CoordinateOperationNNPtr> resTemp;
+ metadata::ExtentPtr lastExtent;
+ double lastAccuracy = -1;
+ bool lastHasGrids = false;
+ bool lastGridsAvailable = true;
+ std::set<std::set<std::string>> setOfSetOfGrids;
+ size_t lastStepCount = 0;
+ CoordinateOperationPtr lastOp;
+
+ bool first = true;
+ for (const auto &op : res) {
+ const auto curAccuracy = getAccuracy(op);
+ bool dummy = false;
+ const auto curExtent = getExtent(op, true, dummy);
+ bool curHasGrids = false;
+ bool curGridsAvailable = true;
+ std::set<std::string> curSetOfGrids;
+
+ const auto curStepCount = getStepCount(op);
+
+ if (context->getAuthorityFactory()) {
+ const auto gridsNeeded = op->gridsNeeded(
+ context->getAuthorityFactory()->databaseContext());
+ for (const auto &gridDesc : gridsNeeded) {
+ curHasGrids = true;
+ curSetOfGrids.insert(gridDesc.shortName);
+ if (!gridDesc.available) {
+ curGridsAvailable = false;
+ }
+ }
+ }
+
+ if (first) {
+ resTemp.emplace_back(op);
+
+ lastHasGrids = curHasGrids;
+ lastGridsAvailable = curGridsAvailable;
+ first = false;
+ } else {
+ if (lastOp->_isEquivalentTo(op.get())) {
+ continue;
+ }
+ const bool sameExtent =
+ ((!curExtent && !lastExtent) ||
+ (curExtent && lastExtent &&
+ curExtent->contains(NN_NO_CHECK(lastExtent)) &&
+ lastExtent->contains(NN_NO_CHECK(curExtent))));
+ if (((curAccuracy > lastAccuracy && lastAccuracy >= 0) ||
+ (curAccuracy < 0 && lastAccuracy >= 0)) &&
+ sameExtent) {
+ // If that set of grids has always been used for that
+ // extent,
+ // no need to add them again
+ if (setOfSetOfGrids.find(curSetOfGrids) !=
+ setOfSetOfGrids.end()) {
+ continue;
+ }
+ // If we have already found a operation without grids for
+ // that extent, no need to add any lower accuracy operation
+ if (!lastHasGrids) {
+ continue;
+ }
+ // If we had only operations involving grids, but one
+ // past operation had available grids, no need to add
+ // the new one.
+ if (curHasGrids && curGridsAvailable &&
+ lastGridsAvailable) {
+ continue;
+ }
+ } else if (curAccuracy == lastAccuracy && sameExtent) {
+ if (curStepCount > lastStepCount) {
+ continue;
+ }
+ }
+
+ resTemp.emplace_back(op);
+
+ if (sameExtent) {
+ if (!curHasGrids) {
+ lastHasGrids = false;
+ }
+ if (curGridsAvailable) {
+ lastGridsAvailable = true;
+ }
+ } else {
+ setOfSetOfGrids.clear();
+
+ lastHasGrids = curHasGrids;
+ lastGridsAvailable = curGridsAvailable;
+ }
+ }
+
+ lastOp = op.as_nullable();
+ lastStepCount = curStepCount;
+ lastExtent = curExtent;
+ lastAccuracy = curAccuracy;
+ if (!curSetOfGrids.empty()) {
+ setOfSetOfGrids.insert(curSetOfGrids);
+ }
+ }
+ res = std::move(resTemp);
+ }
+
+ // ----------------------------------------------------------------------
+
+ // cppcheck-suppress functionStatic
+ void removeDuplicateOps() {
+
+ // When going from EPSG:4807 (NTF Paris) to EPSG:4171 (RGC93), we get
+ // EPSG:7811, NTF (Paris) to RGF93 (2), 1 m
+ // and unknown id, NTF (Paris) to NTF (1) + Inverse of RGF93 to NTF (2),
+ // 1 m
+ // both have same PROJ string and extent
+ // Do not keep the later (that has more steps) as it adds no value.
+
+ std::set<std::string> setPROJPlusExtent;
+ std::vector<CoordinateOperationNNPtr> resTemp;
+ for (const auto &op : res) {
+ auto formatter = io::PROJStringFormatter::create();
+ try {
+ std::string key(op->exportToPROJString(formatter.get()));
+ bool dummy = false;
+ auto extentOp = getExtent(op, true, dummy);
+ if (extentOp) {
+ const auto &geogElts = extentOp->geographicElements();
+ if (geogElts.size() == 1) {
+ auto bbox = dynamic_cast<
+ const metadata::GeographicBoundingBox *>(
+ geogElts[0].get());
+ if (bbox) {
+ double w = bbox->westBoundLongitude();
+ double s = bbox->southBoundLatitude();
+ double e = bbox->eastBoundLongitude();
+ double n = bbox->northBoundLatitude();
+ key += "-";
+ key += toString(w);
+ key += "-";
+ key += toString(s);
+ key += "-";
+ key += toString(e);
+ key += "-";
+ key += toString(n);
+ }
+ }
+ }
+
+ if (setPROJPlusExtent.find(key) == setPROJPlusExtent.end()) {
+ resTemp.emplace_back(op);
+ setPROJPlusExtent.insert(key);
+ }
+ } catch (const std::exception &) {
+ resTemp.emplace_back(op);
+ }
+ }
+ res = std::move(resTemp);
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+/** \brief Filter operations and sort them given context.
+ *
+ * If a desired accuracy is specified, only keep operations whose accuracy
+ * is at least the desired one.
+ * If an area of interest is specified, only keep operations whose area of
+ * use include the area of interest.
+ * Then sort remaining operations by descending area of use, and increasing
+ * accuracy.
+ */
+static std::vector<CoordinateOperationNNPtr>
+filterAndSort(const std::vector<CoordinateOperationNNPtr> &sourceList,
+ const CoordinateOperationContextNNPtr &context,
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) {
+ return FilterAndSort(sourceList, context, sourceCRS, targetCRS, false)
+ .getRes();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+// Apply the inverse() method on all elements of the input list
+static std::vector<CoordinateOperationNNPtr>
+applyInverse(const std::vector<CoordinateOperationNNPtr> &list) {
+ auto res = list;
+ for (auto &op : res) {
+ op = op->inverse();
+ }
+ return res;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+// Look in the authority registry for operations from sourceCRS to targetCRS
+static std::vector<CoordinateOperationNNPtr>
+findOpsInRegistryDirect(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS,
+ const CoordinateOperationContextNNPtr &context) {
+ const auto &authFactory = context->getAuthorityFactory();
+ assert(authFactory);
+ for (const auto &idSrc : sourceCRS->identifiers()) {
+ const auto &srcAuthName = *(idSrc->codeSpace());
+ const auto &srcCode = idSrc->code();
+ if (!srcAuthName.empty()) {
+ for (const auto &idTarget : targetCRS->identifiers()) {
+ const auto &targetAuthName = *(idTarget->codeSpace());
+ const auto &targetCode = idTarget->code();
+ if (!targetAuthName.empty()) {
+ auto res =
+ authFactory->createFromCoordinateReferenceSystemCodes(
+ srcAuthName, srcCode, targetAuthName, targetCode,
+ context->getUsePROJAlternativeGridNames(),
+ context->getGridAvailabilityUse() ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID);
+ if (!res.empty()) {
+ return res;
+ }
+ }
+ }
+ }
+ }
+ return std::vector<CoordinateOperationNNPtr>();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// Look in the authority registry for operations from sourceCRS to targetCRS
+// using an intermediate pivot
+static std::vector<CoordinateOperationNNPtr> findsOpsInRegistryWithIntermediate(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const CoordinateOperationContextNNPtr &context) {
+ if (!context->getAllowUseIntermediateCRS()) {
+ return std::vector<CoordinateOperationNNPtr>();
+ }
+
+ const auto &authFactory = context->getAuthorityFactory();
+ assert(authFactory);
+ for (const auto &idSrc : sourceCRS->identifiers()) {
+ const auto &srcAuthName = *(idSrc->codeSpace());
+ const auto &srcCode = idSrc->code();
+ if (!srcAuthName.empty()) {
+ for (const auto &idTarget : targetCRS->identifiers()) {
+ const auto &targetAuthName = *(idTarget->codeSpace());
+ const auto &targetCode = idTarget->code();
+ if (!targetAuthName.empty()) {
+ auto res = authFactory->createFromCRSCodesWithIntermediates(
+ srcAuthName, srcCode, targetAuthName, targetCode,
+ context->getUsePROJAlternativeGridNames(),
+ context->getGridAvailabilityUse() ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID,
+ context->getIntermediateCRS());
+ if (!res.empty()) {
+ return res;
+ }
+ }
+ }
+ }
+ }
+ return std::vector<CoordinateOperationNNPtr>();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static TransformationNNPtr
+createNullGeographicOffset(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS) {
+ std::string name(NULL_GEOGRAPHIC_OFFSET);
+ name += " from ";
+ name += sourceCRS->nameStr();
+ name += " to ";
+ name += targetCRS->nameStr();
+
+ const auto &sourceCRSExtent = getExtent(sourceCRS);
+ const auto &targetCRSExtent = getExtent(targetCRS);
+ const bool sameExtent =
+ sourceCRSExtent && targetCRSExtent &&
+ sourceCRSExtent->_isEquivalentTo(
+ targetCRSExtent.get(), util::IComparable::Criterion::EQUIVALENT);
+
+ util::PropertyMap map;
+ map.set(common::IdentifiedObject::NAME_KEY, name)
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ sameExtent ? NN_NO_CHECK(sourceCRSExtent)
+ : metadata::Extent::WORLD);
+ const common::Angle angle0(0);
+ if (dynamic_cast<const crs::SingleCRS *>(sourceCRS.get())
+ ->coordinateSystem()
+ ->axisList()
+ .size() == 3 ||
+ dynamic_cast<const crs::SingleCRS *>(targetCRS.get())
+ ->coordinateSystem()
+ ->axisList()
+ .size() == 3) {
+ return Transformation::createGeographic3DOffsets(
+ map, sourceCRS, targetCRS, angle0, angle0, common::Length(0), {});
+ } else {
+ return Transformation::createGeographic2DOffsets(
+ map, sourceCRS, targetCRS, angle0, angle0, {});
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+struct MyPROJStringExportableGeodToGeod final
+ : public io::IPROJStringExportable {
+ crs::GeodeticCRSPtr geodSrc{};
+ crs::GeodeticCRSPtr geodDst{};
+
+ MyPROJStringExportableGeodToGeod(const crs::GeodeticCRSPtr &geodSrcIn,
+ const crs::GeodeticCRSPtr &geodDstIn)
+ : geodSrc(geodSrcIn), geodDst(geodDstIn) {}
+
+ ~MyPROJStringExportableGeodToGeod() override;
+
+ void
+ // cppcheck-suppress functionStatic
+ _exportToPROJString(io::PROJStringFormatter *formatter) const override {
+
+ formatter->startInversion();
+ geodSrc->_exportToPROJString(formatter);
+ formatter->stopInversion();
+ geodDst->_exportToPROJString(formatter);
+ }
+};
+
+MyPROJStringExportableGeodToGeod::~MyPROJStringExportableGeodToGeod() = default;
+
+// ---------------------------------------------------------------------------
+
+struct MyPROJStringExportableHorizVertical final
+ : public io::IPROJStringExportable {
+ CoordinateOperationPtr horizTransform{};
+ CoordinateOperationPtr verticalTransform{};
+ crs::GeographicCRSPtr geogDst{};
+
+ MyPROJStringExportableHorizVertical(
+ const CoordinateOperationPtr &horizTransformIn,
+ const CoordinateOperationPtr &verticalTransformIn,
+ const crs::GeographicCRSPtr &geogDstIn)
+ : horizTransform(horizTransformIn),
+ verticalTransform(verticalTransformIn), geogDst(geogDstIn) {}
+
+ ~MyPROJStringExportableHorizVertical() override;
+
+ void
+ // cppcheck-suppress functionStatic
+ _exportToPROJString(io::PROJStringFormatter *formatter) const override {
+
+ formatter->setOmitZUnitConversion(true);
+ horizTransform->_exportToPROJString(formatter);
+
+ formatter->startInversion();
+ geogDst->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+ formatter->setOmitZUnitConversion(false);
+
+ verticalTransform->_exportToPROJString(formatter);
+
+ formatter->setOmitZUnitConversion(true);
+ geogDst->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->setOmitZUnitConversion(false);
+ }
+};
+
+MyPROJStringExportableHorizVertical::~MyPROJStringExportableHorizVertical() =
+ default;
+
+// ---------------------------------------------------------------------------
+
+struct MyPROJStringExportableHorizVerticalHorizPROJBased final
+ : public io::IPROJStringExportable {
+ CoordinateOperationPtr opSrcCRSToGeogCRS{};
+ CoordinateOperationPtr verticalTransform{};
+ CoordinateOperationPtr opGeogCRStoDstCRS{};
+ crs::GeographicCRSPtr interpolationGeogCRS{};
+
+ MyPROJStringExportableHorizVerticalHorizPROJBased(
+ const CoordinateOperationPtr &opSrcCRSToGeogCRSIn,
+ const CoordinateOperationPtr &verticalTransformIn,
+ const CoordinateOperationPtr &opGeogCRStoDstCRSIn,
+ const crs::GeographicCRSPtr &interpolationGeogCRSIn)
+ : opSrcCRSToGeogCRS(opSrcCRSToGeogCRSIn),
+ verticalTransform(verticalTransformIn),
+ opGeogCRStoDstCRS(opGeogCRStoDstCRSIn),
+ interpolationGeogCRS(interpolationGeogCRSIn) {}
+
+ ~MyPROJStringExportableHorizVerticalHorizPROJBased() override;
+
+ void
+ // cppcheck-suppress functionStatic
+ _exportToPROJString(io::PROJStringFormatter *formatter) const override {
+
+ formatter->setOmitZUnitConversion(true);
+
+ opSrcCRSToGeogCRS->_exportToPROJString(formatter);
+
+ formatter->startInversion();
+ interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter);
+ formatter->stopInversion();
+
+ formatter->setOmitZUnitConversion(false);
+
+ verticalTransform->_exportToPROJString(formatter);
+
+ formatter->setOmitZUnitConversion(true);
+
+ interpolationGeogCRS->addAngularUnitConvertAndAxisSwap(formatter);
+
+ opGeogCRStoDstCRS->_exportToPROJString(formatter);
+
+ formatter->setOmitZUnitConversion(false);
+ }
+};
+
+MyPROJStringExportableHorizVerticalHorizPROJBased::
+ ~MyPROJStringExportableHorizVerticalHorizPROJBased() = default;
+}
+NS_PROJ_END
+
+#if 0
+namespace dropbox{ namespace oxygen {
+template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableGeodToGeod>>::~nn() = default;
+template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableHorizVertical>>::~nn() = default;
+template<> nn<std::shared_ptr<NS_PROJ::operation::MyPROJStringExportableHorizVerticalHorizPROJBased>>::~nn() = default;
+}}
+#endif
+
+NS_PROJ_START
+namespace operation {
+
+// ---------------------------------------------------------------------------
+
+static std::string buildTransfName(const std::string &srcName,
+ const std::string &targetName) {
+ std::string name("Transformation from ");
+ name += srcName;
+ name += " to ";
+ name += targetName;
+ return name;
+}
+
+// ---------------------------------------------------------------------------
+
+static CoordinateOperationNNPtr
+createGeodToGeodPROJBased(const crs::CRSNNPtr &geodSrc,
+ const crs::CRSNNPtr &geodDst) {
+
+ auto exportable = util::nn_make_shared<MyPROJStringExportableGeodToGeod>(
+ util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(geodSrc),
+ util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(geodDst));
+
+ auto properties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildTransfName(geodSrc->nameStr(), geodDst->nameStr()));
+ return createPROJBased(properties, exportable, geodSrc, geodDst);
+}
+
+// ---------------------------------------------------------------------------
+
+static CoordinateOperationNNPtr createHorizVerticalPROJBased(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const operation::CoordinateOperationNNPtr &horizTransform,
+ const operation::CoordinateOperationNNPtr &verticalTransform) {
+
+ auto geogDst = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(targetCRS);
+ assert(geogDst);
+
+ auto exportable = util::nn_make_shared<MyPROJStringExportableHorizVertical>(
+ horizTransform, verticalTransform, geogDst);
+
+ bool dummy = false;
+ auto ops = std::vector<CoordinateOperationNNPtr>{horizTransform,
+ verticalTransform};
+ auto extent = getExtent(ops, true, dummy);
+ auto properties = util::PropertyMap();
+ properties.set(common::IdentifiedObject::NAME_KEY,
+ computeConcatenatedName(ops));
+
+ if (extent) {
+ properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ NN_NO_CHECK(extent));
+ }
+
+ std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
+ const double accuracy = getAccuracy(ops);
+ if (accuracy >= 0.0) {
+ accuracies.emplace_back(
+ metadata::PositionalAccuracy::create(toString(accuracy)));
+ }
+
+ return createPROJBased(properties, exportable, sourceCRS, targetCRS,
+ accuracies);
+}
+
+// ---------------------------------------------------------------------------
+
+static CoordinateOperationNNPtr createHorizVerticalHorizPROJBased(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const operation::CoordinateOperationNNPtr &opSrcCRSToGeogCRS,
+ const operation::CoordinateOperationNNPtr &verticalTransform,
+ const operation::CoordinateOperationNNPtr &opGeogCRStoDstCRS,
+ const crs::GeographicCRSPtr &interpolationGeogCRS) {
+
+ auto exportable =
+ util::nn_make_shared<MyPROJStringExportableHorizVerticalHorizPROJBased>(
+ opSrcCRSToGeogCRS, verticalTransform, opGeogCRStoDstCRS,
+ interpolationGeogCRS);
+
+ bool dummy = false;
+ auto ops = std::vector<CoordinateOperationNNPtr>{
+ opSrcCRSToGeogCRS, verticalTransform, opGeogCRStoDstCRS};
+ auto extent = getExtent(ops, true, dummy);
+ auto properties = util::PropertyMap();
+ properties.set(common::IdentifiedObject::NAME_KEY,
+ computeConcatenatedName(ops));
+
+ if (extent) {
+ properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ NN_NO_CHECK(extent));
+ }
+
+ std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
+ const double accuracy = getAccuracy(ops);
+ if (accuracy >= 0.0) {
+ accuracies.emplace_back(
+ metadata::PositionalAccuracy::create(toString(accuracy)));
+ }
+
+ return createPROJBased(properties, exportable, sourceCRS, targetCRS,
+ accuracies);
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+ConversionNNPtr CoordinateOperationFactory::Private::createGeographicGeocentric(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) {
+ auto properties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildOpName("Conversion", sourceCRS, targetCRS));
+ auto conv = Conversion::createGeographicGeocentric(properties);
+ conv->setCRSs(sourceCRS, targetCRS, nullptr);
+ return conv;
+}
+
+// ---------------------------------------------------------------------------
+
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::Private::createOperationsGeogToGeog(
+ std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS, const crs::GeographicCRS *geogSrc,
+ const crs::GeographicCRS *geogDst) {
+
+ assert(sourceCRS.get() == geogSrc);
+ assert(targetCRS.get() == geogDst);
+ const bool allowEmptyIntersection = true;
+
+ const auto &src_pm = geogSrc->primeMeridian()->longitude();
+ const auto &dst_pm = geogDst->primeMeridian()->longitude();
+ auto offset_pm =
+ (src_pm.unit() == dst_pm.unit())
+ ? common::Angle(src_pm.value() - dst_pm.value(), src_pm.unit())
+ : common::Angle(
+ src_pm.convertToUnit(common::UnitOfMeasure::DEGREE) -
+ dst_pm.convertToUnit(common::UnitOfMeasure::DEGREE),
+ common::UnitOfMeasure::DEGREE);
+
+ double vconvSrc = 1.0;
+ const auto &srcCS = geogSrc->coordinateSystem();
+ const auto &srcAxisList = srcCS->axisList();
+ if (srcAxisList.size() == 3) {
+ vconvSrc = srcAxisList[2]->unit().conversionToSI();
+ }
+ double vconvDst = 1.0;
+ const auto &dstCS = geogDst->coordinateSystem();
+ const auto &dstAxisList = dstCS->axisList();
+ if (dstAxisList.size() == 3) {
+ vconvDst = dstAxisList[2]->unit().conversionToSI();
+ }
+
+ std::string name(buildTransfName(geogSrc->nameStr(), geogDst->nameStr()));
+
+ // Do they differ by vertical units ?
+ if (vconvSrc != vconvDst &&
+ geogSrc->ellipsoid()->_isEquivalentTo(
+ geogDst->ellipsoid().get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ if (offset_pm.value() == 0) {
+ // If only by vertical units, use a Change of Vertical
+ // Unit
+ // transformation
+ const double factor = vconvSrc / vconvDst;
+ auto conv = Conversion::createChangeVerticalUnit(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ name),
+ common::Scale(factor));
+ conv->setCRSs(sourceCRS, targetCRS, nullptr);
+ res.push_back(conv);
+ return res;
+ } else {
+ res.emplace_back(createGeodToGeodPROJBased(sourceCRS, targetCRS));
+ return res;
+ }
+ }
+
+ // Do the CRS differ only by their axis order ?
+ if (geogSrc->datum() != nullptr && geogDst->datum() != nullptr &&
+ geogSrc->datum()->_isEquivalentTo(
+ geogDst->datum().get(), util::IComparable::Criterion::EQUIVALENT) &&
+ !srcCS->_isEquivalentTo(dstCS.get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ auto srcOrder = srcCS->axisOrder();
+ auto dstOrder = dstCS->axisOrder();
+ if ((srcOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST &&
+ dstOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH) ||
+ (srcOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH &&
+ dstOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST)) {
+ auto conv = Conversion::createAxisOrderReversal(false);
+ conv->setCRSs(sourceCRS, targetCRS, nullptr);
+ res.emplace_back(conv);
+ return res;
+ }
+ if ((srcOrder ==
+ cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST_HEIGHT_UP &&
+ dstOrder ==
+ cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH_HEIGHT_UP) ||
+ (srcOrder ==
+ cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH_HEIGHT_UP &&
+ dstOrder ==
+ cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST_HEIGHT_UP)) {
+ auto conv = Conversion::createAxisOrderReversal(true);
+ conv->setCRSs(sourceCRS, targetCRS, nullptr);
+ res.emplace_back(conv);
+ return res;
+ }
+ }
+
+ std::vector<CoordinateOperationNNPtr> steps;
+ // If both are geographic and only differ by their prime
+ // meridian,
+ // apply a longitude rotation transformation.
+ if (geogSrc->ellipsoid()->_isEquivalentTo(
+ geogDst->ellipsoid().get(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ src_pm.getSIValue() != dst_pm.getSIValue()) {
+
+ steps.emplace_back(Transformation::createLongitudeRotation(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, name)
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ sourceCRS, targetCRS, offset_pm));
+ // If only the target has a non-zero prime meridian, chain a
+ // null geographic offset and then the longitude rotation
+ } else if (src_pm.getSIValue() == 0 && dst_pm.getSIValue() != 0) {
+ auto datum = datum::GeodeticReferenceFrame::create(
+ util::PropertyMap(), geogDst->ellipsoid(),
+ util::optional<std::string>(), geogSrc->primeMeridian());
+ std::string interm_crs_name(geogDst->nameStr());
+ interm_crs_name += " altered to use prime meridian of ";
+ interm_crs_name += geogSrc->nameStr();
+ auto interm_crs =
+ util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, interm_crs_name)
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ datum, dstCS));
+
+ steps.emplace_back(createNullGeographicOffset(sourceCRS, interm_crs));
+
+ steps.emplace_back(Transformation::createLongitudeRotation(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY,
+ buildTransfName(geogSrc->nameStr(), interm_crs->nameStr()))
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ interm_crs, targetCRS, offset_pm));
+
+ } else {
+ // If the prime meridians are different, chain a longitude
+ // rotation and the null geographic offset.
+ if (src_pm.getSIValue() != dst_pm.getSIValue()) {
+ auto datum = datum::GeodeticReferenceFrame::create(
+ util::PropertyMap(), geogSrc->ellipsoid(),
+ util::optional<std::string>(), geogDst->primeMeridian());
+ std::string interm_crs_name(geogSrc->nameStr());
+ interm_crs_name += " altered to use prime meridian of ";
+ interm_crs_name += geogDst->nameStr();
+ auto interm_crs = util::nn_static_pointer_cast<crs::CRS>(
+ crs::GeographicCRS::create(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ interm_crs_name),
+ datum, srcCS));
+
+ steps.emplace_back(Transformation::createLongitudeRotation(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY,
+ buildTransfName(geogSrc->nameStr(),
+ interm_crs->nameStr()))
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ sourceCRS, interm_crs, offset_pm));
+ steps.emplace_back(
+ createNullGeographicOffset(interm_crs, targetCRS));
+ } else {
+ steps.emplace_back(
+ createNullGeographicOffset(sourceCRS, targetCRS));
+ }
+ }
+
+ res.emplace_back(ConcatenatedOperation::createComputeMetadata(
+ steps, !allowEmptyIntersection));
+ return res;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static bool hasIdentifiers(const CoordinateOperationNNPtr &op) {
+ if (!op->identifiers().empty()) {
+ return true;
+ }
+ auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get());
+ if (concatenated) {
+ for (const auto &subOp : concatenated->operations()) {
+ if (hasIdentifiers(subOp)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static std::vector<crs::CRSNNPtr>
+findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory,
+ const datum::GeodeticReferenceFramePtr &datum) {
+ std::vector<crs::CRSNNPtr> candidates;
+ for (const auto &id : datum->identifiers()) {
+ const auto &authName = *(id->codeSpace());
+ const auto &code = id->code();
+ if (!authName.empty()) {
+ auto l_candidates = authFactory->createGeodeticCRSFromDatum(
+ authName, code, std::string());
+ for (const auto &candidate : l_candidates) {
+ candidates.emplace_back(candidate);
+ }
+ }
+ }
+ return candidates;
+}
+
+// ---------------------------------------------------------------------------
+
+static bool isNullTransformation(const std::string &name) {
+
+ return starts_with(name, NULL_GEOCENTRIC_TRANSLATION) ||
+ starts_with(name, NULL_GEOGRAPHIC_OFFSET);
+}
+
+// ---------------------------------------------------------------------------
+
+void CoordinateOperationFactory::Private::createOperationsWithDatumPivot(
+ std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS, const crs::GeodeticCRS *geodSrc,
+ const crs::GeodeticCRS *geodDst, Private::Context &context) {
+
+ const bool allowEmptyIntersection = true;
+
+ struct CreateOperationsWithDatumPivotAntiRecursion {
+ Context &context;
+
+ explicit CreateOperationsWithDatumPivotAntiRecursion(Context &contextIn)
+ : context(contextIn) {
+ assert(!context.inCreateOperationsWithDatumPivotAntiRecursion);
+ context.inCreateOperationsWithDatumPivotAntiRecursion = true;
+ }
+
+ ~CreateOperationsWithDatumPivotAntiRecursion() {
+ context.inCreateOperationsWithDatumPivotAntiRecursion = false;
+ }
+ };
+ CreateOperationsWithDatumPivotAntiRecursion guard(context);
+
+ const auto &authFactory = context.context->getAuthorityFactory();
+ const auto candidatesSrcGeod(
+ findCandidateGeodCRSForDatum(authFactory, geodSrc->datum()));
+ const auto candidatesDstGeod(
+ findCandidateGeodCRSForDatum(authFactory, geodDst->datum()));
+
+ for (const auto &candidateSrcGeod : candidatesSrcGeod) {
+ const auto opsFirst =
+ createOperations(sourceCRS, candidateSrcGeod, context);
+ assert(!opsFirst.empty());
+ const bool isNullFirst = isNullTransformation(opsFirst[0]->nameStr());
+
+ for (const auto &candidateDstGeod : candidatesDstGeod) {
+ const auto opsSecond =
+ createOperations(candidateSrcGeod, candidateDstGeod, context);
+ const auto opsThird =
+ createOperations(candidateDstGeod, targetCRS, context);
+ assert(!opsThird.empty());
+
+ for (auto &opSecond : opsSecond) {
+ // Check that it is not a transformation synthetized by
+ // ourselves
+ if (!hasIdentifiers(opSecond)) {
+ continue;
+ }
+ // And even if it is a referenced transformation, check that
+ // it is not a trivial one
+ auto so = dynamic_cast<const SingleOperation *>(opSecond.get());
+ if (so && isAxisOrderReversal(so->method()->getEPSGCode())) {
+ continue;
+ }
+
+ std::vector<CoordinateOperationNNPtr> subOps;
+ if (isNullFirst) {
+ opSecond->setCRSs(sourceCRS,
+ NN_CHECK_ASSERT(opSecond->targetCRS()),
+ nullptr);
+ } else {
+ subOps.emplace_back(opsFirst[0]);
+ }
+ if (isNullTransformation(opsThird[0]->nameStr())) {
+ opSecond->setCRSs(NN_CHECK_ASSERT(opSecond->sourceCRS()),
+ targetCRS, nullptr);
+ subOps.emplace_back(opSecond);
+ } else {
+ subOps.emplace_back(opSecond);
+ subOps.emplace_back(opsThird[0]);
+ }
+ res.emplace_back(ConcatenatedOperation::createComputeMetadata(
+ subOps, !allowEmptyIntersection));
+ }
+ }
+ if (!res.empty()) {
+ return;
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static CoordinateOperationNNPtr
+createNullGeocentricTranslation(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS) {
+ std::string name(NULL_GEOCENTRIC_TRANSLATION);
+ name += " from ";
+ name += sourceCRS->nameStr();
+ name += " to ";
+ name += targetCRS->nameStr();
+
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ Transformation::createGeocentricTranslations(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, name)
+ .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ metadata::Extent::WORLD),
+ sourceCRS, targetCRS, 0.0, 0.0, 0.0, {}));
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+bool CoordinateOperationFactory::Private::hasPerfectAccuracyResult(
+ const std::vector<CoordinateOperationNNPtr> &res, const Context &context) {
+ auto resTmp = FilterAndSort(res, context.context, context.sourceCRS,
+ context.targetCRS, true)
+ .getRes();
+ for (const auto &op : resTmp) {
+ const double acc = getAccuracy(op);
+ if (acc == 0.0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::Private::createOperations(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ Private::Context &context) {
+
+ std::vector<CoordinateOperationNNPtr> res;
+ const bool allowEmptyIntersection = true;
+
+ auto geodSrc = dynamic_cast<const crs::GeodeticCRS *>(sourceCRS.get());
+ auto geodDst = dynamic_cast<const crs::GeodeticCRS *>(targetCRS.get());
+
+ // First look-up if the registry provide us with operations.
+ auto derivedSrc = dynamic_cast<const crs::DerivedCRS *>(sourceCRS.get());
+ auto derivedDst = dynamic_cast<const crs::DerivedCRS *>(targetCRS.get());
+ if (context.context->getAuthorityFactory() &&
+ (derivedSrc == nullptr ||
+ !derivedSrc->baseCRS()->_isEquivalentTo(
+ targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) &&
+ (derivedDst == nullptr ||
+ !derivedDst->baseCRS()->_isEquivalentTo(
+ sourceCRS.get(), util::IComparable::Criterion::EQUIVALENT))) {
+
+ bool doFilterAndCheckPerfectOp = true;
+ res = findOpsInRegistryDirect(sourceCRS, targetCRS, context.context);
+ if (!sourceCRS->_isEquivalentTo(targetCRS.get())) {
+ auto resFromInverse = applyInverse(
+ findOpsInRegistryDirect(targetCRS, sourceCRS, context.context));
+ res.insert(res.end(), resFromInverse.begin(), resFromInverse.end());
+
+ // If we get at least a result with perfect accuracy, do not
+ // bother generating synthetic transforms.
+ if (hasPerfectAccuracyResult(res, context)) {
+ return res;
+ }
+
+ doFilterAndCheckPerfectOp = false;
+
+ // NAD27 to NAD83 has tens of results already. No need to look
+ // for a pivot
+ if (res.size() < 5 || getenv("PROJ_FORCE_SEARCH_PIVOT")) {
+ auto resWithIntermediate = findsOpsInRegistryWithIntermediate(
+ sourceCRS, targetCRS, context.context);
+ res.insert(res.end(), resWithIntermediate.begin(),
+ resWithIntermediate.end());
+ doFilterAndCheckPerfectOp = true;
+ }
+ }
+
+ if (res.empty() &&
+ !context.inCreateOperationsWithDatumPivotAntiRecursion && geodSrc &&
+ geodDst) {
+ // If we still didn't find a transformation, and that the source
+ // and target are GeodeticCRS, then go through their underlying
+ // datum to find potential transformations between other GeodeticRSs
+ // that are made of those datum
+ // The typical example is if transforming between two GeographicCRS,
+ // but transformations are only available between their
+ // corresponding geocentric CRS.
+ const auto &srcDatum = geodSrc->datum();
+ const bool srcHasDatumWithId =
+ srcDatum && !srcDatum->identifiers().empty();
+ const auto &dstDatum = geodDst->datum();
+ const bool dstHasDatumWithId =
+ dstDatum && !dstDatum->identifiers().empty();
+ if (srcHasDatumWithId && dstHasDatumWithId) {
+ createOperationsWithDatumPivot(res, sourceCRS, targetCRS,
+ geodSrc, geodDst, context);
+ doFilterAndCheckPerfectOp = !res.empty();
+ }
+ }
+
+ if (doFilterAndCheckPerfectOp) {
+ // If we get at least a result with perfect accuracy, do not bother
+ // generating synthetic transforms.
+ if (hasPerfectAccuracyResult(res, context)) {
+ return res;
+ }
+ }
+ }
+
+ // Special case if both CRS are geodetic
+ if (geodSrc && geodDst && !derivedSrc && !derivedDst) {
+
+ if (geodSrc->ellipsoid()->celestialBody() !=
+ geodDst->ellipsoid()->celestialBody()) {
+ throw util::UnsupportedOperationException(
+ "Source and target ellipsoid do not belong to the same "
+ "celestial body");
+ }
+
+ auto geogSrc =
+ dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
+ auto geogDst =
+ dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
+ if (geogSrc && geogDst) {
+ return createOperationsGeogToGeog(res, sourceCRS, targetCRS,
+ geogSrc, geogDst);
+ }
+
+ const bool isSrcGeocentric = geodSrc->isGeocentric();
+ const bool isSrcGeographic = geogSrc != nullptr;
+ const bool isTargetGeocentric = geodDst->isGeocentric();
+ const bool isTargetGeographic = geogDst != nullptr;
+ if (((isSrcGeocentric && isTargetGeographic) ||
+ (isSrcGeographic && isTargetGeocentric)) &&
+ geodSrc->datum() != nullptr && geodDst->datum() != nullptr) {
+
+ // Same datum ?
+ if (geodSrc->datum()->_isEquivalentTo(
+ geodDst->datum().get(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ res.emplace_back(
+ createGeographicGeocentric(sourceCRS, targetCRS));
+ } else if (isSrcGeocentric) {
+ std::string interm_crs_name(geogDst->nameStr());
+ interm_crs_name += " (geocentric)";
+ auto interm_crs = util::nn_static_pointer_cast<crs::CRS>(
+ crs::GeodeticCRS::create(
+ addDomains(util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ interm_crs_name),
+ geogDst),
+ NN_NO_CHECK(geogDst->datum()),
+ NN_CHECK_ASSERT(
+ util::nn_dynamic_pointer_cast<cs::CartesianCS>(
+ geodSrc->coordinateSystem()))));
+ auto opFirst =
+ createNullGeocentricTranslation(sourceCRS, interm_crs);
+ auto opSecond =
+ createGeographicGeocentric(interm_crs, targetCRS);
+ res.emplace_back(ConcatenatedOperation::createComputeMetadata(
+ {opFirst, opSecond}, !allowEmptyIntersection));
+ } else {
+ return applyInverse(
+ createOperations(targetCRS, sourceCRS, context));
+ }
+
+ return res;
+ }
+
+ if (isSrcGeocentric && isTargetGeocentric) {
+ res.emplace_back(
+ createNullGeocentricTranslation(sourceCRS, targetCRS));
+ return res;
+ }
+
+ // Tranformation between two geodetic systems of unknown type
+ // This should normally not be triggered with "standard" CRS
+ res.emplace_back(createGeodToGeodPROJBased(sourceCRS, targetCRS));
+ return res;
+ }
+
+ // If the source is a derived CRS, then chain the inverse of its
+ // deriving conversion, with transforms from its baseCRS to the
+ // targetCRS
+ if (derivedSrc) {
+ auto opFirst = derivedSrc->derivingConversion()->inverse();
+ // Small optimization if the targetCRS is the baseCRS of the source
+ // derivedCRS.
+ if (derivedSrc->baseCRS()->_isEquivalentTo(
+ targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ res.emplace_back(opFirst);
+ return res;
+ }
+ auto opsSecond =
+ createOperations(derivedSrc->baseCRS(), targetCRS, context);
+ for (const auto &opSecond : opsSecond) {
+ try {
+ res.emplace_back(ConcatenatedOperation::createComputeMetadata(
+ {opFirst, opSecond}, !allowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ return res;
+ }
+
+ // reverse of previous case
+ if (derivedDst) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ // boundCRS to a geogCRS that is the same as the hubCRS
+ auto boundSrc = dynamic_cast<const crs::BoundCRS *>(sourceCRS.get());
+ auto geogDst = dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
+ if (boundSrc && geogDst) {
+ const auto &hubSrc = boundSrc->hubCRS();
+ auto hubSrcGeog =
+ dynamic_cast<const crs::GeographicCRS *>(hubSrc.get());
+ auto geogCRSOfBaseOfBoundSrc =
+ boundSrc->baseCRS()->extractGeographicCRS();
+ if (hubSrcGeog &&
+ (hubSrcGeog->_isEquivalentTo(
+ geogDst, util::IComparable::Criterion::EQUIVALENT) ||
+ hubSrcGeog->is2DPartOf3D(NN_NO_CHECK(geogDst))) &&
+ geogCRSOfBaseOfBoundSrc) {
+ if (boundSrc->baseCRS() == geogCRSOfBaseOfBoundSrc) {
+ // Optimization to avoid creating a useless concatenated
+ // operation
+ res.emplace_back(boundSrc->transformation());
+ return res;
+ }
+ auto opsFirst =
+ createOperations(boundSrc->baseCRS(),
+ NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context);
+ if (!opsFirst.empty()) {
+ for (const auto &opFirst : opsFirst) {
+ try {
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ {opFirst, boundSrc->transformation()},
+ !allowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ if (!res.empty()) {
+ return res;
+ }
+ }
+ }
+
+ if (hubSrcGeog &&
+ hubSrcGeog->_isEquivalentTo(
+ geogDst, util::IComparable::Criterion::EQUIVALENT) &&
+ dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get())) {
+ res.emplace_back(boundSrc->transformation());
+ return res;
+ }
+
+ return createOperations(boundSrc->baseCRS(), targetCRS, context);
+ }
+
+ // reverse of previous case
+ auto boundDst = dynamic_cast<const crs::BoundCRS *>(targetCRS.get());
+ auto geogSrc = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
+ if (geogSrc && boundDst) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ // vertCRS (as boundCRS with transformation to target vertCRS) to
+ // vertCRS
+ auto vertDst = dynamic_cast<const crs::VerticalCRS *>(targetCRS.get());
+ if (boundSrc && vertDst) {
+ auto baseSrcVert =
+ dynamic_cast<const crs::VerticalCRS *>(boundSrc->baseCRS().get());
+ const auto &hubSrc = boundSrc->hubCRS();
+ auto hubSrcVert = dynamic_cast<const crs::VerticalCRS *>(hubSrc.get());
+ if (baseSrcVert && hubSrcVert &&
+ vertDst->_isEquivalentTo(
+ hubSrcVert, util::IComparable::Criterion::EQUIVALENT)) {
+ res.emplace_back(boundSrc->transformation());
+ return res;
+ }
+
+ return createOperations(boundSrc->baseCRS(), targetCRS, context);
+ }
+
+ // reverse of previous case
+ auto vertSrc = dynamic_cast<const crs::VerticalCRS *>(sourceCRS.get());
+ if (boundDst && vertSrc) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ if (vertSrc && vertDst) {
+ const auto &srcDatum = vertSrc->datum();
+ const auto &dstDatum = vertDst->datum();
+ if (srcDatum && dstDatum &&
+ srcDatum->_isEquivalentTo(
+ dstDatum.get(), util::IComparable::Criterion::EQUIVALENT)) {
+ const double convSrc = vertSrc->coordinateSystem()
+ ->axisList()[0]
+ ->unit()
+ .conversionToSI();
+ const double convDst = vertDst->coordinateSystem()
+ ->axisList()[0]
+ ->unit()
+ .conversionToSI();
+ if (convSrc != convDst) {
+ const double factor = convSrc / convDst;
+ auto conv = Conversion::createChangeVerticalUnit(
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildTransfName(sourceCRS->nameStr(),
+ targetCRS->nameStr())),
+ common::Scale(factor));
+ conv->setCRSs(sourceCRS, targetCRS, nullptr);
+ res.push_back(conv);
+ return res;
+ }
+ }
+ }
+
+ // A bit odd case as we are comparing apples to oranges, but in case
+ // the vertical unit differ, do something useful.
+ if (vertSrc && geogDst) {
+ const double convSrc =
+ vertSrc->coordinateSystem()->axisList()[0]->unit().conversionToSI();
+ double convDst = 1.0;
+ const auto &geogAxis = geogDst->coordinateSystem()->axisList();
+ if (geogAxis.size() == 3) {
+ convDst = geogAxis[2]->unit().conversionToSI();
+ }
+ if (convSrc != convDst) {
+ const double factor = convSrc / convDst;
+ auto conv = Conversion::createChangeVerticalUnit(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ buildTransfName(sourceCRS->nameStr(),
+ targetCRS->nameStr())),
+ common::Scale(factor));
+ conv->setCRSs(sourceCRS, targetCRS, nullptr);
+ res.push_back(conv);
+ return res;
+ }
+ }
+
+ // reverse of previous case
+ if (vertDst && geogSrc) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ // boundCRS to boundCRS using the same geographic hubCRS
+ if (boundSrc && boundDst) {
+ const auto &hubSrc = boundSrc->hubCRS();
+ auto hubSrcGeog =
+ dynamic_cast<const crs::GeographicCRS *>(hubSrc.get());
+ const auto &hubDst = boundDst->hubCRS();
+ auto hubDstGeog =
+ dynamic_cast<const crs::GeographicCRS *>(hubDst.get());
+ auto geogCRSOfBaseOfBoundSrc =
+ boundSrc->baseCRS()->extractGeographicCRS();
+ auto geogCRSOfBaseOfBoundDst =
+ boundDst->baseCRS()->extractGeographicCRS();
+ if (hubSrcGeog && hubDstGeog &&
+ hubSrcGeog->_isEquivalentTo(
+ hubDstGeog, util::IComparable::Criterion::EQUIVALENT) &&
+ geogCRSOfBaseOfBoundSrc && geogCRSOfBaseOfBoundDst) {
+ const bool firstIsNoOp = geogCRSOfBaseOfBoundSrc->_isEquivalentTo(
+ boundSrc->baseCRS().get(),
+ util::IComparable::Criterion::EQUIVALENT);
+ const bool lastIsNoOp = geogCRSOfBaseOfBoundDst->_isEquivalentTo(
+ boundDst->baseCRS().get(),
+ util::IComparable::Criterion::EQUIVALENT);
+ auto opsFirst =
+ createOperations(boundSrc->baseCRS(),
+ NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), context);
+ auto opsLast =
+ createOperations(NN_NO_CHECK(geogCRSOfBaseOfBoundDst),
+ boundDst->baseCRS(), context);
+ if (!opsFirst.empty() && !opsLast.empty()) {
+ const auto &opSecond = boundSrc->transformation();
+ auto opThird = boundDst->transformation()->inverse();
+ for (const auto &opFirst : opsFirst) {
+ for (const auto &opLast : opsLast) {
+ try {
+ std::vector<CoordinateOperationNNPtr> ops;
+ if (!firstIsNoOp) {
+ ops.push_back(opFirst);
+ }
+ ops.push_back(opSecond);
+ ops.push_back(opThird);
+ if (!lastIsNoOp) {
+ ops.push_back(opLast);
+ }
+ res.emplace_back(
+ ConcatenatedOperation::createComputeMetadata(
+ ops, !allowEmptyIntersection));
+ } catch (const InvalidOperationEmptyIntersection &) {
+ }
+ }
+ }
+ if (!res.empty()) {
+ return res;
+ }
+ }
+ }
+
+ return createOperations(boundSrc->baseCRS(), boundDst->baseCRS(),
+ context);
+ }
+
+ auto compoundSrc = dynamic_cast<crs::CompoundCRS *>(sourceCRS.get());
+ if (compoundSrc && geogDst) {
+ const auto &componentsSrc = compoundSrc->componentReferenceSystems();
+ if (!componentsSrc.empty()) {
+ std::vector<CoordinateOperationNNPtr> horizTransforms;
+ if (componentsSrc[0]->extractGeographicCRS()) {
+ horizTransforms =
+ createOperations(componentsSrc[0], targetCRS, context);
+ }
+ std::vector<CoordinateOperationNNPtr> verticalTransforms;
+ if (componentsSrc.size() >= 2 &&
+ componentsSrc[1]->extractVerticalCRS()) {
+ verticalTransforms =
+ createOperations(componentsSrc[1], targetCRS, context);
+ }
+ if (!horizTransforms.empty() && !verticalTransforms.empty()) {
+ for (const auto &horizTransform : horizTransforms) {
+ for (const auto &verticalTransform : verticalTransforms) {
+
+ auto op = createHorizVerticalPROJBased(
+ sourceCRS, targetCRS, horizTransform,
+ verticalTransform);
+
+ res.emplace_back(op);
+ }
+ }
+ return res;
+ } else {
+ return horizTransforms;
+ }
+ }
+ }
+
+ // reverse of previous case
+ auto compoundDst = dynamic_cast<const crs::CompoundCRS *>(targetCRS.get());
+ if (geogSrc && compoundDst) {
+ return applyInverse(createOperations(targetCRS, sourceCRS, context));
+ }
+
+ if (compoundSrc && compoundDst) {
+ const auto &componentsSrc = compoundSrc->componentReferenceSystems();
+ const auto &componentsDst = compoundDst->componentReferenceSystems();
+ if (!componentsSrc.empty() &&
+ componentsSrc.size() == componentsDst.size()) {
+ if (componentsSrc[0]->extractGeographicCRS() &&
+ componentsDst[0]->extractGeographicCRS()) {
+
+ std::vector<CoordinateOperationNNPtr> verticalTransforms;
+ if (componentsSrc.size() >= 2 &&
+ componentsSrc[1]->extractVerticalCRS() &&
+ componentsDst[1]->extractVerticalCRS()) {
+ verticalTransforms = createOperations(
+ componentsSrc[1], componentsDst[1], context);
+ }
+
+ for (const auto &verticalTransform : verticalTransforms) {
+ auto interpolationGeogCRS =
+ NN_NO_CHECK(componentsSrc[0]->extractGeographicCRS());
+ auto transformationVerticalTransform =
+ dynamic_cast<const Transformation *>(
+ verticalTransform.get());
+ if (transformationVerticalTransform) {
+ auto interpTransformCRS =
+ transformationVerticalTransform->interpolationCRS();
+ if (interpTransformCRS) {
+ auto nn_interpTransformCRS =
+ NN_NO_CHECK(interpTransformCRS);
+ if (dynamic_cast<const crs::GeographicCRS *>(
+ nn_interpTransformCRS.get())) {
+ interpolationGeogCRS =
+ NN_NO_CHECK(util::nn_dynamic_pointer_cast<
+ crs::GeographicCRS>(
+ nn_interpTransformCRS));
+ }
+ }
+ }
+ auto opSrcCRSToGeogCRS = createOperations(
+ componentsSrc[0], interpolationGeogCRS, context);
+ auto opGeogCRStoDstCRS = createOperations(
+ interpolationGeogCRS, componentsDst[0], context);
+ for (const auto &opSrc : opSrcCRSToGeogCRS) {
+ for (const auto &opDst : opGeogCRStoDstCRS) {
+
+ auto op = createHorizVerticalHorizPROJBased(
+ sourceCRS, targetCRS, opSrc, verticalTransform,
+ opDst, interpolationGeogCRS);
+ res.emplace_back(op);
+ }
+ }
+ }
+
+ if (verticalTransforms.empty()) {
+ return createOperations(componentsSrc[0], componentsDst[0],
+ context);
+ }
+ }
+ }
+ }
+
+ return res;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Find a list of CoordinateOperation from sourceCRS to targetCRS.
+ *
+ * The operations are sorted with the most relevant ones first: by
+ * descending
+ * area (intersection of the transformation area with the area of interest,
+ * or intersection of the transformation with the area of use of the CRS),
+ * and
+ * by increasing accuracy. Operations with unknown accuracy are sorted last,
+ * whatever their area.
+ *
+ * @param sourceCRS source CRS.
+ * @param targetCRS source CRS.
+ * @param context Search context.
+ * @return a list
+ */
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::createOperations(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const CoordinateOperationContextNNPtr &context) const {
+
+ // Look if we are called on CRS that have a link to a 'canonical'
+ // BoundCRS
+ // If so, use that one as input
+ const auto &srcBoundCRS = sourceCRS->canonicalBoundCRS();
+ const auto &targetBoundCRS = targetCRS->canonicalBoundCRS();
+ auto l_sourceCRS = srcBoundCRS ? NN_NO_CHECK(srcBoundCRS) : sourceCRS;
+ auto l_targetCRS = targetBoundCRS ? NN_NO_CHECK(targetBoundCRS) : targetCRS;
+
+ Private::Context contextPrivate(sourceCRS, targetCRS, context);
+ return filterAndSort(
+ Private::createOperations(l_sourceCRS, l_targetCRS, contextPrivate),
+ context, l_sourceCRS, l_targetCRS);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a CoordinateOperationFactory.
+ */
+CoordinateOperationFactoryNNPtr CoordinateOperationFactory::create() {
+ return NN_NO_CHECK(
+ CoordinateOperationFactory::make_unique<CoordinateOperationFactory>());
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+InverseCoordinateOperation::~InverseCoordinateOperation() = default;
+
+// ---------------------------------------------------------------------------
+
+InverseCoordinateOperation::InverseCoordinateOperation(
+ const CoordinateOperationNNPtr &forwardOperation, bool wktSupportsInversion)
+ : forwardOperation_(forwardOperation),
+ wktSupportsInversion_(wktSupportsInversion) {}
+
+// ---------------------------------------------------------------------------
+
+void InverseCoordinateOperation::setPropertiesFromForward() {
+ setProperties(
+ createPropertiesForInverse(forwardOperation_.get(), false, false));
+ setAccuracies(forwardOperation_->coordinateOperationAccuracies());
+ if (forwardOperation_->sourceCRS() && forwardOperation_->targetCRS()) {
+ setCRSs(forwardOperation_.get(), true);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr InverseCoordinateOperation::inverse() const {
+ return forwardOperation_;
+}
+
+// ---------------------------------------------------------------------------
+
+void InverseCoordinateOperation::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const {
+ formatter->startInversion();
+ forwardOperation_->_exportToPROJString(formatter);
+ formatter->stopInversion();
+}
+
+// ---------------------------------------------------------------------------
+
+bool InverseCoordinateOperation::_isEquivalentTo(
+ const util::IComparable *other,
+ util::IComparable::Criterion criterion) const {
+ auto otherICO = dynamic_cast<const InverseCoordinateOperation *>(other);
+ if (otherICO == nullptr ||
+ !ObjectUsage::_isEquivalentTo(other, criterion)) {
+ return false;
+ }
+ return inverse()->_isEquivalentTo(otherICO->inverse().get(), criterion);
+}
+
+// ---------------------------------------------------------------------------
+
+PROJBasedOperation::~PROJBasedOperation() = default;
+
+// ---------------------------------------------------------------------------
+
+PROJBasedOperation::PROJBasedOperation(
+ const OperationMethodNNPtr &methodIn,
+ const std::vector<GeneralParameterValueNNPtr> &values)
+ : SingleOperation(methodIn) {
+ setParameterValues(values);
+}
+
+// ---------------------------------------------------------------------------
+
+static const std::string PROJSTRING_PARAMETER_NAME("PROJ string");
+
+PROJBasedOperationNNPtr PROJBasedOperation::create(
+ const util::PropertyMap &properties, const std::string &PROJString,
+ const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ auto parameter = OperationParameter::create(util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, PROJSTRING_PARAMETER_NAME));
+ auto method = OperationMethod::create(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ "PROJ-based operation method"),
+ std::vector<OperationParameterNNPtr>{parameter});
+ std::vector<GeneralParameterValueNNPtr> values;
+ values.push_back(OperationParameterValue::create(
+ parameter, ParameterValue::create(PROJString)));
+ auto op =
+ PROJBasedOperation::nn_make_shared<PROJBasedOperation>(method, values);
+ op->assignSelf(op);
+ if (sourceCRS && targetCRS) {
+ op->setCRSs(NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), nullptr);
+ }
+ op->setProperties(
+ addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation"));
+ op->setAccuracies(accuracies);
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+static const std::string
+ APPROX_PROJSTRING_PARAMETER_NAME("(Approximte) PROJ string");
+
+PROJBasedOperationNNPtr PROJBasedOperation::create(
+ const util::PropertyMap &properties,
+ const io::IPROJStringExportableNNPtr &projExportable, bool inverse,
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
+ auto parameter = OperationParameter::create(util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, APPROX_PROJSTRING_PARAMETER_NAME));
+ auto method = OperationMethod::create(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ "PROJ-based operation method"),
+ std::vector<OperationParameterNNPtr>{parameter});
+ std::vector<GeneralParameterValueNNPtr> values;
+
+ auto formatter = io::PROJStringFormatter::create();
+ if (inverse) {
+ formatter->startInversion();
+ }
+ projExportable->_exportToPROJString(formatter.get());
+ if (inverse) {
+ formatter->stopInversion();
+ }
+ auto projString = formatter->toString();
+
+ values.push_back(OperationParameterValue::create(
+ parameter, ParameterValue::create(projString)));
+ auto op =
+ PROJBasedOperation::nn_make_shared<PROJBasedOperation>(method, values);
+ op->assignSelf(op);
+ op->setCRSs(sourceCRS, targetCRS, nullptr);
+ op->setProperties(
+ addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation"));
+ op->setAccuracies(accuracies);
+ op->projStringExportable_ = projExportable.as_nullable();
+ op->inverse_ = inverse;
+ return op;
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr PROJBasedOperation::inverse() const {
+
+ if (projStringExportable_) {
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ PROJBasedOperation::create(
+ createPropertiesForInverse(this, false, false),
+ NN_NO_CHECK(projStringExportable_), !inverse_,
+ NN_NO_CHECK(targetCRS()), NN_NO_CHECK(sourceCRS()),
+ coordinateOperationAccuracies()));
+ }
+
+ auto formatter = io::PROJStringFormatter::create();
+ formatter->startInversion();
+ try {
+ formatter->ingestPROJString(
+ parameterValue(PROJSTRING_PARAMETER_NAME)->stringValue());
+ } catch (const io::ParsingException &e) {
+ throw util::UnsupportedOperationException(
+ std::string("PROJBasedOperation::inverse() failed: ") + e.what());
+ }
+ formatter->stopInversion();
+
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ PROJBasedOperation::create(
+ createPropertiesForInverse(this, false, false),
+ formatter->toString(), targetCRS(), sourceCRS(),
+ coordinateOperationAccuracies()));
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJBasedOperation::_exportToWKT(io::WKTFormatter *formatter) const {
+
+ if (sourceCRS() && targetCRS()) {
+ exportTransformationToWKT(formatter);
+ return;
+ }
+
+ const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
+ if (!isWKT2) {
+ throw io::FormattingException(
+ "PROJBasedOperation can only be exported to WKT2");
+ }
+
+ formatter->startNode(io::WKTConstants::CONVERSION, false);
+ formatter->addQuotedString(nameStr());
+ method()->_exportToWKT(formatter);
+
+ for (const auto &paramValue : parameterValues()) {
+ paramValue->_exportToWKT(formatter);
+ }
+ formatter->endNode();
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJBasedOperation::_exportToPROJString(
+ io::PROJStringFormatter *formatter) const {
+ if (projStringExportable_) {
+ if (inverse_) {
+ formatter->startInversion();
+ }
+ projStringExportable_->_exportToPROJString(formatter);
+ if (inverse_) {
+ formatter->stopInversion();
+ }
+ return;
+ }
+
+ try {
+ formatter->ingestPROJString(
+ parameterValue(PROJSTRING_PARAMETER_NAME)->stringValue());
+ } catch (const io::ParsingException &e) {
+ throw io::FormattingException(
+ std::string("PROJBasedOperation::exportToPROJString() failed: ") +
+ e.what());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+std::set<GridDescription> PROJBasedOperation::gridsNeeded(
+ const io::DatabaseContextPtr &databaseContext) const {
+ std::set<GridDescription> res;
+
+ try {
+ auto formatterOut = io::PROJStringFormatter::create();
+ auto formatter = io::PROJStringFormatter::create();
+ formatter->ingestPROJString(exportToPROJString(formatterOut.get()));
+ const auto usedGridNames = formatter->getUsedGridNames();
+ for (const auto &shortName : usedGridNames) {
+ GridDescription desc;
+ desc.shortName = shortName;
+ if (databaseContext) {
+ databaseContext->lookForGridInfo(
+ desc.shortName, desc.fullName, desc.packageName, desc.url,
+ desc.directDownload, desc.openLicense, desc.available);
+ }
+ res.insert(desc);
+ }
+ } catch (const io::ParsingException &) {
+ }
+
+ return res;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+} // namespace operation
+NS_PROJ_END