aboutsummaryrefslogtreecommitdiff
path: root/src/iso19111/io.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/iso19111/io.cpp')
-rw-r--r--src/iso19111/io.cpp7501
1 files changed, 7501 insertions, 0 deletions
diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp
new file mode 100644
index 00000000..fe3680fb
--- /dev/null
+++ b/src/iso19111/io.cpp
@@ -0,0 +1,7501 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: ISO19111:2018 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+
+#include <algorithm>
+#include <cassert>
+#include <cctype>
+#include <cmath>
+#include <cstring>
+#include <list>
+#include <locale>
+#include <map>
+#include <set>
+#include <sstream> // std::istringstream
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "proj/common.hpp"
+#include "proj/coordinateoperation.hpp"
+#include "proj/coordinatesystem.hpp"
+#include "proj/crs.hpp"
+#include "proj/datum.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/coordinateoperation_internal.hpp"
+#include "proj/internal/coordinatesystem_internal.hpp"
+#include "proj/internal/internal.hpp"
+#include "proj/internal/io_internal.hpp"
+
+#include "proj_constants.h"
+
+#include "pj_wkt1_parser.h"
+#include "pj_wkt2_parser.h"
+
+// PROJ include order is sensitive
+// clang-format off
+#include "proj.h"
+#include "projects.h"
+#include "proj_api.h"
+// clang-format on
+
+using namespace NS_PROJ::common;
+using namespace NS_PROJ::crs;
+using namespace NS_PROJ::cs;
+using namespace NS_PROJ::datum;
+using namespace NS_PROJ::internal;
+using namespace NS_PROJ::metadata;
+using namespace NS_PROJ::operation;
+using namespace NS_PROJ::util;
+
+//! @cond Doxygen_Suppress
+static const std::string emptyString{};
+//! @endcond
+
+#if 0
+namespace dropbox{ namespace oxygen {
+template<> nn<NS_PROJ::io::DatabaseContextPtr>::~nn() = default;
+template<> nn<NS_PROJ::io::AuthorityFactoryPtr>::~nn() = default;
+template<> nn<std::shared_ptr<NS_PROJ::io::IPROJStringExportable>>::~nn() = default;
+template<> nn<std::unique_ptr<NS_PROJ::io::PROJStringFormatter, std::default_delete<NS_PROJ::io::PROJStringFormatter> > >::~nn() = default;
+template<> nn<std::unique_ptr<NS_PROJ::io::WKTFormatter, std::default_delete<NS_PROJ::io::WKTFormatter> > >::~nn() = default;
+template<> nn<std::unique_ptr<NS_PROJ::io::WKTNode, std::default_delete<NS_PROJ::io::WKTNode> > >::~nn() = default;
+}}
+#endif
+
+NS_PROJ_START
+namespace io {
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+IWKTExportable::~IWKTExportable() = default;
+
+// ---------------------------------------------------------------------------
+
+std::string IWKTExportable::exportToWKT(WKTFormatter *formatter) const {
+ _exportToWKT(formatter);
+ return formatter->toString();
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct WKTFormatter::Private {
+ struct Params {
+ WKTFormatter::Convention convention_ = WKTFormatter::Convention::WKT2;
+ WKTFormatter::Version version_ = WKTFormatter::Version::WKT2;
+ bool multiLine_ = true;
+ bool strict_ = true;
+ int indentWidth_ = 4;
+ bool idOnTopLevelOnly_ = false;
+ bool outputAxisOrder_ = false;
+ bool primeMeridianOmittedIfGreenwich_ = false;
+ bool ellipsoidUnitOmittedIfMetre_ = false;
+ bool primeMeridianOrParameterUnitOmittedIfSameAsAxis_ = false;
+ bool forceUNITKeyword_ = false;
+ bool outputCSUnitOnlyOnceIfSame_ = false;
+ bool primeMeridianInDegree_ = false;
+ bool use2018Keywords_ = false;
+ bool useESRIDialect_ = false;
+ OutputAxisRule outputAxis_ = WKTFormatter::OutputAxisRule::YES;
+ };
+ Params params_{};
+ DatabaseContextPtr dbContext_{};
+
+ int indentLevel_ = 0;
+ int level_ = 0;
+ std::vector<bool> stackHasChild_{};
+ std::vector<bool> stackHasId_{false};
+ std::vector<bool> stackEmptyKeyword_{};
+ std::vector<bool> outputUnitStack_{true};
+ std::vector<bool> outputIdStack_{true};
+ std::vector<UnitOfMeasureNNPtr> axisLinearUnitStack_{
+ util::nn_make_shared<UnitOfMeasure>(UnitOfMeasure::METRE)};
+ std::vector<UnitOfMeasureNNPtr> axisAngularUnitStack_{
+ util::nn_make_shared<UnitOfMeasure>(UnitOfMeasure::DEGREE)};
+ bool abridgedTransformation_ = false;
+ bool useDerivingConversion_ = false;
+ std::vector<double> toWGS84Parameters_{};
+ std::string hDatumExtension_{};
+ std::string vDatumExtension_{};
+ std::vector<bool> inversionStack_{false};
+ std::string result_{};
+
+ // cppcheck-suppress functionStatic
+ void addNewLine();
+ void addIndentation();
+ // cppcheck-suppress functionStatic
+ void startNewChild();
+};
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Constructs a new formatter.
+ *
+ * A formatter can be used only once (its internal state is mutated)
+ *
+ * Its default behaviour can be adjusted with the different setters.
+ *
+ * @param convention WKT flavor. Defaults to Convention::WKT2
+ * @param dbContext Database context, to allow queries in it if needed.
+ * This is used for example for WKT1_ESRI output to do name substitutions.
+ *
+ * @return new formatter.
+ */
+WKTFormatterNNPtr WKTFormatter::create(Convention convention,
+ // cppcheck-suppress passedByValue
+ DatabaseContextPtr dbContext) {
+ auto ret = NN_NO_CHECK(WKTFormatter::make_unique<WKTFormatter>(convention));
+ ret->d->dbContext_ = dbContext;
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Constructs a new formatter from another one.
+ *
+ * A formatter can be used only once (its internal state is mutated)
+ *
+ * Its default behaviour can be adjusted with the different setters.
+ *
+ * @param other source formatter.
+ * @return new formatter.
+ */
+WKTFormatterNNPtr WKTFormatter::create(const WKTFormatterNNPtr &other) {
+ auto f = create(other->d->params_.convention_, other->d->dbContext_);
+ f->d->params_ = other->d->params_;
+ return f;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+WKTFormatter::~WKTFormatter() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Whether to use multi line output or not. */
+WKTFormatter &WKTFormatter::setMultiLine(bool multiLine) noexcept {
+ d->params_.multiLine_ = multiLine;
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set number of spaces for each indentation level (defaults to 4).
+ */
+WKTFormatter &WKTFormatter::setIndentationWidth(int width) noexcept {
+ d->params_.indentWidth_ = width;
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether AXIS nodes should be output.
+ */
+WKTFormatter &
+WKTFormatter::setOutputAxis(OutputAxisRule outputAxisIn) noexcept {
+ d->params_.outputAxis_ = outputAxisIn;
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether the formatter should operate on strict more or not.
+ *
+ * The default is strit mode, in which case a FormattingException can be thrown.
+ */
+WKTFormatter &WKTFormatter::setStrict(bool strictIn) noexcept {
+ d->params_.strict_ = strictIn;
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns whether the formatter is in strict mode. */
+bool WKTFormatter::isStrict() const noexcept { return d->params_.strict_; }
+
+// ---------------------------------------------------------------------------
+
+/** Returns the WKT string from the formatter. */
+const std::string &WKTFormatter::toString() const {
+ if (d->indentLevel_ > 0 || d->level_ > 0) {
+ // For intermediary nodes, the formatter is in a inconsistent
+ // state.
+ throw FormattingException("toString() called on intermediate nodes");
+ }
+ if (d->axisLinearUnitStack_.size() != 1)
+ throw FormattingException(
+ "Unbalanced pushAxisLinearUnit() / popAxisLinearUnit()");
+ if (d->axisAngularUnitStack_.size() != 1)
+ throw FormattingException(
+ "Unbalanced pushAxisAngularUnit() / popAxisAngularUnit()");
+ if (d->outputIdStack_.size() != 1)
+ throw FormattingException("Unbalanced pushOutputId() / popOutputId()");
+ if (d->outputUnitStack_.size() != 1)
+ throw FormattingException(
+ "Unbalanced pushOutputUnit() / popOutputUnit()");
+
+ return d->result_;
+}
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+WKTFormatter::WKTFormatter(Convention convention)
+ : d(internal::make_unique<Private>()) {
+ d->params_.convention_ = convention;
+ switch (convention) {
+ case Convention::WKT2_2018:
+ d->params_.use2018Keywords_ = true;
+ PROJ_FALLTHROUGH
+ case Convention::WKT2:
+ d->params_.version_ = WKTFormatter::Version::WKT2;
+ d->params_.outputAxisOrder_ = true;
+ break;
+
+ case Convention::WKT2_2018_SIMPLIFIED:
+ d->params_.use2018Keywords_ = true;
+ PROJ_FALLTHROUGH
+ case Convention::WKT2_SIMPLIFIED:
+ d->params_.version_ = WKTFormatter::Version::WKT2;
+ d->params_.idOnTopLevelOnly_ = true;
+ d->params_.outputAxisOrder_ = false;
+ d->params_.primeMeridianOmittedIfGreenwich_ = true;
+ d->params_.ellipsoidUnitOmittedIfMetre_ = true;
+ d->params_.primeMeridianOrParameterUnitOmittedIfSameAsAxis_ = true;
+ d->params_.forceUNITKeyword_ = true;
+ d->params_.outputCSUnitOnlyOnceIfSame_ = true;
+ break;
+
+ case Convention::WKT1_GDAL:
+ d->params_.version_ = WKTFormatter::Version::WKT1;
+ d->params_.outputAxisOrder_ = false;
+ d->params_.forceUNITKeyword_ = true;
+ d->params_.primeMeridianInDegree_ = true;
+ d->params_.outputAxis_ =
+ WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE;
+ break;
+
+ case Convention::WKT1_ESRI:
+ d->params_.version_ = WKTFormatter::Version::WKT1;
+ d->params_.outputAxisOrder_ = false;
+ d->params_.forceUNITKeyword_ = true;
+ d->params_.primeMeridianInDegree_ = true;
+ d->params_.useESRIDialect_ = true;
+ d->params_.multiLine_ = false;
+ d->params_.outputAxis_ = WKTFormatter::OutputAxisRule::NO;
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+WKTFormatter &WKTFormatter::setOutputId(bool outputIdIn) {
+ if (d->indentLevel_ != 0) {
+ throw Exception(
+ "setOutputId() shall only be called when the stack state is empty");
+ }
+ d->outputIdStack_[0] = outputIdIn;
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::Private::addNewLine() { result_ += '\n'; }
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::Private::addIndentation() {
+ result_ += std::string(indentLevel_ * params_.indentWidth_, ' ');
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::enter() {
+ if (d->indentLevel_ == 0 && d->level_ == 0) {
+ d->stackHasChild_.push_back(false);
+ }
+ ++d->level_;
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::leave() {
+ assert(d->level_ > 0);
+ --d->level_;
+ if (d->indentLevel_ == 0 && d->level_ == 0) {
+ d->stackHasChild_.pop_back();
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::startNode(const std::string &keyword, bool hasId) {
+ if (!d->stackHasChild_.empty()) {
+ d->startNewChild();
+ } else if (!d->result_.empty()) {
+ d->result_ += ',';
+ if (d->params_.multiLine_ && !keyword.empty()) {
+ d->addNewLine();
+ }
+ }
+
+ if (d->params_.multiLine_) {
+ if ((d->indentLevel_ || d->level_) && !keyword.empty()) {
+ if (!d->result_.empty()) {
+ d->addNewLine();
+ }
+ d->addIndentation();
+ }
+ }
+
+ if (!keyword.empty()) {
+ d->result_ += keyword;
+ d->result_ += '[';
+ }
+ d->indentLevel_++;
+ d->stackHasChild_.push_back(false);
+ d->stackEmptyKeyword_.push_back(keyword.empty());
+
+ // Starting from a node that has a ID, we should emit ID nodes for :
+ // - this node
+ // - and for METHOD&PARAMETER nodes in WKT2, unless idOnTopLevelOnly_ is
+ // set.
+ // For WKT2, all other intermediate nodes shouldn't have ID ("not
+ // recommended")
+ if (!d->params_.idOnTopLevelOnly_ && d->indentLevel_ >= 2 &&
+ d->params_.version_ == WKTFormatter::Version::WKT2 &&
+ (keyword == WKTConstants::METHOD ||
+ keyword == WKTConstants::PARAMETER)) {
+ pushOutputId(d->outputIdStack_[0]);
+ } else if (d->indentLevel_ >= 2 &&
+ d->params_.version_ == WKTFormatter::Version::WKT2) {
+ pushOutputId(d->outputIdStack_[0] && !d->stackHasId_.back());
+ } else {
+ pushOutputId(outputId());
+ }
+
+ d->stackHasId_.push_back(hasId || d->stackHasId_.back());
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::endNode() {
+ assert(d->indentLevel_ > 0);
+ d->stackHasId_.pop_back();
+ popOutputId();
+ d->indentLevel_--;
+ bool emptyKeyword = d->stackEmptyKeyword_.back();
+ d->stackEmptyKeyword_.pop_back();
+ d->stackHasChild_.pop_back();
+ if (!emptyKeyword)
+ d->result_ += ']';
+}
+
+// ---------------------------------------------------------------------------
+
+WKTFormatter &WKTFormatter::simulCurNodeHasId() {
+ d->stackHasId_.back() = true;
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::Private::startNewChild() {
+ assert(!stackHasChild_.empty());
+ if (stackHasChild_.back()) {
+ result_ += ',';
+ }
+ stackHasChild_.back() = true;
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::addQuotedString(const char *str) {
+ addQuotedString(std::string(str));
+}
+
+void WKTFormatter::addQuotedString(const std::string &str) {
+ d->startNewChild();
+ d->result_ += '"';
+ d->result_ += replaceAll(str, "\"", "\"\"");
+ d->result_ += '"';
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::add(const std::string &str) {
+ d->startNewChild();
+ d->result_ += str;
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::add(int number) {
+ d->startNewChild();
+ d->result_ += internal::toString(number);
+}
+
+// ---------------------------------------------------------------------------
+
+#ifdef __MINGW32__
+static std::string normalizeSerializedString(const std::string &in) {
+ // mingw will output 1e-0xy instead of 1e-xy. Fix that
+ auto pos = in.find("e-0");
+ if (pos == std::string::npos) {
+ return in;
+ }
+ if (pos + 4 < in.size() && isdigit(in[pos + 3]) && isdigit(in[pos + 4])) {
+ return in.substr(0, pos + 2) + in.substr(pos + 3);
+ }
+ return in;
+}
+#else
+static inline std::string normalizeSerializedString(const std::string &in) {
+ return in;
+}
+#endif
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::add(double number, int precision) {
+ d->startNewChild();
+ if (number == 0.0) {
+ if (d->params_.useESRIDialect_) {
+ d->result_ += "0.0";
+ } else {
+ d->result_ += '0';
+ }
+ } else {
+ std::string val(
+ normalizeSerializedString(internal::toString(number, precision)));
+ d->result_ += val;
+ if (d->params_.useESRIDialect_ && val.find('.') == std::string::npos) {
+ d->result_ += ".0";
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::pushOutputUnit(bool outputUnitIn) {
+ d->outputUnitStack_.push_back(outputUnitIn);
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::popOutputUnit() { d->outputUnitStack_.pop_back(); }
+
+// ---------------------------------------------------------------------------
+
+bool WKTFormatter::outputUnit() const { return d->outputUnitStack_.back(); }
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::pushOutputId(bool outputIdIn) {
+ d->outputIdStack_.push_back(outputIdIn);
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::popOutputId() { d->outputIdStack_.pop_back(); }
+
+// ---------------------------------------------------------------------------
+
+bool WKTFormatter::outputId() const {
+ return !d->params_.useESRIDialect_ && d->outputIdStack_.back();
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::pushAxisLinearUnit(const UnitOfMeasureNNPtr &unit) {
+ d->axisLinearUnitStack_.push_back(unit);
+}
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::popAxisLinearUnit() { d->axisLinearUnitStack_.pop_back(); }
+
+// ---------------------------------------------------------------------------
+
+const UnitOfMeasureNNPtr &WKTFormatter::axisLinearUnit() const {
+ return d->axisLinearUnitStack_.back();
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::pushAxisAngularUnit(const UnitOfMeasureNNPtr &unit) {
+ d->axisAngularUnitStack_.push_back(unit);
+}
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::popAxisAngularUnit() { d->axisAngularUnitStack_.pop_back(); }
+
+// ---------------------------------------------------------------------------
+
+const UnitOfMeasureNNPtr &WKTFormatter::axisAngularUnit() const {
+ return d->axisAngularUnitStack_.back();
+}
+
+// ---------------------------------------------------------------------------
+
+WKTFormatter::OutputAxisRule WKTFormatter::outputAxis() const {
+ return d->params_.outputAxis_;
+}
+
+// ---------------------------------------------------------------------------
+
+bool WKTFormatter::outputAxisOrder() const {
+ return d->params_.outputAxisOrder_;
+}
+
+// ---------------------------------------------------------------------------
+
+bool WKTFormatter::primeMeridianOmittedIfGreenwich() const {
+ return d->params_.primeMeridianOmittedIfGreenwich_;
+}
+
+// ---------------------------------------------------------------------------
+
+bool WKTFormatter::ellipsoidUnitOmittedIfMetre() const {
+ return d->params_.ellipsoidUnitOmittedIfMetre_;
+}
+
+// ---------------------------------------------------------------------------
+
+bool WKTFormatter::primeMeridianOrParameterUnitOmittedIfSameAsAxis() const {
+ return d->params_.primeMeridianOrParameterUnitOmittedIfSameAsAxis_;
+}
+
+// ---------------------------------------------------------------------------
+
+bool WKTFormatter::outputCSUnitOnlyOnceIfSame() const {
+ return d->params_.outputCSUnitOnlyOnceIfSame_;
+}
+
+// ---------------------------------------------------------------------------
+
+bool WKTFormatter::forceUNITKeyword() const {
+ return d->params_.forceUNITKeyword_;
+}
+
+// ---------------------------------------------------------------------------
+
+bool WKTFormatter::primeMeridianInDegree() const {
+ return d->params_.primeMeridianInDegree_;
+}
+
+// ---------------------------------------------------------------------------
+
+WKTFormatter::Version WKTFormatter::version() const {
+ return d->params_.version_;
+}
+
+// ---------------------------------------------------------------------------
+
+bool WKTFormatter::use2018Keywords() const {
+ return d->params_.use2018Keywords_;
+}
+
+// ---------------------------------------------------------------------------
+
+bool WKTFormatter::useESRIDialect() const { return d->params_.useESRIDialect_; }
+
+// ---------------------------------------------------------------------------
+
+const DatabaseContextPtr &WKTFormatter::databaseContext() const {
+ return d->dbContext_;
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::setAbridgedTransformation(bool outputIn) {
+ d->abridgedTransformation_ = outputIn;
+}
+
+// ---------------------------------------------------------------------------
+
+bool WKTFormatter::abridgedTransformation() const {
+ return d->abridgedTransformation_;
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::setUseDerivingConversion(bool useDerivingConversionIn) {
+ d->useDerivingConversion_ = useDerivingConversionIn;
+}
+
+// ---------------------------------------------------------------------------
+
+bool WKTFormatter::useDerivingConversion() const {
+ return d->useDerivingConversion_;
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::setTOWGS84Parameters(const std::vector<double> &params) {
+ d->toWGS84Parameters_ = params;
+}
+
+// ---------------------------------------------------------------------------
+
+const std::vector<double> &WKTFormatter::getTOWGS84Parameters() const {
+ return d->toWGS84Parameters_;
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::setVDatumExtension(const std::string &filename) {
+ d->vDatumExtension_ = filename;
+}
+
+// ---------------------------------------------------------------------------
+
+const std::string &WKTFormatter::getVDatumExtension() const {
+ return d->vDatumExtension_;
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::setHDatumExtension(const std::string &filename) {
+ d->hDatumExtension_ = filename;
+}
+
+// ---------------------------------------------------------------------------
+
+const std::string &WKTFormatter::getHDatumExtension() const {
+ return d->hDatumExtension_;
+}
+
+// ---------------------------------------------------------------------------
+
+std::string WKTFormatter::morphNameToESRI(const std::string &name) {
+ std::string ret;
+ bool insertUnderscore = false;
+ // Replace any special character by underscore, except at the beginning
+ // and of the name where those characters are removed.
+ for (char ch : name) {
+ if (ch == '+' || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') ||
+ (ch >= 'A' && ch <= 'Z')) {
+ if (insertUnderscore && !ret.empty()) {
+ ret += '_';
+ }
+ ret += ch;
+ insertUnderscore = false;
+ } else {
+ insertUnderscore = true;
+ }
+ }
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::ingestWKTNode(const WKTNodeNNPtr &node) {
+ startNode(node->value(), true);
+ for (const auto &child : node->children()) {
+ if (!child->children().empty()) {
+ ingestWKTNode(child);
+ } else {
+ add(child->value());
+ }
+ }
+ endNode();
+}
+
+#ifdef unused
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::startInversion() {
+ d->inversionStack_.push_back(!d->inversionStack_.back());
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTFormatter::stopInversion() {
+ assert(!d->inversionStack_.empty());
+ d->inversionStack_.pop_back();
+}
+
+// ---------------------------------------------------------------------------
+
+bool WKTFormatter::isInverted() const { return d->inversionStack_.back(); }
+#endif
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+static WKTNodeNNPtr
+ null_node(NN_NO_CHECK(internal::make_unique<WKTNode>(std::string())));
+
+static inline bool isNull(const WKTNodeNNPtr &node) {
+ return &node == &null_node;
+}
+
+struct WKTNode::Private {
+ std::string value_{};
+ std::vector<WKTNodeNNPtr> children_{};
+
+ explicit Private(const std::string &valueIn) : value_(valueIn) {}
+
+ // cppcheck-suppress functionStatic
+ inline const std::string &value() PROJ_CONST_DEFN { return value_; }
+
+ // cppcheck-suppress functionStatic
+ inline const std::vector<WKTNodeNNPtr> &children() PROJ_CONST_DEFN {
+ return children_;
+ }
+
+ // cppcheck-suppress functionStatic
+ inline size_t childrenSize() PROJ_CONST_DEFN { return children_.size(); }
+
+ // cppcheck-suppress functionStatic
+ const WKTNodeNNPtr &lookForChild(const std::string &childName,
+ int occurrence) const noexcept;
+
+ // cppcheck-suppress functionStatic
+ const WKTNodeNNPtr &lookForChild(const std::string &name) const noexcept;
+
+ // cppcheck-suppress functionStatic
+ const WKTNodeNNPtr &lookForChild(const std::string &name,
+ const std::string &name2) const noexcept;
+
+ // cppcheck-suppress functionStatic
+ const WKTNodeNNPtr &lookForChild(const std::string &name,
+ const std::string &name2,
+ const std::string &name3) const noexcept;
+
+ // cppcheck-suppress functionStatic
+ const WKTNodeNNPtr &lookForChild(const std::string &name,
+ const std::string &name2,
+ const std::string &name3,
+ const std::string &name4) const noexcept;
+};
+
+#define GP() getPrivate()
+
+// ---------------------------------------------------------------------------
+
+const WKTNodeNNPtr &WKTNode::Private::lookForChild(const std::string &childName,
+ int occurrence) const
+ noexcept {
+ int occCount = 0;
+ for (const auto &child : children_) {
+ if (ci_equal(child->GP()->value(), childName)) {
+ if (occurrence == occCount) {
+ return child;
+ }
+ occCount++;
+ }
+ }
+ return null_node;
+}
+
+const WKTNodeNNPtr &
+WKTNode::Private::lookForChild(const std::string &name) const noexcept {
+ for (const auto &child : children_) {
+ const auto &v = child->GP()->value();
+ if (ci_equal(v, name)) {
+ return child;
+ }
+ }
+ return null_node;
+}
+
+const WKTNodeNNPtr &
+WKTNode::Private::lookForChild(const std::string &name,
+ const std::string &name2) const noexcept {
+ for (const auto &child : children_) {
+ const auto &v = child->GP()->value();
+ if (ci_equal(v, name) || ci_equal(v, name2)) {
+ return child;
+ }
+ }
+ return null_node;
+}
+
+const WKTNodeNNPtr &
+WKTNode::Private::lookForChild(const std::string &name,
+ const std::string &name2,
+ const std::string &name3) const noexcept {
+ for (const auto &child : children_) {
+ const auto &v = child->GP()->value();
+ if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3)) {
+ return child;
+ }
+ }
+ return null_node;
+}
+
+const WKTNodeNNPtr &WKTNode::Private::lookForChild(
+ const std::string &name, const std::string &name2, const std::string &name3,
+ const std::string &name4) const noexcept {
+ for (const auto &child : children_) {
+ const auto &v = child->GP()->value();
+ if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3) ||
+ ci_equal(v, name4)) {
+ return child;
+ }
+ }
+ return null_node;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a WKTNode.
+ *
+ * @param valueIn the name of the node.
+ */
+WKTNode::WKTNode(const std::string &valueIn)
+ : d(internal::make_unique<Private>(valueIn)) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+WKTNode::~WKTNode() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Adds a child to the current node.
+ *
+ * @param child child to add. This should not be a parent of this node.
+ */
+void WKTNode::addChild(WKTNodeNNPtr &&child) {
+ d->children_.push_back(std::move(child));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the (occurrence-1)th sub-node of name childName.
+ *
+ * @param childName name of the child.
+ * @param occurrence occurrence index (starting at 0)
+ * @return the child, or nullptr.
+ */
+const WKTNodePtr &WKTNode::lookForChild(const std::string &childName,
+ int occurrence) const noexcept {
+ int occCount = 0;
+ for (const auto &child : d->children_) {
+ if (ci_equal(child->GP()->value(), childName)) {
+ if (occurrence == occCount) {
+ return child;
+ }
+ occCount++;
+ }
+ }
+ return null_node;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the count of children of given name.
+ *
+ * @param childName name of the children to look for.
+ * @return count
+ */
+int WKTNode::countChildrenOfName(const std::string &childName) const noexcept {
+ int occCount = 0;
+ for (const auto &child : d->children_) {
+ if (ci_equal(child->GP()->value(), childName)) {
+ occCount++;
+ }
+ }
+ return occCount;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the value of a node.
+ */
+const std::string &WKTNode::value() const { return d->value_; }
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the children of a node.
+ */
+const std::vector<WKTNodeNNPtr> &WKTNode::children() const {
+ return d->children_;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static size_t skipSpace(const std::string &str, size_t start) {
+ size_t i = start;
+ while (i < str.size() && ::isspace(str[i])) {
+ ++i;
+ }
+ return i;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+// As used in examples of OGC 12-063r5
+static const std::string startPrintedQuote("\xE2\x80\x9C");
+static const std::string endPrintedQuote("\xE2\x80\x9D");
+//! @endcond
+
+WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart,
+ int recLevel, size_t &indexEnd) {
+ if (recLevel == 16) {
+ throw ParsingException("too many nesting levels");
+ }
+ std::string value;
+ size_t i = skipSpace(wkt, indexStart);
+ if (i == wkt.size()) {
+ throw ParsingException("whitespace only string");
+ }
+ std::string closingStringMarker;
+ bool inString = false;
+
+ for (; i < wkt.size() &&
+ (inString || (wkt[i] != '[' && wkt[i] != '(' && wkt[i] != ',' &&
+ wkt[i] != ']' && wkt[i] != ')' && !::isspace(wkt[i])));
+ ++i) {
+ if (wkt[i] == '"') {
+ if (!inString) {
+ inString = true;
+ closingStringMarker = "\"";
+ } else if (closingStringMarker == "\"") {
+ if (i + 1 < wkt.size() && wkt[i + 1] == '"') {
+ i++;
+ } else {
+ inString = false;
+ closingStringMarker.clear();
+ }
+ }
+ } else if (i + 3 <= wkt.size() &&
+ wkt.substr(i, 3) == startPrintedQuote) {
+ if (!inString) {
+ inString = true;
+ closingStringMarker = endPrintedQuote;
+ value += '"';
+ i += 2;
+ continue;
+ }
+ } else if (i + 3 <= wkt.size() &&
+ closingStringMarker == endPrintedQuote &&
+ wkt.substr(i, 3) == endPrintedQuote) {
+ inString = false;
+ closingStringMarker.clear();
+ value += '"';
+ i += 2;
+ continue;
+ }
+ value += wkt[i];
+ }
+ i = skipSpace(wkt, i);
+ if (i == wkt.size()) {
+ if (indexStart == 0) {
+ throw ParsingException("missing [");
+ } else {
+ throw ParsingException("missing , or ]");
+ }
+ }
+
+ auto node = NN_NO_CHECK(internal::make_unique<WKTNode>(value));
+
+ if (indexStart > 0) {
+ if (wkt[i] == ',') {
+ indexEnd = i + 1;
+ return node;
+ }
+ if (wkt[i] == ']' || wkt[i] == ')') {
+ indexEnd = i;
+ return node;
+ }
+ }
+ if (wkt[i] != '[' && wkt[i] != '(') {
+ throw ParsingException("missing [");
+ }
+ ++i; // skip [
+ i = skipSpace(wkt, i);
+ while (i < wkt.size() && wkt[i] != ']' && wkt[i] != ')') {
+ size_t indexEndChild;
+ node->addChild(createFrom(wkt, i, recLevel + 1, indexEndChild));
+ assert(indexEndChild > i);
+ i = indexEndChild;
+ i = skipSpace(wkt, i);
+ if (i < wkt.size() && wkt[i] == ',') {
+ ++i;
+ i = skipSpace(wkt, i);
+ }
+ }
+ if (i == wkt.size() || (wkt[i] != ']' && wkt[i] != ')')) {
+ throw ParsingException("missing ]");
+ }
+ indexEnd = i + 1;
+ return node;
+}
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a WKTNode hierarchy from a WKT string.
+ *
+ * @param wkt the WKT string to parse.
+ * @param indexStart the start index in the wkt string.
+ * @throw ParsingException
+ */
+WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart) {
+ size_t indexEnd;
+ return createFrom(wkt, indexStart, 0, indexEnd);
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static std::string escapeIfQuotedString(const std::string &str) {
+ if (str.size() > 2 && str[0] == '"' && str.back() == '"') {
+ std::string res("\"");
+ res += replaceAll(str.substr(1, str.size() - 2), "\"", "\"\"");
+ res += '"';
+ return res;
+ } else {
+ return str;
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return a WKT representation of the tree structure.
+ */
+std::string WKTNode::toString() const {
+ std::string str(escapeIfQuotedString(d->value_));
+ if (!d->children_.empty()) {
+ str += "[";
+ bool first = true;
+ for (auto &child : d->children_) {
+ if (!first) {
+ str += ',';
+ }
+ first = false;
+ str += child->toString();
+ }
+ str += "]";
+ }
+ return str;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+struct WKTParser::Private {
+ bool strict_ = true;
+ std::list<std::string> warningList_{};
+ std::vector<double> toWGS84Parameters_{};
+ std::string datumPROJ4Grids_{};
+ bool esriStyle_ = false;
+ DatabaseContextPtr dbContext_{};
+
+ static constexpr int MAX_PROPERTY_SIZE = 1024;
+ PropertyMap **properties_{};
+ int propertyCount_ = 0;
+
+ Private() { properties_ = new PropertyMap *[MAX_PROPERTY_SIZE]; }
+
+ ~Private() {
+ for (int i = 0; i < propertyCount_; i++) {
+ delete properties_[i];
+ }
+ delete[] properties_;
+ }
+ Private(const Private &) = delete;
+ Private &operator=(const Private &) = delete;
+
+ void emitRecoverableAssertion(const std::string &errorMsg);
+
+ BaseObjectNNPtr build(const WKTNodeNNPtr &node);
+
+ IdentifierPtr buildId(const WKTNodeNNPtr &node, bool tolerant = true);
+
+ PropertyMap &buildProperties(const WKTNodeNNPtr &node);
+
+ ObjectDomainPtr buildObjectDomain(const WKTNodeNNPtr &node);
+
+ static std::string stripQuotes(const WKTNodeNNPtr &node);
+
+ static double asDouble(const WKTNodeNNPtr &node);
+
+ UnitOfMeasure
+ buildUnit(const WKTNodeNNPtr &node,
+ UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN);
+
+ UnitOfMeasure buildUnitInSubNode(
+ const WKTNodeNNPtr &node,
+ common::UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN);
+
+ EllipsoidNNPtr buildEllipsoid(const WKTNodeNNPtr &node);
+
+ PrimeMeridianNNPtr
+ buildPrimeMeridian(const WKTNodeNNPtr &node,
+ const UnitOfMeasure &defaultAngularUnit);
+
+ optional<std::string> getAnchor(const WKTNodeNNPtr &node);
+
+ static void parseDynamic(const WKTNodeNNPtr &dynamicNode,
+ double &frameReferenceEpoch,
+ util::optional<std::string> &modelName);
+
+ GeodeticReferenceFrameNNPtr
+ buildGeodeticReferenceFrame(const WKTNodeNNPtr &node,
+ const PrimeMeridianNNPtr &primeMeridian,
+ const WKTNodeNNPtr &dynamicNode);
+
+ DatumEnsembleNNPtr buildDatumEnsemble(const WKTNodeNNPtr &node,
+ const PrimeMeridianPtr &primeMeridian,
+ bool expectEllipsoid);
+
+ MeridianNNPtr buildMeridian(const WKTNodeNNPtr &node);
+ CoordinateSystemAxisNNPtr buildAxis(const WKTNodeNNPtr &node,
+ const UnitOfMeasure &unitIn,
+ const UnitOfMeasure::Type &unitType,
+ bool isGeocentric,
+ int expectedOrderNum);
+
+ CoordinateSystemNNPtr buildCS(const WKTNodeNNPtr &node, /* maybe null */
+ const WKTNodeNNPtr &parentNode,
+ const UnitOfMeasure &defaultAngularUnit);
+
+ GeodeticCRSNNPtr buildGeodeticCRS(const WKTNodeNNPtr &node);
+
+ CRSNNPtr buildDerivedGeodeticCRS(const WKTNodeNNPtr &node);
+
+ static UnitOfMeasure
+ guessUnitForParameter(const std::string &paramName,
+ const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit);
+
+ void consumeParameters(const WKTNodeNNPtr &node, bool isAbridged,
+ std::vector<OperationParameterNNPtr> &parameters,
+ std::vector<ParameterValueNNPtr> &values,
+ const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit);
+
+ static void addExtensionProj4ToProp(const WKTNode::Private *nodeP,
+ PropertyMap &props);
+
+ ConversionNNPtr buildConversion(const WKTNodeNNPtr &node,
+ const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit);
+
+ static bool hasWebMercPROJ4String(const WKTNodeNNPtr &projCRSNode,
+ const WKTNodeNNPtr &projectionNode);
+
+ static std::string projectionGetParameter(const WKTNodeNNPtr &projCRSNode,
+ const char *paramName);
+
+ ConversionNNPtr buildProjection(const WKTNodeNNPtr &projCRSNode,
+ const WKTNodeNNPtr &projectionNode,
+ const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit);
+
+ ConversionNNPtr
+ buildProjectionStandard(const WKTNodeNNPtr &projCRSNode,
+ const WKTNodeNNPtr &projectionNode,
+ const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit);
+
+ ConversionNNPtr
+ buildProjectionFromESRI(const WKTNodeNNPtr &projCRSNode,
+ const WKTNodeNNPtr &projectionNode,
+ const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit);
+
+ ProjectedCRSNNPtr buildProjectedCRS(const WKTNodeNNPtr &node);
+
+ VerticalReferenceFrameNNPtr
+ buildVerticalReferenceFrame(const WKTNodeNNPtr &node,
+ const WKTNodeNNPtr &dynamicNode);
+
+ TemporalDatumNNPtr buildTemporalDatum(const WKTNodeNNPtr &node);
+
+ EngineeringDatumNNPtr buildEngineeringDatum(const WKTNodeNNPtr &node);
+
+ ParametricDatumNNPtr buildParametricDatum(const WKTNodeNNPtr &node);
+
+ CRSNNPtr buildVerticalCRS(const WKTNodeNNPtr &node);
+
+ DerivedVerticalCRSNNPtr buildDerivedVerticalCRS(const WKTNodeNNPtr &node);
+
+ CompoundCRSNNPtr buildCompoundCRS(const WKTNodeNNPtr &node);
+
+ BoundCRSNNPtr buildBoundCRS(const WKTNodeNNPtr &node);
+
+ TemporalCSNNPtr buildTemporalCS(const WKTNodeNNPtr &parentNode);
+
+ TemporalCRSNNPtr buildTemporalCRS(const WKTNodeNNPtr &node);
+
+ DerivedTemporalCRSNNPtr buildDerivedTemporalCRS(const WKTNodeNNPtr &node);
+
+ EngineeringCRSNNPtr buildEngineeringCRS(const WKTNodeNNPtr &node);
+
+ EngineeringCRSNNPtr
+ buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node);
+
+ DerivedEngineeringCRSNNPtr
+ buildDerivedEngineeringCRS(const WKTNodeNNPtr &node);
+
+ ParametricCSNNPtr buildParametricCS(const WKTNodeNNPtr &parentNode);
+
+ ParametricCRSNNPtr buildParametricCRS(const WKTNodeNNPtr &node);
+
+ DerivedParametricCRSNNPtr
+ buildDerivedParametricCRS(const WKTNodeNNPtr &node);
+
+ DerivedProjectedCRSNNPtr buildDerivedProjectedCRS(const WKTNodeNNPtr &node);
+
+ CRSPtr buildCRS(const WKTNodeNNPtr &node);
+
+ CoordinateOperationNNPtr buildCoordinateOperation(const WKTNodeNNPtr &node);
+
+ ConcatenatedOperationNNPtr
+ buildConcatenatedOperation(const WKTNodeNNPtr &node);
+};
+
+// ---------------------------------------------------------------------------
+
+WKTParser::WKTParser() : d(internal::make_unique<Private>()) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+WKTParser::~WKTParser() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether parsing should be done in strict mode.
+ */
+WKTParser &WKTParser::setStrict(bool strict) {
+ d->strict_ = strict;
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the list of warnings found during parsing.
+ *
+ * \note The list might be non-empty only is setStrict(false) has been called.
+ */
+std::list<std::string> WKTParser::warningList() const {
+ return d->warningList_;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void WKTParser::Private::emitRecoverableAssertion(const std::string &errorMsg) {
+ if (strict_) {
+ throw ParsingException(errorMsg);
+ } else {
+ warningList_.push_back(errorMsg);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static double asDouble(const std::string &val) { return c_locale_stod(val); }
+
+// ---------------------------------------------------------------------------
+
+PROJ_NO_RETURN static void ThrowNotEnoughChildren(const std::string &nodeName) {
+ throw ParsingException(
+ concat("not enough children in ", nodeName, " node"));
+}
+
+// ---------------------------------------------------------------------------
+
+PROJ_NO_RETURN static void
+ThrowNotRequiredNumberOfChildren(const std::string &nodeName) {
+ throw ParsingException(
+ concat("not required number of children in ", nodeName, " node"));
+}
+
+// ---------------------------------------------------------------------------
+
+PROJ_NO_RETURN static void ThrowMissing(const std::string &nodeName) {
+ throw ParsingException(concat("missing ", nodeName, " node"));
+}
+
+// ---------------------------------------------------------------------------
+
+PROJ_NO_RETURN static void
+ThrowNotExpectedCSType(const std::string &expectedCSType) {
+ throw ParsingException(concat("CS node is not of type ", expectedCSType));
+}
+
+// ---------------------------------------------------------------------------
+
+static ParsingException buildRethrow(const char *funcName,
+ const std::exception &e) {
+ std::string res(funcName);
+ res += ": ";
+ res += e.what();
+ return ParsingException(res);
+}
+
+// ---------------------------------------------------------------------------
+
+std::string WKTParser::Private::stripQuotes(const WKTNodeNNPtr &node) {
+ return ::stripQuotes(node->GP()->value());
+}
+
+// ---------------------------------------------------------------------------
+
+double WKTParser::Private::asDouble(const WKTNodeNNPtr &node) {
+ return io::asDouble(node->GP()->value());
+}
+
+// ---------------------------------------------------------------------------
+
+IdentifierPtr WKTParser::Private::buildId(const WKTNodeNNPtr &node,
+ bool tolerant) {
+ const auto *nodeP = node->GP();
+ const auto &nodeChidren = nodeP->children();
+ if (nodeChidren.size() >= 2) {
+ auto codeSpace = stripQuotes(nodeChidren[0]);
+ auto code = stripQuotes(nodeChidren[1]);
+ auto &citationNode = nodeP->lookForChild(WKTConstants::CITATION);
+ auto &uriNode = nodeP->lookForChild(WKTConstants::URI);
+ PropertyMap propertiesId;
+ propertiesId.set(Identifier::CODESPACE_KEY, codeSpace);
+ bool authoritySet = false;
+ /*if (!isNull(citationNode))*/ {
+ const auto *citationNodeP = citationNode->GP();
+ if (citationNodeP->childrenSize() == 1) {
+ authoritySet = true;
+ propertiesId.set(Identifier::AUTHORITY_KEY,
+ stripQuotes(citationNodeP->children()[0]));
+ }
+ }
+ if (!authoritySet) {
+ propertiesId.set(Identifier::AUTHORITY_KEY, codeSpace);
+ }
+ /*if (!isNull(uriNode))*/ {
+ const auto *uriNodeP = uriNode->GP();
+ if (uriNodeP->childrenSize() == 1) {
+ propertiesId.set(Identifier::URI_KEY,
+ stripQuotes(uriNodeP->children()[0]));
+ }
+ }
+ if (nodeChidren.size() >= 3 &&
+ nodeChidren[2]->GP()->childrenSize() == 0) {
+ auto version = stripQuotes(nodeChidren[2]);
+ propertiesId.set(Identifier::VERSION_KEY, version);
+ }
+ return Identifier::create(code, propertiesId);
+ } else if (strict_ || !tolerant) {
+ ThrowNotEnoughChildren(nodeP->value());
+ } else {
+ std::string msg("not enough children in ");
+ msg += nodeP->value();
+ msg += " node";
+ warningList_.emplace_back(std::move(msg));
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+PropertyMap &WKTParser::Private::buildProperties(const WKTNodeNNPtr &node) {
+
+ if (propertyCount_ == MAX_PROPERTY_SIZE) {
+ throw ParsingException("MAX_PROPERTY_SIZE reached");
+ }
+ properties_[propertyCount_] = new PropertyMap();
+ auto &&properties = properties_[propertyCount_];
+ propertyCount_++;
+
+ std::string authNameFromAlias;
+ std::string codeFromAlias;
+ const auto *nodeP = node->GP();
+ const auto &nodeChildren = nodeP->children();
+ if (!nodeChildren.empty()) {
+ const auto &nodeName(nodeP->value());
+ auto name(stripQuotes(nodeChildren[0]));
+ if (ends_with(name, " (deprecated)")) {
+ name.resize(name.size() - strlen(" (deprecated)"));
+ properties->set(common::IdentifiedObject::DEPRECATED_KEY, true);
+ }
+
+ const char *tableNameForAlias = nullptr;
+ if (ci_equal(nodeName, WKTConstants::GEOGCS)) {
+ if (starts_with(name, "GCS_")) {
+ esriStyle_ = true;
+ if (name == "GCS_WGS_1984") {
+ name = "WGS 84";
+ } else {
+ tableNameForAlias = "geodetic_crs";
+ }
+ }
+ } else if (esriStyle_ && ci_equal(nodeName, WKTConstants::SPHEROID)) {
+ if (name == "WGS_1984") {
+ name = "WGS 84";
+ authNameFromAlias = Identifier::EPSG;
+ codeFromAlias = "7030";
+ } else {
+ tableNameForAlias = "ellipsoid";
+ }
+ }
+
+ if (dbContext_ && tableNameForAlias) {
+ std::string outTableName;
+ auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
+ std::string());
+ auto officialName = authFactory->getOfficialNameFromAlias(
+ name, tableNameForAlias, "ESRI", false, outTableName,
+ authNameFromAlias, codeFromAlias);
+ if (!officialName.empty()) {
+ name = officialName;
+
+ // Clearing authority for geodetic_crs because of
+ // potential axis order mismatch.
+ if (strcmp(tableNameForAlias, "geodetic_crs") == 0) {
+ authNameFromAlias.clear();
+ codeFromAlias.clear();
+ }
+ }
+ }
+
+ properties->set(IdentifiedObject::NAME_KEY, name);
+ }
+
+ auto identifiers = ArrayOfBaseObject::create();
+ for (const auto &subNode : nodeChildren) {
+ const auto &subNodeName(subNode->GP()->value());
+ if (ci_equal(subNodeName, WKTConstants::ID) ||
+ ci_equal(subNodeName, WKTConstants::AUTHORITY)) {
+ auto id = buildId(subNode);
+ if (id) {
+ identifiers->add(NN_NO_CHECK(id));
+ }
+ }
+ }
+ if (identifiers->empty() && !authNameFromAlias.empty()) {
+ identifiers->add(Identifier::create(
+ codeFromAlias,
+ PropertyMap()
+ .set(Identifier::CODESPACE_KEY, authNameFromAlias)
+ .set(Identifier::AUTHORITY_KEY, authNameFromAlias)));
+ }
+ if (!identifiers->empty()) {
+ properties->set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
+ }
+
+ auto &remarkNode = nodeP->lookForChild(WKTConstants::REMARK);
+ if (!isNull(remarkNode)) {
+ const auto &remarkChildren = remarkNode->GP()->children();
+ if (remarkChildren.size() == 1) {
+ properties->set(IdentifiedObject::REMARKS_KEY,
+ stripQuotes(remarkChildren[0]));
+ } else {
+ ThrowNotRequiredNumberOfChildren(remarkNode->GP()->value());
+ }
+ }
+
+ ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create();
+ for (const auto &subNode : nodeP->children()) {
+ const auto &subNodeName(subNode->GP()->value());
+ if (ci_equal(subNodeName, WKTConstants::USAGE)) {
+ auto objectDomain = buildObjectDomain(subNode);
+ if (!objectDomain) {
+ throw ParsingException(
+ concat("missing children in ", subNodeName, " node"));
+ }
+ array->add(NN_NO_CHECK(objectDomain));
+ }
+ }
+ if (!array->empty()) {
+ properties->set(ObjectUsage::OBJECT_DOMAIN_KEY, array);
+ } else {
+ auto objectDomain = buildObjectDomain(node);
+ if (objectDomain) {
+ properties->set(ObjectUsage::OBJECT_DOMAIN_KEY,
+ NN_NO_CHECK(objectDomain));
+ }
+ }
+
+ return *properties;
+}
+
+// ---------------------------------------------------------------------------
+
+ObjectDomainPtr
+WKTParser::Private::buildObjectDomain(const WKTNodeNNPtr &node) {
+
+ const auto *nodeP = node->GP();
+ auto &scopeNode = nodeP->lookForChild(WKTConstants::SCOPE);
+ auto &areaNode = nodeP->lookForChild(WKTConstants::AREA);
+ auto &bboxNode = nodeP->lookForChild(WKTConstants::BBOX);
+ auto &verticalExtentNode =
+ nodeP->lookForChild(WKTConstants::VERTICALEXTENT);
+ auto &temporalExtentNode = nodeP->lookForChild(WKTConstants::TIMEEXTENT);
+ if (!isNull(scopeNode) || !isNull(areaNode) || !isNull(bboxNode) ||
+ !isNull(verticalExtentNode) || !isNull(temporalExtentNode)) {
+ optional<std::string> scope;
+ const auto *scopeNodeP = scopeNode->GP();
+ const auto &scopeChildren = scopeNodeP->children();
+ if (scopeChildren.size() == 1) {
+ scope = stripQuotes(scopeChildren[0]);
+ }
+ ExtentPtr extent;
+ if (!isNull(areaNode) || !isNull(bboxNode)) {
+ util::optional<std::string> description;
+ std::vector<GeographicExtentNNPtr> geogExtent;
+ std::vector<VerticalExtentNNPtr> verticalExtent;
+ std::vector<TemporalExtentNNPtr> temporalExtent;
+ if (!isNull(areaNode)) {
+ const auto &areaChildren = areaNode->GP()->children();
+ if (areaChildren.size() == 1) {
+ description = stripQuotes(areaChildren[0]);
+ } else {
+ ThrowNotRequiredNumberOfChildren(areaNode->GP()->value());
+ }
+ }
+ if (!isNull(bboxNode)) {
+ const auto &bboxChildren = bboxNode->GP()->children();
+ if (bboxChildren.size() == 4) {
+ try {
+ double south = asDouble(bboxChildren[0]);
+ double west = asDouble(bboxChildren[1]);
+ double north = asDouble(bboxChildren[2]);
+ double east = asDouble(bboxChildren[3]);
+ auto bbox = GeographicBoundingBox::create(west, south,
+ east, north);
+ geogExtent.emplace_back(bbox);
+ } catch (const std::exception &) {
+ throw ParsingException(concat("not 4 double values in ",
+ bboxNode->GP()->value(),
+ " node"));
+ }
+ } else {
+ ThrowNotRequiredNumberOfChildren(bboxNode->GP()->value());
+ }
+ }
+
+ if (!isNull(verticalExtentNode)) {
+ const auto &verticalExtentChildren =
+ verticalExtentNode->GP()->children();
+ const auto verticalExtentChildrenSize =
+ verticalExtentChildren.size();
+ if (verticalExtentChildrenSize == 2 ||
+ verticalExtentChildrenSize == 3) {
+ double min;
+ double max;
+ try {
+ min = asDouble(verticalExtentChildren[0]);
+ max = asDouble(verticalExtentChildren[1]);
+ } catch (const std::exception &) {
+ throw ParsingException(
+ concat("not 2 double values in ",
+ verticalExtentNode->GP()->value(), " node"));
+ }
+ UnitOfMeasure unit = UnitOfMeasure::METRE;
+ if (verticalExtentChildrenSize == 3) {
+ unit = buildUnit(verticalExtentChildren[2],
+ UnitOfMeasure::Type::LINEAR);
+ }
+ verticalExtent.emplace_back(VerticalExtent::create(
+ min, max, util::nn_make_shared<UnitOfMeasure>(unit)));
+ } else {
+ ThrowNotRequiredNumberOfChildren(
+ verticalExtentNode->GP()->value());
+ }
+ }
+
+ if (!isNull(temporalExtentNode)) {
+ const auto &temporalExtentChildren =
+ temporalExtentNode->GP()->children();
+ if (temporalExtentChildren.size() == 2) {
+ temporalExtent.emplace_back(TemporalExtent::create(
+ stripQuotes(temporalExtentChildren[0]),
+ stripQuotes(temporalExtentChildren[1])));
+ } else {
+ ThrowNotRequiredNumberOfChildren(
+ temporalExtentNode->GP()->value());
+ }
+ }
+ extent = Extent::create(description, geogExtent, verticalExtent,
+ temporalExtent)
+ .as_nullable();
+ }
+ return ObjectDomain::create(scope, extent).as_nullable();
+ }
+
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+UnitOfMeasure WKTParser::Private::buildUnit(const WKTNodeNNPtr &node,
+ UnitOfMeasure::Type type) {
+ const auto *nodeP = node->GP();
+ const auto &children = nodeP->children();
+ if ((type != UnitOfMeasure::Type::TIME && children.size() < 2) ||
+ (type == UnitOfMeasure::Type::TIME && children.size() < 1)) {
+ ThrowNotEnoughChildren(nodeP->value());
+ }
+ try {
+ std::string unitName(stripQuotes(children[0]));
+ PropertyMap properties(buildProperties(node));
+ auto &idNode =
+ nodeP->lookForChild(WKTConstants::ID, WKTConstants::AUTHORITY);
+ if (!isNull(idNode) && idNode->GP()->childrenSize() < 2) {
+ emitRecoverableAssertion("not enough children in " +
+ idNode->GP()->value() + " node");
+ }
+ const bool hasValidIdNode =
+ !isNull(idNode) && idNode->GP()->childrenSize() >= 2;
+
+ const auto &idNodeChildren(idNode->GP()->children());
+ std::string codeSpace(hasValidIdNode ? stripQuotes(idNodeChildren[0])
+ : std::string());
+ std::string code(hasValidIdNode ? stripQuotes(idNodeChildren[1])
+ : std::string());
+
+ bool queryDb = true;
+ if (type == UnitOfMeasure::Type::UNKNOWN) {
+ if (ci_equal(unitName, "METER") || ci_equal(unitName, "METRE")) {
+ type = UnitOfMeasure::Type::LINEAR;
+ unitName = "metre";
+ if (codeSpace.empty()) {
+ codeSpace = Identifier::EPSG;
+ code = "9001";
+ queryDb = false;
+ }
+ } else if (ci_equal(unitName, "DEGREE") ||
+ ci_equal(unitName, "GRAD")) {
+ type = UnitOfMeasure::Type::ANGULAR;
+ }
+ }
+
+ if (esriStyle_ && dbContext_ && queryDb) {
+ std::string outTableName;
+ std::string authNameFromAlias;
+ std::string codeFromAlias;
+ auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
+ std::string());
+ auto officialName = authFactory->getOfficialNameFromAlias(
+ unitName, "unit_of_measure", "ESRI", false, outTableName,
+ authNameFromAlias, codeFromAlias);
+ if (!officialName.empty()) {
+ unitName = officialName;
+ codeSpace = authNameFromAlias;
+ code = codeFromAlias;
+ }
+ }
+
+ double convFactor = children.size() >= 2 ? asDouble(children[1]) : 0.0;
+ constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37;
+ constexpr double REL_ERROR = 1e-10;
+ // Fix common rounding errors
+ if (std::fabs(convFactor - UnitOfMeasure::DEGREE.conversionToSI()) <
+ REL_ERROR * convFactor) {
+ convFactor = UnitOfMeasure::DEGREE.conversionToSI();
+ } else if (std::fabs(convFactor - US_FOOT_CONV_FACTOR) <
+ REL_ERROR * convFactor) {
+ convFactor = US_FOOT_CONV_FACTOR;
+ }
+
+ return UnitOfMeasure(unitName, convFactor, type, codeSpace, code);
+ } catch (const std::exception &e) {
+ throw buildRethrow(__FUNCTION__, e);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+// node here is a parent node, not a UNIT/LENGTHUNIT/ANGLEUNIT/TIMEUNIT/... node
+UnitOfMeasure WKTParser::Private::buildUnitInSubNode(const WKTNodeNNPtr &node,
+ UnitOfMeasure::Type type) {
+ const auto *nodeP = node->GP();
+ {
+ auto &unitNode = nodeP->lookForChild(WKTConstants::LENGTHUNIT);
+ if (!isNull(unitNode)) {
+ return buildUnit(unitNode, UnitOfMeasure::Type::LINEAR);
+ }
+ }
+
+ {
+ auto &unitNode = nodeP->lookForChild(WKTConstants::ANGLEUNIT);
+ if (!isNull(unitNode)) {
+ return buildUnit(unitNode, UnitOfMeasure::Type::ANGULAR);
+ }
+ }
+
+ {
+ auto &unitNode = nodeP->lookForChild(WKTConstants::SCALEUNIT);
+ if (!isNull(unitNode)) {
+ return buildUnit(unitNode, UnitOfMeasure::Type::SCALE);
+ }
+ }
+
+ {
+ auto &unitNode = nodeP->lookForChild(WKTConstants::TIMEUNIT);
+ if (!isNull(unitNode)) {
+ return buildUnit(unitNode, UnitOfMeasure::Type::TIME);
+ }
+ }
+ {
+ auto &unitNode = nodeP->lookForChild(WKTConstants::TEMPORALQUANTITY);
+ if (!isNull(unitNode)) {
+ return buildUnit(unitNode, UnitOfMeasure::Type::TIME);
+ }
+ }
+
+ {
+ auto &unitNode = nodeP->lookForChild(WKTConstants::PARAMETRICUNIT);
+ if (!isNull(unitNode)) {
+ return buildUnit(unitNode, UnitOfMeasure::Type::PARAMETRIC);
+ }
+ }
+
+ {
+ auto &unitNode = nodeP->lookForChild(WKTConstants::UNIT);
+ if (!isNull(unitNode)) {
+ return buildUnit(unitNode, type);
+ }
+ }
+
+ return UnitOfMeasure::NONE;
+}
+
+// ---------------------------------------------------------------------------
+
+EllipsoidNNPtr WKTParser::Private::buildEllipsoid(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ const auto &children = nodeP->children();
+ if (children.size() < 3) {
+ ThrowNotEnoughChildren(nodeP->value());
+ }
+ try {
+ UnitOfMeasure unit =
+ buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR);
+ if (unit == UnitOfMeasure::NONE) {
+ unit = UnitOfMeasure::METRE;
+ }
+ Length semiMajorAxis(asDouble(children[1]), unit);
+ Scale invFlattening(asDouble(children[2]));
+ const auto celestialBody(
+ Ellipsoid::guessBodyName(dbContext_, semiMajorAxis.getSIValue()));
+ if (invFlattening.getSIValue() == 0) {
+ return Ellipsoid::createSphere(buildProperties(node), semiMajorAxis,
+ celestialBody);
+ } else {
+ return Ellipsoid::createFlattenedSphere(
+ buildProperties(node), semiMajorAxis, invFlattening,
+ celestialBody);
+ }
+ } catch (const std::exception &e) {
+ throw buildRethrow(__FUNCTION__, e);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+PrimeMeridianNNPtr WKTParser::Private::buildPrimeMeridian(
+ const WKTNodeNNPtr &node, const UnitOfMeasure &defaultAngularUnit) {
+ const auto *nodeP = node->GP();
+ const auto &children = nodeP->children();
+ if (children.size() < 2) {
+ ThrowNotEnoughChildren(nodeP->value());
+ }
+ auto name = stripQuotes(children[0]);
+ UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR);
+ if (unit == UnitOfMeasure::NONE) {
+ unit = defaultAngularUnit;
+ if (unit == UnitOfMeasure::NONE) {
+ unit = UnitOfMeasure::DEGREE;
+ }
+ }
+ try {
+ double angleValue = asDouble(children[1]);
+
+ // Correct for GDAL WKT1 departure
+ if (name == "Paris" && std::fabs(angleValue - 2.33722917) < 1e-8 &&
+ unit == UnitOfMeasure::GRAD) {
+ angleValue = 2.5969213;
+ }
+
+ Angle angle(angleValue, unit);
+ return PrimeMeridian::create(buildProperties(node), angle);
+ } catch (const std::exception &e) {
+ throw buildRethrow(__FUNCTION__, e);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+optional<std::string> WKTParser::Private::getAnchor(const WKTNodeNNPtr &node) {
+
+ auto &anchorNode = node->GP()->lookForChild(WKTConstants::ANCHOR);
+ if (anchorNode->GP()->childrenSize() == 1) {
+ return optional<std::string>(
+ stripQuotes(anchorNode->GP()->children()[0]));
+ }
+ return optional<std::string>();
+}
+
+// ---------------------------------------------------------------------------
+
+static const PrimeMeridianNNPtr &
+fixupPrimeMeridan(const EllipsoidNNPtr &ellipsoid,
+ const PrimeMeridianNNPtr &pm) {
+ return (ellipsoid->celestialBody() != Ellipsoid::EARTH &&
+ pm.get() == PrimeMeridian::GREENWICH.get())
+ ? PrimeMeridian::REFERENCE_MERIDIAN
+ : pm;
+}
+
+// ---------------------------------------------------------------------------
+
+GeodeticReferenceFrameNNPtr WKTParser::Private::buildGeodeticReferenceFrame(
+ const WKTNodeNNPtr &node, const PrimeMeridianNNPtr &primeMeridian,
+ const WKTNodeNNPtr &dynamicNode) {
+ const auto *nodeP = node->GP();
+ auto &ellipsoidNode =
+ nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID);
+ if (isNull(ellipsoidNode)) {
+ ThrowMissing(WKTConstants::ELLIPSOID);
+ }
+ auto &properties = buildProperties(node);
+
+ // do that before buildEllipsoid() so that esriStyle_ can be set
+ auto name = stripQuotes(nodeP->children()[0]);
+ if (name == "WGS_1984") {
+ properties.set(IdentifiedObject::NAME_KEY,
+ GeodeticReferenceFrame::EPSG_6326->nameStr());
+ } else if (starts_with(name, "D_")) {
+ esriStyle_ = true;
+ const char *tableNameForAlias = nullptr;
+ std::string authNameFromAlias;
+ std::string codeFromAlias;
+ if (name == "D_WGS_1984") {
+ name = "World Geodetic System 1984";
+ authNameFromAlias = Identifier::EPSG;
+ codeFromAlias = "6326";
+ } else {
+ tableNameForAlias = "geodetic_datum";
+ }
+
+ if (dbContext_ && tableNameForAlias) {
+ std::string outTableName;
+ auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
+ std::string());
+ auto officialName = authFactory->getOfficialNameFromAlias(
+ name, tableNameForAlias, "ESRI", false, outTableName,
+ authNameFromAlias, codeFromAlias);
+ if (!officialName.empty()) {
+ if (primeMeridian->nameStr() !=
+ PrimeMeridian::GREENWICH->nameStr()) {
+ auto nameWithPM =
+ officialName + " (" + primeMeridian->nameStr() + ")";
+ if (dbContext_->isKnownName(nameWithPM, "geodetic_datum")) {
+ officialName = nameWithPM;
+ }
+ }
+ name = officialName;
+ }
+ }
+
+ properties.set(IdentifiedObject::NAME_KEY, name);
+ if (!authNameFromAlias.empty()) {
+ auto identifiers = ArrayOfBaseObject::create();
+ identifiers->add(Identifier::create(
+ codeFromAlias,
+ PropertyMap()
+ .set(Identifier::CODESPACE_KEY, authNameFromAlias)
+ .set(Identifier::AUTHORITY_KEY, authNameFromAlias)));
+ properties.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
+ }
+ } else if (name.find('_') != std::string::npos) {
+ // Likely coming from WKT1
+ if (dbContext_) {
+ auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
+ std::string());
+ auto res = authFactory->createObjectsFromName(
+ name, {AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME},
+ true, 1);
+ bool foundDatumName = false;
+ if (!res.empty()) {
+ const auto &refDatum = res.front();
+ if (metadata::Identifier::isEquivalentName(
+ name.c_str(), refDatum->nameStr().c_str())) {
+ foundDatumName = true;
+ properties.set(IdentifiedObject::NAME_KEY,
+ refDatum->nameStr());
+ if (!properties.get(Identifier::CODESPACE_KEY) &&
+ refDatum->identifiers().size() == 1) {
+ const auto &id = refDatum->identifiers()[0];
+ auto identifiers = ArrayOfBaseObject::create();
+ identifiers->add(Identifier::create(
+ id->code(), PropertyMap()
+ .set(Identifier::CODESPACE_KEY,
+ *id->codeSpace())
+ .set(Identifier::AUTHORITY_KEY,
+ *id->codeSpace())));
+ properties.set(IdentifiedObject::IDENTIFIERS_KEY,
+ identifiers);
+ }
+ }
+ } else {
+ // Get official name from database if AUTHORITY is present
+ auto &idNode = nodeP->lookForChild(WKTConstants::AUTHORITY);
+ if (!isNull(idNode)) {
+ try {
+ auto id = buildId(idNode);
+ auto authFactory2 = AuthorityFactory::create(
+ NN_NO_CHECK(dbContext_), *id->codeSpace());
+ auto dbDatum =
+ authFactory2->createGeodeticDatum(id->code());
+ foundDatumName = true;
+ properties.set(IdentifiedObject::NAME_KEY,
+ dbDatum->nameStr());
+ } catch (const std::exception &) {
+ }
+ }
+ }
+
+ if (!foundDatumName) {
+ std::string outTableName;
+ std::string authNameFromAlias;
+ std::string codeFromAlias;
+ auto officialName = authFactory->getOfficialNameFromAlias(
+ name, "geodetic_datum", std::string(), true, outTableName,
+ authNameFromAlias, codeFromAlias);
+ if (!officialName.empty()) {
+ properties.set(IdentifiedObject::NAME_KEY, officialName);
+ }
+ }
+ }
+ }
+
+ auto ellipsoid = buildEllipsoid(ellipsoidNode);
+ const auto &primeMeridianModified =
+ fixupPrimeMeridan(ellipsoid, primeMeridian);
+
+ auto &TOWGS84Node = nodeP->lookForChild(WKTConstants::TOWGS84);
+ if (!isNull(TOWGS84Node)) {
+ const auto &TOWGS84Children = TOWGS84Node->GP()->children();
+ const size_t TOWGS84Size = TOWGS84Children.size();
+ if (TOWGS84Size == 3 || TOWGS84Size == 7) {
+ try {
+ for (const auto &child : TOWGS84Children) {
+ toWGS84Parameters_.push_back(asDouble(child));
+ }
+ for (size_t i = TOWGS84Size; i < 7; ++i) {
+ toWGS84Parameters_.push_back(0.0);
+ }
+ } catch (const std::exception &) {
+ throw ParsingException("Invalid TOWGS84 node");
+ }
+ } else {
+ throw ParsingException("Invalid TOWGS84 node");
+ }
+ }
+
+ auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
+ const auto &extensionChildren = extensionNode->GP()->children();
+ if (extensionChildren.size() == 2) {
+ if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) {
+ datumPROJ4Grids_ = stripQuotes(extensionChildren[1]);
+ }
+ }
+
+ if (!isNull(dynamicNode)) {
+ double frameReferenceEpoch = 0.0;
+ util::optional<std::string> modelName;
+ parseDynamic(dynamicNode, frameReferenceEpoch, modelName);
+ return DynamicGeodeticReferenceFrame::create(
+ properties, ellipsoid, getAnchor(node), primeMeridianModified,
+ common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR),
+ modelName);
+ }
+
+ return GeodeticReferenceFrame::create(
+ properties, ellipsoid, getAnchor(node), primeMeridianModified);
+}
+
+// ---------------------------------------------------------------------------
+
+DatumEnsembleNNPtr
+WKTParser::Private::buildDatumEnsemble(const WKTNodeNNPtr &node,
+ const PrimeMeridianPtr &primeMeridian,
+ bool expectEllipsoid) {
+ const auto *nodeP = node->GP();
+ auto &ellipsoidNode =
+ nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID);
+ if (expectEllipsoid && isNull(ellipsoidNode)) {
+ ThrowMissing(WKTConstants::ELLIPSOID);
+ }
+
+ std::vector<DatumNNPtr> datums;
+ for (const auto &subNode : nodeP->children()) {
+ if (ci_equal(subNode->GP()->value(), WKTConstants::MEMBER)) {
+ if (subNode->GP()->childrenSize() == 0) {
+ throw ParsingException("Invalid MEMBER node");
+ }
+ if (expectEllipsoid) {
+ datums.emplace_back(GeodeticReferenceFrame::create(
+ buildProperties(subNode), buildEllipsoid(ellipsoidNode),
+ optional<std::string>(),
+ primeMeridian ? NN_NO_CHECK(primeMeridian)
+ : PrimeMeridian::GREENWICH));
+ } else {
+ datums.emplace_back(
+ VerticalReferenceFrame::create(buildProperties(subNode)));
+ }
+ }
+ }
+
+ auto &accuracyNode = nodeP->lookForChild(WKTConstants::ENSEMBLEACCURACY);
+ auto &accuracyNodeChildren = accuracyNode->GP()->children();
+ if (accuracyNodeChildren.empty()) {
+ ThrowMissing(WKTConstants::ENSEMBLEACCURACY);
+ }
+ auto accuracy =
+ PositionalAccuracy::create(accuracyNodeChildren[0]->GP()->value());
+
+ try {
+ return DatumEnsemble::create(buildProperties(node), datums, accuracy);
+ } catch (const util::Exception &e) {
+ throw buildRethrow(__FUNCTION__, e);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+MeridianNNPtr WKTParser::Private::buildMeridian(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ const auto &children = nodeP->children();
+ if (children.size() < 2) {
+ ThrowNotEnoughChildren(nodeP->value());
+ }
+ UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR);
+ try {
+ double angleValue = asDouble(children[0]);
+ Angle angle(angleValue, unit);
+ return Meridian::create(angle);
+ } catch (const std::exception &e) {
+ throw buildRethrow(__FUNCTION__, e);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+PROJ_NO_RETURN static void ThrowParsingExceptionMissingUNIT() {
+ throw ParsingException("buildCS: missing UNIT");
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateSystemAxisNNPtr
+WKTParser::Private::buildAxis(const WKTNodeNNPtr &node,
+ const UnitOfMeasure &unitIn,
+ const UnitOfMeasure::Type &unitType,
+ bool isGeocentric, int expectedOrderNum) {
+ const auto *nodeP = node->GP();
+ const auto &children = nodeP->children();
+ if (children.size() < 2) {
+ ThrowNotEnoughChildren(nodeP->value());
+ }
+
+ auto &orderNode = nodeP->lookForChild(WKTConstants::ORDER);
+ if (!isNull(orderNode)) {
+ const auto &orderNodeChildren = orderNode->GP()->children();
+ if (orderNodeChildren.size() != 1) {
+ ThrowNotEnoughChildren(WKTConstants::ORDER);
+ }
+ const auto &order = orderNodeChildren[0]->GP()->value();
+ int orderNum;
+ try {
+ orderNum = std::stoi(order);
+ } catch (const std::exception &) {
+ throw ParsingException(
+ concat("buildAxis: invalid ORDER value: ", order));
+ }
+ if (orderNum != expectedOrderNum) {
+ throw ParsingException(
+ concat("buildAxis: did not get expected ORDER value: ", order));
+ }
+ }
+
+ // The axis designation in WK2 can be: "name", "(abbrev)" or "name
+ // (abbrev)"
+ std::string axisDesignation(stripQuotes(children[0]));
+ size_t sepPos = axisDesignation.find(" (");
+ std::string axisName;
+ std::string abbreviation;
+ if (sepPos != std::string::npos && axisDesignation.back() == ')') {
+ axisName = CoordinateSystemAxis::normalizeAxisName(
+ axisDesignation.substr(0, sepPos));
+ abbreviation = axisDesignation.substr(sepPos + 2);
+ abbreviation.resize(abbreviation.size() - 1);
+ } else if (!axisDesignation.empty() && axisDesignation[0] == '(' &&
+ axisDesignation.back() == ')') {
+ abbreviation = axisDesignation.substr(1, axisDesignation.size() - 2);
+ if (abbreviation == AxisAbbreviation::E) {
+ axisName = AxisName::Easting;
+ } else if (abbreviation == AxisAbbreviation::N) {
+ axisName = AxisName::Northing;
+ } else if (abbreviation == AxisAbbreviation::lat) {
+ axisName = AxisName::Latitude;
+ } else if (abbreviation == AxisAbbreviation::lon) {
+ axisName = AxisName::Longitude;
+ }
+ } else {
+ axisName = CoordinateSystemAxis::normalizeAxisName(axisDesignation);
+ if (axisName == AxisName::Latitude) {
+ abbreviation = AxisAbbreviation::lat;
+ } else if (axisName == AxisName::Longitude) {
+ abbreviation = AxisAbbreviation::lon;
+ } else if (axisName == AxisName::Ellipsoidal_height) {
+ abbreviation = AxisAbbreviation::h;
+ }
+ }
+ const std::string &dirString = children[1]->GP()->value();
+ const AxisDirection *direction = AxisDirection::valueOf(dirString);
+
+ // WKT2, geocentric CS: axis names are omitted
+ if (axisName.empty()) {
+ if (direction == &AxisDirection::GEOCENTRIC_X &&
+ abbreviation == AxisAbbreviation::X) {
+ axisName = AxisName::Geocentric_X;
+ } else if (direction == &AxisDirection::GEOCENTRIC_Y &&
+ abbreviation == AxisAbbreviation::Y) {
+ axisName = AxisName::Geocentric_Y;
+ } else if (direction == &AxisDirection::GEOCENTRIC_Z &&
+ abbreviation == AxisAbbreviation::Z) {
+ axisName = AxisName::Geocentric_Z;
+ }
+ }
+
+ // WKT1
+ if (!direction && isGeocentric && axisName == AxisName::Geocentric_X) {
+ abbreviation = AxisAbbreviation::X;
+ direction = &AxisDirection::GEOCENTRIC_X;
+ } else if (!direction && isGeocentric &&
+ axisName == AxisName::Geocentric_Y) {
+ abbreviation = AxisAbbreviation::Y;
+ direction = &AxisDirection::GEOCENTRIC_Y;
+ } else if (isGeocentric && axisName == AxisName::Geocentric_Z &&
+ (dirString == AxisDirectionWKT1::NORTH.toString() ||
+ dirString == AxisDirectionWKT1::OTHER.toString())) {
+ abbreviation = AxisAbbreviation::Z;
+ direction = &AxisDirection::GEOCENTRIC_Z;
+ } else if (dirString == AxisDirectionWKT1::OTHER.toString()) {
+ direction = &AxisDirection::UNSPECIFIED;
+ } else if (!direction && AxisDirectionWKT1::valueOf(dirString) != nullptr) {
+ direction = AxisDirection::valueOf(tolower(dirString));
+ }
+
+ if (!direction) {
+ throw ParsingException(
+ concat("unhandled axis direction: ", children[1]->GP()->value()));
+ }
+ UnitOfMeasure unit(buildUnitInSubNode(node));
+ if (unit == UnitOfMeasure::NONE) {
+ // If no unit in the AXIS node, use the one potentially coming from
+ // the CS.
+ unit = unitIn;
+ if (unit == UnitOfMeasure::NONE &&
+ unitType != UnitOfMeasure::Type::NONE &&
+ unitType != UnitOfMeasure::Type::TIME) {
+ ThrowParsingExceptionMissingUNIT();
+ }
+ }
+
+ auto &meridianNode = nodeP->lookForChild(WKTConstants::MERIDIAN);
+
+ return CoordinateSystemAxis::create(
+ buildProperties(node).set(IdentifiedObject::NAME_KEY, axisName),
+ abbreviation, *direction, unit,
+ !isNull(meridianNode) ? buildMeridian(meridianNode).as_nullable()
+ : nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+static const PropertyMap emptyPropertyMap{};
+
+PROJ_NO_RETURN static void ThrowParsingException(const std::string &msg) {
+ throw ParsingException(msg);
+}
+
+static ParsingException
+buildParsingExceptionInvalidAxisCount(const std::string &csType) {
+ return ParsingException(
+ concat("buildCS: invalid CS axis count for ", csType));
+}
+
+CoordinateSystemNNPtr
+WKTParser::Private::buildCS(const WKTNodeNNPtr &node, /* maybe null */
+ const WKTNodeNNPtr &parentNode,
+ const UnitOfMeasure &defaultAngularUnit) {
+ bool isGeocentric = false;
+ std::string csType;
+ const int numberOfAxis =
+ parentNode->countChildrenOfName(WKTConstants::AXIS);
+ int axisCount = numberOfAxis;
+ if (!isNull(node)) {
+ const auto *nodeP = node->GP();
+ const auto &children = nodeP->children();
+ if (children.size() < 2) {
+ ThrowNotEnoughChildren(nodeP->value());
+ }
+ csType = children[0]->GP()->value();
+ try {
+ axisCount = std::stoi(children[1]->GP()->value());
+ } catch (const std::exception &) {
+ ThrowParsingException(concat("buildCS: invalid CS axis count: ",
+ children[1]->GP()->value()));
+ }
+ } else {
+ const char *csTypeCStr = "";
+ const auto &parentNodeName = parentNode->GP()->value();
+ if (ci_equal(parentNodeName, WKTConstants::GEOCCS)) {
+ csTypeCStr = "Cartesian";
+ isGeocentric = true;
+ if (axisCount == 0) {
+ auto unit =
+ buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
+ if (unit == UnitOfMeasure::NONE) {
+ ThrowParsingExceptionMissingUNIT();
+ }
+ return CartesianCS::createGeocentric(unit);
+ }
+ } else if (ci_equal(parentNodeName, WKTConstants::GEOGCS)) {
+ csTypeCStr = "Ellipsoidal";
+ if (axisCount == 0) {
+ // Missing axis with GEOGCS ? Presumably Long/Lat order
+ // implied
+ auto unit = buildUnitInSubNode(parentNode,
+ UnitOfMeasure::Type::ANGULAR);
+ if (unit == UnitOfMeasure::NONE) {
+ ThrowParsingExceptionMissingUNIT();
+ }
+ // WKT1 --> long/lat
+ return EllipsoidalCS::createLongitudeLatitude(unit);
+ }
+ } else if (ci_equal(parentNodeName, WKTConstants::BASEGEODCRS) ||
+ ci_equal(parentNodeName, WKTConstants::BASEGEOGCRS)) {
+ csTypeCStr = "Ellipsoidal";
+ if (axisCount == 0) {
+ auto unit = buildUnitInSubNode(parentNode,
+ UnitOfMeasure::Type::ANGULAR);
+ if (unit == UnitOfMeasure::NONE) {
+ unit = defaultAngularUnit;
+ }
+ // WKT2 --> presumably lat/long
+ return EllipsoidalCS::createLatitudeLongitude(unit);
+ }
+ } else if (ci_equal(parentNodeName, WKTConstants::PROJCS) ||
+ ci_equal(parentNodeName, WKTConstants::BASEPROJCRS) ||
+ ci_equal(parentNodeName, WKTConstants::BASEENGCRS)) {
+ csTypeCStr = "Cartesian";
+ if (axisCount == 0) {
+ auto unit =
+ buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
+ if (unit == UnitOfMeasure::NONE) {
+ if (ci_equal(parentNodeName, WKTConstants::PROJCS)) {
+ ThrowParsingExceptionMissingUNIT();
+ } else {
+ unit = UnitOfMeasure::METRE;
+ }
+ }
+ return CartesianCS::createEastingNorthing(unit);
+ }
+ } else if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
+ ci_equal(parentNodeName, WKTConstants::BASEVERTCRS)) {
+ csTypeCStr = "vertical";
+ if (axisCount == 0) {
+ auto unit =
+ buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
+ if (unit == UnitOfMeasure::NONE) {
+ if (ci_equal(parentNodeName, WKTConstants::VERT_CS)) {
+ ThrowParsingExceptionMissingUNIT();
+ } else {
+ unit = UnitOfMeasure::METRE;
+ }
+ }
+ return VerticalCS::createGravityRelatedHeight(unit);
+ }
+ } else if (ci_equal(parentNodeName, WKTConstants::LOCAL_CS)) {
+ if (axisCount == 0) {
+ auto unit =
+ buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
+ if (unit == UnitOfMeasure::NONE) {
+ unit = UnitOfMeasure::METRE;
+ }
+ return CartesianCS::createEastingNorthing(unit);
+ } else if (axisCount == 1) {
+ csTypeCStr = "vertical";
+ } else if (axisCount == 2) {
+ csTypeCStr = "Cartesian";
+ } else {
+ throw ParsingException(
+ "buildCS: unexpected AXIS count for LOCAL_CS");
+ }
+ } else if (ci_equal(parentNodeName, WKTConstants::BASEPARAMCRS)) {
+ csTypeCStr = "parametric";
+ if (axisCount == 0) {
+ auto unit =
+ buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
+ if (unit == UnitOfMeasure::NONE) {
+ unit = UnitOfMeasure("unknown", 1,
+ UnitOfMeasure::Type::PARAMETRIC);
+ }
+ return ParametricCS::create(
+ emptyPropertyMap,
+ CoordinateSystemAxis::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "unknown parametric"),
+ std::string(), AxisDirection::UNSPECIFIED, unit));
+ }
+ } else if (ci_equal(parentNodeName, WKTConstants::BASETIMECRS)) {
+ csTypeCStr = "temporal";
+ if (axisCount == 0) {
+ auto unit =
+ buildUnitInSubNode(parentNode, UnitOfMeasure::Type::TIME);
+ if (unit == UnitOfMeasure::NONE) {
+ unit =
+ UnitOfMeasure("unknown", 1, UnitOfMeasure::Type::TIME);
+ }
+ return DateTimeTemporalCS::create(
+ emptyPropertyMap,
+ CoordinateSystemAxis::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "unknown temporal"),
+ std::string(), AxisDirection::FUTURE, unit));
+ }
+ } else {
+ // Shouldn't happen normally
+ throw ParsingException(
+ concat("buildCS: unexpected parent node: ", parentNodeName));
+ }
+ csType = csTypeCStr;
+ }
+
+ if (axisCount != 1 && axisCount != 2 && axisCount != 3) {
+ throw buildParsingExceptionInvalidAxisCount(csType);
+ }
+ if (numberOfAxis != axisCount) {
+ throw ParsingException("buildCS: declared number of axis by CS node "
+ "and number of AXIS are inconsistent");
+ }
+
+ const auto unitType =
+ ci_equal(csType, "ellipsoidal")
+ ? UnitOfMeasure::Type::ANGULAR
+ : ci_equal(csType, "ordinal")
+ ? UnitOfMeasure::Type::NONE
+ : ci_equal(csType, "parametric")
+ ? UnitOfMeasure::Type::PARAMETRIC
+ : ci_equal(csType, "Cartesian") ||
+ ci_equal(csType, "vertical")
+ ? UnitOfMeasure::Type::LINEAR
+ : (ci_equal(csType, "temporal") ||
+ ci_equal(csType, "TemporalDateTime") ||
+ ci_equal(csType, "TemporalCount") ||
+ ci_equal(csType, "TemporalMeasure"))
+ ? UnitOfMeasure::Type::TIME
+ : UnitOfMeasure::Type::UNKNOWN;
+ UnitOfMeasure unit = buildUnitInSubNode(parentNode, unitType);
+
+ std::vector<CoordinateSystemAxisNNPtr> axisList;
+ for (int i = 0; i < axisCount; i++) {
+ axisList.emplace_back(
+ buildAxis(parentNode->GP()->lookForChild(WKTConstants::AXIS, i),
+ unit, unitType, isGeocentric, i + 1));
+ };
+
+ const PropertyMap &csMap = emptyPropertyMap;
+ if (ci_equal(csType, "ellipsoidal")) {
+ if (axisCount == 2) {
+ return EllipsoidalCS::create(csMap, axisList[0], axisList[1]);
+ } else if (axisCount == 3) {
+ return EllipsoidalCS::create(csMap, axisList[0], axisList[1],
+ axisList[2]);
+ }
+ } else if (ci_equal(csType, "Cartesian")) {
+ if (axisCount == 2) {
+ return CartesianCS::create(csMap, axisList[0], axisList[1]);
+ } else if (axisCount == 3) {
+ return CartesianCS::create(csMap, axisList[0], axisList[1],
+ axisList[2]);
+ }
+ } else if (ci_equal(csType, "vertical")) {
+ if (axisCount == 1) {
+ return VerticalCS::create(csMap, axisList[0]);
+ }
+ } else if (ci_equal(csType, "spherical")) {
+ if (axisCount == 3) {
+ return SphericalCS::create(csMap, axisList[0], axisList[1],
+ axisList[2]);
+ }
+ } else if (ci_equal(csType, "ordinal")) { // WKT2-2018
+ return OrdinalCS::create(csMap, axisList);
+ } else if (ci_equal(csType, "parametric")) {
+ if (axisCount == 1) {
+ return ParametricCS::create(csMap, axisList[0]);
+ }
+ } else if (ci_equal(csType, "temporal")) { // WKT2-2015
+ if (axisCount == 1) {
+ return DateTimeTemporalCS::create(
+ csMap,
+ axisList[0]); // FIXME: there are 3 possible subtypes of
+ // TemporalCS
+ }
+ } else if (ci_equal(csType, "TemporalDateTime")) { // WKT2-2018
+ if (axisCount == 1) {
+ return DateTimeTemporalCS::create(csMap, axisList[0]);
+ }
+ } else if (ci_equal(csType, "TemporalCount")) { // WKT2-2018
+ if (axisCount == 1) {
+ return TemporalCountCS::create(csMap, axisList[0]);
+ }
+ } else if (ci_equal(csType, "TemporalMeasure")) { // WKT2-2018
+ if (axisCount == 1) {
+ return TemporalMeasureCS::create(csMap, axisList[0]);
+ }
+ } else {
+ throw ParsingException(concat("unhandled CS type: ", csType));
+ }
+ throw buildParsingExceptionInvalidAxisCount(csType);
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTParser::Private::addExtensionProj4ToProp(const WKTNode::Private *nodeP,
+ PropertyMap &props) {
+ auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
+ const auto &extensionChildren = extensionNode->GP()->children();
+ if (extensionChildren.size() == 2) {
+ if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
+ props.set("EXTENSION_PROJ4", stripQuotes(extensionChildren[1]));
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+GeodeticCRSNNPtr
+WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ auto &datumNode = nodeP->lookForChild(
+ WKTConstants::DATUM, WKTConstants::GEODETICDATUM, WKTConstants::TRF);
+ auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE);
+ if (isNull(datumNode) && isNull(ensembleNode)) {
+ throw ParsingException("Missing DATUM or ENSEMBLE node");
+ }
+
+ auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC);
+
+ auto &csNode = nodeP->lookForChild(WKTConstants::CS);
+ const auto &nodeName = nodeP->value();
+ if (isNull(csNode) && !ci_equal(nodeName, WKTConstants::GEOGCS) &&
+ !ci_equal(nodeName, WKTConstants::GEOCCS) &&
+ !ci_equal(nodeName, WKTConstants::BASEGEODCRS) &&
+ !ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) {
+ ThrowMissing(WKTConstants::CS);
+ }
+
+ auto &primeMeridianNode =
+ nodeP->lookForChild(WKTConstants::PRIMEM, WKTConstants::PRIMEMERIDIAN);
+ if (isNull(primeMeridianNode)) {
+ // PRIMEM is required in WKT1
+ if (ci_equal(nodeName, WKTConstants::GEOGCS) ||
+ ci_equal(nodeName, WKTConstants::GEOCCS)) {
+ emitRecoverableAssertion(nodeName + " should have a PRIMEM node");
+ }
+ }
+
+ auto angularUnit =
+ buildUnitInSubNode(node, ci_equal(nodeName, WKTConstants::GEOGCS)
+ ? UnitOfMeasure::Type::ANGULAR
+ : UnitOfMeasure::Type::UNKNOWN);
+ if (angularUnit.type() != UnitOfMeasure::Type::ANGULAR) {
+ angularUnit = UnitOfMeasure::NONE;
+ }
+
+ auto primeMeridian =
+ !isNull(primeMeridianNode)
+ ? buildPrimeMeridian(primeMeridianNode, angularUnit)
+ : PrimeMeridian::GREENWICH;
+ if (angularUnit == UnitOfMeasure::NONE) {
+ angularUnit = primeMeridian->longitude().unit();
+ }
+
+ auto props = buildProperties(node);
+ addExtensionProj4ToProp(nodeP, props);
+
+ // No explicit AXIS node ? (WKT1)
+ if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) {
+ props.set("IMPLICIT_CS", true);
+ }
+
+ auto datum =
+ !isNull(datumNode)
+ ? buildGeodeticReferenceFrame(datumNode, primeMeridian, dynamicNode)
+ .as_nullable()
+ : nullptr;
+ auto datumEnsemble =
+ !isNull(ensembleNode)
+ ? buildDatumEnsemble(ensembleNode, primeMeridian, true)
+ .as_nullable()
+ : nullptr;
+ auto cs = buildCS(csNode, node, angularUnit);
+ auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs);
+ if (ellipsoidalCS) {
+ assert(!ci_equal(nodeName, WKTConstants::GEOCCS));
+ try {
+ return GeographicCRS::create(props, datum, datumEnsemble,
+ NN_NO_CHECK(ellipsoidalCS));
+ } catch (const util::Exception &e) {
+ throw ParsingException(std::string("buildGeodeticCRS: ") +
+ e.what());
+ }
+ } else if (ci_equal(nodeName, WKTConstants::GEOGCRS) ||
+ ci_equal(nodeName, WKTConstants::GEOGRAPHICCRS) ||
+ ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) {
+ // This is a WKT2-2018 GeographicCRS. An ellipsoidal CS is expected
+ throw ParsingException(concat("ellipsoidal CS expected, but found ",
+ cs->getWKT2Type(true)));
+ }
+
+ auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
+ if (cartesianCS) {
+ if (cartesianCS->axisList().size() != 3) {
+ throw ParsingException(
+ "Cartesian CS for a GeodeticCRS should have 3 axis");
+ }
+ try {
+ return GeodeticCRS::create(props, datum, datumEnsemble,
+ NN_NO_CHECK(cartesianCS));
+ } catch (const util::Exception &e) {
+ throw ParsingException(std::string("buildGeodeticCRS: ") +
+ e.what());
+ }
+ }
+
+ auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
+ if (sphericalCS) {
+ try {
+ return GeodeticCRS::create(props, datum, datumEnsemble,
+ NN_NO_CHECK(sphericalCS));
+ } catch (const util::Exception &e) {
+ throw ParsingException(std::string("buildGeodeticCRS: ") +
+ e.what());
+ }
+ }
+
+ throw ParsingException(
+ concat("unhandled CS type: ", cs->getWKT2Type(true)));
+}
+
+// ---------------------------------------------------------------------------
+
+CRSNNPtr WKTParser::Private::buildDerivedGeodeticCRS(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ auto &baseGeodCRSNode = nodeP->lookForChild(WKTConstants::BASEGEODCRS,
+ WKTConstants::BASEGEOGCRS);
+ // given the constraints enforced on calling code path
+ assert(!isNull(baseGeodCRSNode));
+
+ auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode);
+
+ auto &derivingConversionNode =
+ nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
+ if (isNull(derivingConversionNode)) {
+ ThrowMissing(WKTConstants::DERIVINGCONVERSION);
+ }
+ auto derivingConversion = buildConversion(
+ derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
+
+ auto &csNode = nodeP->lookForChild(WKTConstants::CS);
+ if (isNull(csNode)) {
+ ThrowMissing(WKTConstants::CS);
+ }
+ auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
+
+ auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs);
+ if (ellipsoidalCS) {
+ return DerivedGeographicCRS::create(buildProperties(node), baseGeodCRS,
+ derivingConversion,
+ NN_NO_CHECK(ellipsoidalCS));
+ } else if (ci_equal(nodeP->value(), WKTConstants::GEOGCRS)) {
+ // This is a WKT2-2018 GeographicCRS. An ellipsoidal CS is expected
+ throw ParsingException(concat("ellipsoidal CS expected, but found ",
+ cs->getWKT2Type(true)));
+ }
+
+ auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
+ if (cartesianCS) {
+ if (cartesianCS->axisList().size() != 3) {
+ throw ParsingException(
+ "Cartesian CS for a GeodeticCRS should have 3 axis");
+ }
+ return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS,
+ derivingConversion,
+ NN_NO_CHECK(cartesianCS));
+ }
+
+ auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
+ if (sphericalCS) {
+ return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS,
+ derivingConversion,
+ NN_NO_CHECK(sphericalCS));
+ }
+
+ throw ParsingException(
+ concat("unhandled CS type: ", cs->getWKT2Type(true)));
+}
+
+// ---------------------------------------------------------------------------
+
+UnitOfMeasure WKTParser::Private::guessUnitForParameter(
+ const std::string &paramName, const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit) {
+ UnitOfMeasure unit;
+ // scale must be first because of 'Scale factor on pseudo standard parallel'
+ if (ci_find(paramName, "scale") != std::string::npos) {
+ unit = UnitOfMeasure::SCALE_UNITY;
+ } else if (ci_find(paramName, "latitude") != std::string::npos ||
+ ci_find(paramName, "longitude") != std::string::npos ||
+ ci_find(paramName, "meridian") != std::string::npos ||
+ ci_find(paramName, "parallel") != std::string::npos ||
+ ci_find(paramName, "azimuth") != std::string::npos ||
+ ci_find(paramName, "angle") != std::string::npos ||
+ ci_find(paramName, "heading") != std::string::npos) {
+ unit = defaultAngularUnit;
+ } else if (ci_find(paramName, "easting") != std::string::npos ||
+ ci_find(paramName, "northing") != std::string::npos ||
+ ci_find(paramName, "height") != std::string::npos) {
+ unit = defaultLinearUnit;
+ }
+ return unit;
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTParser::Private::consumeParameters(
+ const WKTNodeNNPtr &node, bool isAbridged,
+ std::vector<OperationParameterNNPtr> &parameters,
+ std::vector<ParameterValueNNPtr> &values,
+ const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit) {
+ for (const auto &childNode : node->GP()->children()) {
+ const auto &childNodeChildren = childNode->GP()->children();
+ if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
+ if (childNodeChildren.size() < 2) {
+ ThrowNotEnoughChildren(childNode->GP()->value());
+ }
+ parameters.push_back(
+ OperationParameter::create(buildProperties(childNode)));
+ const auto &paramValue = childNodeChildren[1]->GP()->value();
+ if (!paramValue.empty() && paramValue[0] == '"') {
+ values.push_back(
+ ParameterValue::create(stripQuotes(childNodeChildren[1])));
+ } else {
+ try {
+ double val = asDouble(childNodeChildren[1]);
+ auto unit = buildUnitInSubNode(childNode);
+ if (unit == UnitOfMeasure::NONE) {
+ const auto &paramName =
+ childNodeChildren[0]->GP()->value();
+ unit = guessUnitForParameter(
+ paramName, defaultLinearUnit, defaultAngularUnit);
+ }
+
+ if (isAbridged) {
+ const auto &paramName = parameters.back()->nameStr();
+ int paramEPSGCode = 0;
+ const auto &paramIds = parameters.back()->identifiers();
+ if (paramIds.size() == 1 &&
+ ci_equal(*(paramIds[0]->codeSpace()),
+ Identifier::EPSG)) {
+ paramEPSGCode = ::atoi(paramIds[0]->code().c_str());
+ }
+ const common::UnitOfMeasure *pUnit = nullptr;
+ if (OperationParameterValue::convertFromAbridged(
+ paramName, val, pUnit, paramEPSGCode)) {
+ unit = *pUnit;
+ parameters.back() = OperationParameter::create(
+ buildProperties(childNode)
+ .set(Identifier::CODESPACE_KEY,
+ Identifier::EPSG)
+ .set(Identifier::CODE_KEY, paramEPSGCode));
+ }
+ }
+
+ values.push_back(
+ ParameterValue::create(Measure(val, unit)));
+ } catch (const std::exception &) {
+ throw ParsingException(concat(
+ "unhandled parameter value type : ", paramValue));
+ }
+ }
+ } else if (ci_equal(childNode->GP()->value(),
+ WKTConstants::PARAMETERFILE)) {
+ if (childNodeChildren.size() < 2) {
+ ThrowNotEnoughChildren(childNode->GP()->value());
+ }
+ parameters.push_back(
+ OperationParameter::create(buildProperties(childNode)));
+ values.push_back(ParameterValue::createFilename(
+ stripQuotes(childNodeChildren[1])));
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+ConversionNNPtr
+WKTParser::Private::buildConversion(const WKTNodeNNPtr &node,
+ const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit) {
+ auto &methodNode = node->GP()->lookForChild(WKTConstants::METHOD,
+ WKTConstants::PROJECTION);
+ if (isNull(methodNode)) {
+ ThrowMissing(WKTConstants::METHOD);
+ }
+ if (methodNode->GP()->childrenSize() == 0) {
+ ThrowNotEnoughChildren(WKTConstants::METHOD);
+ }
+
+ std::vector<OperationParameterNNPtr> parameters;
+ std::vector<ParameterValueNNPtr> values;
+ consumeParameters(node, false, parameters, values, defaultLinearUnit,
+ defaultAngularUnit);
+
+ return Conversion::create(buildProperties(node),
+ buildProperties(methodNode), parameters, values);
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr
+WKTParser::Private::buildCoordinateOperation(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD);
+ if (isNull(methodNode)) {
+ ThrowMissing(WKTConstants::METHOD);
+ }
+ if (methodNode->GP()->childrenSize() == 0) {
+ ThrowNotEnoughChildren(WKTConstants::METHOD);
+ }
+
+ auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
+ if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) {
+ ThrowMissing(WKTConstants::SOURCECRS);
+ }
+ auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
+ if (!sourceCRS) {
+ throw ParsingException("Invalid content in SOURCECRS node");
+ }
+
+ auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
+ if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) {
+ ThrowMissing(WKTConstants::TARGETCRS);
+ }
+ auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]);
+ if (!targetCRS) {
+ throw ParsingException("Invalid content in TARGETCRS node");
+ }
+
+ auto &interpolationCRSNode =
+ nodeP->lookForChild(WKTConstants::INTERPOLATIONCRS);
+ CRSPtr interpolationCRS;
+ if (/*!isNull(interpolationCRSNode) && */ interpolationCRSNode->GP()
+ ->childrenSize() == 1) {
+ interpolationCRS = buildCRS(interpolationCRSNode->GP()->children()[0]);
+ }
+
+ std::vector<OperationParameterNNPtr> parameters;
+ std::vector<ParameterValueNNPtr> values;
+ auto defaultLinearUnit = UnitOfMeasure::NONE;
+ auto defaultAngularUnit = UnitOfMeasure::NONE;
+ consumeParameters(node, false, parameters, values, defaultLinearUnit,
+ defaultAngularUnit);
+
+ std::vector<PositionalAccuracyNNPtr> accuracies;
+ auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY);
+ if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) {
+ accuracies.push_back(PositionalAccuracy::create(
+ stripQuotes(accuracyNode->GP()->children()[0])));
+ }
+
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ Transformation::create(buildProperties(node), NN_NO_CHECK(sourceCRS),
+ NN_NO_CHECK(targetCRS), interpolationCRS,
+ buildProperties(methodNode), parameters, values,
+ accuracies));
+}
+
+// ---------------------------------------------------------------------------
+
+ConcatenatedOperationNNPtr
+WKTParser::Private::buildConcatenatedOperation(const WKTNodeNNPtr &node) {
+ std::vector<CoordinateOperationNNPtr> operations;
+ for (const auto &childNode : node->GP()->children()) {
+ if (ci_equal(childNode->GP()->value(), WKTConstants::STEP)) {
+ if (childNode->GP()->childrenSize() != 1) {
+ throw ParsingException("Invalid content in STEP node");
+ }
+ auto op = nn_dynamic_pointer_cast<CoordinateOperation>(
+ build(childNode->GP()->children()[0]));
+ if (!op) {
+ throw ParsingException("Invalid content in STEP node");
+ }
+ operations.emplace_back(NN_NO_CHECK(op));
+ }
+ }
+ try {
+ return ConcatenatedOperation::create(
+ buildProperties(node), operations,
+ std::vector<PositionalAccuracyNNPtr>());
+ } catch (const InvalidOperation &e) {
+ throw ParsingException(
+ std::string("Cannot build concatenated operation: ") + e.what());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+bool WKTParser::Private::hasWebMercPROJ4String(
+ const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode) {
+ if (projectionNode->GP()->childrenSize() == 0) {
+ ThrowNotEnoughChildren(WKTConstants::PROJECTION);
+ }
+ const std::string wkt1ProjectionName =
+ stripQuotes(projectionNode->GP()->children()[0]);
+
+ auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION);
+
+ if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
+ "Mercator_1SP") &&
+ projCRSNode->countChildrenOfName("center_latitude") == 0) {
+
+ // Hack to detect the hacky way of encodign webmerc in GDAL WKT1
+ // with a EXTENSION["PROJ4", "+proj=merc +a=6378137 +b=6378137
+ // +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m
+ // +nadgrids=@null +wktext +no_defs"] node
+ if (extensionNode && extensionNode->GP()->childrenSize() == 2 &&
+ ci_equal(stripQuotes(extensionNode->GP()->children()[0]),
+ "PROJ4")) {
+ std::string projString =
+ stripQuotes(extensionNode->GP()->children()[1]);
+ if (projString.find("+proj=merc") != std::string::npos &&
+ projString.find("+a=6378137") != std::string::npos &&
+ projString.find("+b=6378137") != std::string::npos &&
+ projString.find("+lon_0=0") != std::string::npos &&
+ projString.find("+x_0=0") != std::string::npos &&
+ projString.find("+y_0=0") != std::string::npos &&
+ projString.find("+nadgrids=@null") != std::string::npos &&
+ (projString.find("+lat_ts=") == std::string::npos ||
+ projString.find("+lat_ts=0") != std::string::npos) &&
+ (projString.find("+k=") == std::string::npos ||
+ projString.find("+k=1") != std::string::npos) &&
+ (projString.find("+units=") == std::string::npos ||
+ projString.find("+units=m") != std::string::npos)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+ConversionNNPtr WKTParser::Private::buildProjectionFromESRI(
+ const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode,
+ const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit) {
+ const std::string esriProjectionName =
+ stripQuotes(projectionNode->GP()->children()[0]);
+
+ // Lambert_Conformal_Conic or Krovak may map to different WKT2 methods
+ // depending
+ // on the parameters / their values
+ const auto esriMappings = getMappingsFromESRI(esriProjectionName);
+ if (esriMappings.empty()) {
+ return buildProjectionStandard(projCRSNode, projectionNode,
+ defaultLinearUnit, defaultAngularUnit);
+ }
+
+ struct ci_less_struct {
+ bool operator()(const std::string &lhs, const std::string &rhs) const
+ noexcept {
+ return ci_less(lhs, rhs);
+ }
+ };
+
+ // Build a map of present parameters
+ std::map<std::string, std::string, ci_less_struct> mapParamNameToValue;
+ for (const auto &childNode : projCRSNode->GP()->children()) {
+ if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
+ const auto &childNodeChildren = childNode->GP()->children();
+ if (childNodeChildren.size() < 2) {
+ ThrowNotEnoughChildren(WKTConstants::PARAMETER);
+ }
+ const std::string parameterName(stripQuotes(childNodeChildren[0]));
+ const auto &paramValue = childNodeChildren[1]->GP()->value();
+ mapParamNameToValue[parameterName] = paramValue;
+ }
+ }
+
+ // Compare parameters present with the ones expected in the mapping
+ const ESRIMethodMapping *esriMapping = esriMappings[0];
+ int bestMatchCount = -1;
+ for (const auto &mapping : esriMappings) {
+ int matchCount = 0;
+ for (const auto *param = mapping->params; param->esri_name; ++param) {
+ auto iter = mapParamNameToValue.find(param->esri_name);
+ if (iter != mapParamNameToValue.end()) {
+ if (param->wkt2_name == nullptr) {
+ try {
+ if (param->fixed_value == io::asDouble(iter->second)) {
+ matchCount++;
+ }
+ } catch (const std::exception &) {
+ }
+ } else {
+ matchCount++;
+ }
+ }
+ }
+ if (matchCount > bestMatchCount) {
+ esriMapping = mapping;
+ bestMatchCount = matchCount;
+ }
+ }
+
+ std::map<std::string, const char *> mapWKT2NameToESRIName;
+ for (const auto *param = esriMapping->params; param->esri_name; ++param) {
+ if (param->wkt2_name) {
+ mapWKT2NameToESRIName[param->wkt2_name] = param->esri_name;
+ }
+ }
+
+ const auto *wkt2_mapping = getMapping(esriMapping->wkt2_name);
+ assert(wkt2_mapping);
+ if (ci_equal(esriProjectionName, "Stereographic")) {
+ try {
+ if (std::fabs(io::asDouble(
+ mapParamNameToValue["Latitude_Of_Origin"])) == 90.0) {
+ wkt2_mapping =
+ getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A);
+ }
+ } catch (const std::exception &) {
+ }
+ }
+
+ PropertyMap propertiesMethod;
+ propertiesMethod.set(IdentifiedObject::NAME_KEY, wkt2_mapping->wkt2_name);
+ if (wkt2_mapping->epsg_code != 0) {
+ propertiesMethod.set(Identifier::CODE_KEY, wkt2_mapping->epsg_code);
+ propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
+ }
+
+ std::vector<OperationParameterNNPtr> parameters;
+ std::vector<ParameterValueNNPtr> values;
+
+ if (wkt2_mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL &&
+ ci_equal(esriProjectionName, "Plate_Carree")) {
+ // Add a fixed Latitude of 1st parallel = 0 so as to have all
+ // parameters expected by Equidistant Cylindrical.
+ mapWKT2NameToESRIName[EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL] =
+ "Standard_Parallel_1";
+ mapParamNameToValue["Standard_Parallel_1"] = "0";
+ } else if ((wkt2_mapping->epsg_code ==
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A ||
+ wkt2_mapping->epsg_code ==
+ EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) &&
+ !ci_equal(esriProjectionName,
+ "Rectified_Skew_Orthomorphic_Natural_Origin") &&
+ !ci_equal(esriProjectionName,
+ "Rectified_Skew_Orthomorphic_Center")) {
+ // ESRI WKT lacks the angle to skew grid
+ // Take it from the azimuth value
+ mapWKT2NameToESRIName
+ [EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID] = "Azimuth";
+ }
+
+ for (int i = 0; wkt2_mapping->params[i] != nullptr; i++) {
+ const auto *paramMapping = wkt2_mapping->params[i];
+
+ auto iter = mapWKT2NameToESRIName.find(paramMapping->wkt2_name);
+ if (iter == mapWKT2NameToESRIName.end()) {
+ continue;
+ }
+ const auto &esriParamName = iter->second;
+ auto iter2 = mapParamNameToValue.find(esriParamName);
+ auto mapParamNameToValueEnd = mapParamNameToValue.end();
+ if (iter2 == mapParamNameToValueEnd) {
+ // In case we don't find a direct match, try the aliases
+ for (iter2 = mapParamNameToValue.begin();
+ iter2 != mapParamNameToValueEnd; ++iter2) {
+ if (areEquivalentParameters(iter2->first, esriParamName)) {
+ break;
+ }
+ }
+ if (iter2 == mapParamNameToValueEnd) {
+ continue;
+ }
+ }
+
+ PropertyMap propertiesParameter;
+ propertiesParameter.set(IdentifiedObject::NAME_KEY,
+ paramMapping->wkt2_name);
+ if (paramMapping->epsg_code != 0) {
+ propertiesParameter.set(Identifier::CODE_KEY,
+ paramMapping->epsg_code);
+ propertiesParameter.set(Identifier::CODESPACE_KEY,
+ Identifier::EPSG);
+ }
+ parameters.push_back(OperationParameter::create(propertiesParameter));
+
+ try {
+ double val = io::asDouble(iter2->second);
+ auto unit = guessUnitForParameter(
+ paramMapping->wkt2_name, defaultLinearUnit, defaultAngularUnit);
+ values.push_back(ParameterValue::create(Measure(val, unit)));
+ } catch (const std::exception &) {
+ throw ParsingException(
+ concat("unhandled parameter value type : ", iter2->second));
+ }
+ }
+
+ return Conversion::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
+ propertiesMethod, parameters, values)
+ ->identify();
+}
+
+// ---------------------------------------------------------------------------
+
+ConversionNNPtr
+WKTParser::Private::buildProjection(const WKTNodeNNPtr &projCRSNode,
+ const WKTNodeNNPtr &projectionNode,
+ const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit) {
+ if (projectionNode->GP()->childrenSize() == 0) {
+ ThrowNotEnoughChildren(WKTConstants::PROJECTION);
+ }
+ if (esriStyle_) {
+ return buildProjectionFromESRI(projCRSNode, projectionNode,
+ defaultLinearUnit, defaultAngularUnit);
+ }
+ return buildProjectionStandard(projCRSNode, projectionNode,
+ defaultLinearUnit, defaultAngularUnit);
+}
+
+// ---------------------------------------------------------------------------
+
+std::string
+WKTParser::Private::projectionGetParameter(const WKTNodeNNPtr &projCRSNode,
+ const char *paramName) {
+ for (const auto &childNode : projCRSNode->GP()->children()) {
+ if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
+ const auto &childNodeChildren = childNode->GP()->children();
+ if (childNodeChildren.size() == 2 &&
+ metadata::Identifier::isEquivalentName(
+ stripQuotes(childNodeChildren[0]).c_str(), paramName)) {
+ return childNodeChildren[1]->GP()->value();
+ }
+ }
+ }
+ return std::string();
+}
+
+// ---------------------------------------------------------------------------
+
+ConversionNNPtr WKTParser::Private::buildProjectionStandard(
+ const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode,
+ const UnitOfMeasure &defaultLinearUnit,
+ const UnitOfMeasure &defaultAngularUnit) {
+ std::string wkt1ProjectionName =
+ stripQuotes(projectionNode->GP()->children()[0]);
+
+ std::vector<OperationParameterNNPtr> parameters;
+ std::vector<ParameterValueNNPtr> values;
+ bool tryToIdentifyWKT1Method = true;
+
+ auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION);
+ const auto &extensionChildren = extensionNode->GP()->children();
+
+ bool gdal_3026_hack = false;
+ if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
+ "Mercator_1SP") &&
+ projectionGetParameter(projCRSNode, "center_latitude").empty()) {
+
+ // Hack for https://trac.osgeo.org/gdal/ticket/3026
+ std::string lat0(
+ projectionGetParameter(projCRSNode, "latitude_of_origin"));
+ if (!lat0.empty() && lat0 != "0" && lat0 != "0.0") {
+ wkt1ProjectionName = "Mercator_2SP";
+ gdal_3026_hack = true;
+ } else {
+ // The latitude of origin, which should always be zero, is
+ // missing
+ // in GDAL WKT1, but provisionned in the EPSG Mercator_1SP
+ // definition,
+ // so add it manually.
+ PropertyMap propertiesParameter;
+ propertiesParameter.set(IdentifiedObject::NAME_KEY,
+ "Latitude of natural origin");
+ propertiesParameter.set(Identifier::CODE_KEY, 8801);
+ propertiesParameter.set(Identifier::CODESPACE_KEY,
+ Identifier::EPSG);
+ parameters.push_back(
+ OperationParameter::create(propertiesParameter));
+ values.push_back(
+ ParameterValue::create(Measure(0, UnitOfMeasure::DEGREE)));
+ }
+
+ } else if (metadata::Identifier::isEquivalentName(
+ wkt1ProjectionName.c_str(), "Polar_Stereographic")) {
+ std::map<std::string, Measure> mapParameters;
+ for (const auto &childNode : projCRSNode->GP()->children()) {
+ const auto &childNodeChildren = childNode->GP()->children();
+ if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) &&
+ childNodeChildren.size() == 2) {
+ const std::string wkt1ParameterName(
+ stripQuotes(childNodeChildren[0]));
+ try {
+ double val = asDouble(childNodeChildren[1]);
+ auto unit = guessUnitForParameter(wkt1ParameterName,
+ defaultLinearUnit,
+ defaultAngularUnit);
+ mapParameters.insert(std::pair<std::string, Measure>(
+ tolower(wkt1ParameterName), Measure(val, unit)));
+ } catch (const std::exception &) {
+ }
+ }
+ }
+
+ Measure latitudeOfOrigin = mapParameters["latitude_of_origin"];
+ Measure centralMeridian = mapParameters["central_meridian"];
+ Measure scaleFactorFromMap = mapParameters["scale_factor"];
+ Measure scaleFactor((scaleFactorFromMap.unit() == UnitOfMeasure::NONE)
+ ? Measure(1.0, UnitOfMeasure::SCALE_UNITY)
+ : scaleFactorFromMap);
+ Measure falseEasting = mapParameters["false_easting"];
+ Measure falseNorthing = mapParameters["false_northing"];
+ if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
+ scaleFactor.getSIValue() == 1.0) {
+ return Conversion::createPolarStereographicVariantB(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
+ Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()),
+ Angle(centralMeridian.value(), centralMeridian.unit()),
+ Length(falseEasting.value(), falseEasting.unit()),
+ Length(falseNorthing.value(), falseNorthing.unit()));
+ }
+
+ if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
+ std::fabs(std::fabs(latitudeOfOrigin.convertToUnit(
+ UnitOfMeasure::DEGREE)) -
+ 90.0) < 1e-10) {
+ return Conversion::createPolarStereographicVariantA(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
+ Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()),
+ Angle(centralMeridian.value(), centralMeridian.unit()),
+ Scale(scaleFactor.value(), scaleFactor.unit()),
+ Length(falseEasting.value(), falseEasting.unit()),
+ Length(falseNorthing.value(), falseNorthing.unit()));
+ }
+
+ tryToIdentifyWKT1Method = false;
+ // Import GDAL PROJ4 extension nodes
+ } else if (extensionChildren.size() == 2 &&
+ ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
+ std::string projString = stripQuotes(extensionChildren[1]);
+ if (starts_with(projString, "+proj=")) {
+ try {
+ auto projObj =
+ PROJStringParser().createFromPROJString(projString);
+ auto projObjCrs =
+ nn_dynamic_pointer_cast<ProjectedCRS>(projObj);
+ if (projObjCrs) {
+ return projObjCrs->derivingConversion();
+ }
+ } catch (const io::ParsingException &) {
+ }
+ }
+ }
+
+ std::string projectionName(wkt1ProjectionName);
+ const MethodMapping *mapping =
+ tryToIdentifyWKT1Method ? getMappingFromWKT1(projectionName) : nullptr;
+
+ // For Krovak, we need to look at axis to decide between the Krovak and
+ // Krovak East-North Oriented methods
+ if (ci_equal(projectionName, "Krovak") &&
+ projCRSNode->countChildrenOfName(WKTConstants::AXIS) == 2 &&
+ &buildAxis(
+ projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 0),
+ defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false,
+ 1)->direction() == &AxisDirection::SOUTH &&
+ &buildAxis(
+ projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 1),
+ defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false,
+ 2)->direction() == &AxisDirection::WEST) {
+ mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
+ }
+
+ PropertyMap propertiesMethod;
+ if (mapping) {
+ projectionName = mapping->wkt2_name;
+ if (mapping->epsg_code != 0) {
+ propertiesMethod.set(Identifier::CODE_KEY, mapping->epsg_code);
+ propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
+ }
+ }
+ propertiesMethod.set(IdentifiedObject::NAME_KEY, projectionName);
+
+ for (const auto &childNode : projCRSNode->GP()->children()) {
+ if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
+ const auto &childNodeChildren = childNode->GP()->children();
+ if (childNodeChildren.size() < 2) {
+ ThrowNotEnoughChildren(WKTConstants::PARAMETER);
+ }
+ const auto &paramValue = childNodeChildren[1]->GP()->value();
+
+ PropertyMap propertiesParameter;
+ const std::string wkt1ParameterName(
+ stripQuotes(childNodeChildren[0]));
+ std::string parameterName(wkt1ParameterName);
+ if (gdal_3026_hack) {
+ if (ci_equal(parameterName, "latitude_of_origin")) {
+ parameterName = "standard_parallel_1";
+ } else if (ci_equal(parameterName, "scale_factor") &&
+ paramValue == "1") {
+ continue;
+ }
+ }
+ const auto *paramMapping =
+ mapping ? getMappingFromWKT1(mapping, parameterName) : nullptr;
+ if (mapping &&
+ mapping->epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
+ ci_equal(parameterName, "latitude_of_origin")) {
+ parameterName = EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN;
+ propertiesParameter.set(
+ Identifier::CODE_KEY,
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
+ propertiesParameter.set(Identifier::CODESPACE_KEY,
+ Identifier::EPSG);
+ } else if (paramMapping) {
+ parameterName = paramMapping->wkt2_name;
+ if (paramMapping->epsg_code != 0) {
+ propertiesParameter.set(Identifier::CODE_KEY,
+ paramMapping->epsg_code);
+ propertiesParameter.set(Identifier::CODESPACE_KEY,
+ Identifier::EPSG);
+ }
+ }
+ propertiesParameter.set(IdentifiedObject::NAME_KEY, parameterName);
+ parameters.push_back(
+ OperationParameter::create(propertiesParameter));
+ try {
+ double val = io::asDouble(paramValue);
+ auto unit = guessUnitForParameter(
+ wkt1ParameterName, defaultLinearUnit, defaultAngularUnit);
+ values.push_back(ParameterValue::create(Measure(val, unit)));
+ } catch (const std::exception &) {
+ throw ParsingException(
+ concat("unhandled parameter value type : ", paramValue));
+ }
+ }
+ }
+
+ return Conversion::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
+ propertiesMethod, parameters, values)
+ ->identify();
+}
+
+// ---------------------------------------------------------------------------
+
+static ProjectedCRSNNPtr createPseudoMercator(const PropertyMap &props) {
+ auto conversion = Conversion::createPopularVisualisationPseudoMercator(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(0),
+ Angle(0), Length(0), Length(0));
+ return ProjectedCRS::create(
+ props, GeographicCRS::EPSG_4326, conversion,
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+ProjectedCRSNNPtr
+WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) {
+
+ const auto *nodeP = node->GP();
+ auto &conversionNode = nodeP->lookForChild(WKTConstants::CONVERSION);
+ auto &projectionNode = nodeP->lookForChild(WKTConstants::PROJECTION);
+ if (isNull(conversionNode) && isNull(projectionNode)) {
+ ThrowMissing(WKTConstants::CONVERSION);
+ }
+
+ auto &baseGeodCRSNode =
+ nodeP->lookForChild(WKTConstants::BASEGEODCRS,
+ WKTConstants::BASEGEOGCRS, WKTConstants::GEOGCS);
+ if (isNull(baseGeodCRSNode)) {
+ throw ParsingException(
+ "Missing BASEGEODCRS / BASEGEOGCRS / GEOGCS node");
+ }
+ auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode);
+
+ auto props = buildProperties(node);
+
+ const std::string projCRSName = stripQuotes(nodeP->children()[0]);
+ if (esriStyle_ && dbContext_) {
+ // It is likely that the ESRI definition of EPSG:32661 (UPS North) &
+ // EPSG:32761 (UPS South) uses the easting-northing order, instead
+ // of the EPSG northing-easting order
+ // so don't substitue names to avoid confusion.
+ if (projCRSName == "UPS_North") {
+ props.set(IdentifiedObject::NAME_KEY, "WGS 84 / UPS North (E,N)");
+ } else if (projCRSName == "UPS_South") {
+ props.set(IdentifiedObject::NAME_KEY, "WGS 84 / UPS South (E,N)");
+ } else {
+ std::string outTableName;
+ std::string authNameFromAlias;
+ std::string codeFromAlias;
+ auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
+ std::string());
+ auto officialName = authFactory->getOfficialNameFromAlias(
+ projCRSName, "projected_crs", "ESRI", false, outTableName,
+ authNameFromAlias, codeFromAlias);
+ if (!officialName.empty()) {
+ props.set(IdentifiedObject::NAME_KEY, officialName);
+ }
+ }
+ }
+
+ if (isNull(conversionNode) && hasWebMercPROJ4String(node, projectionNode)) {
+ toWGS84Parameters_.clear();
+ return createPseudoMercator(props);
+ }
+
+ // WGS_84_Pseudo_Mercator: Particular case for corrupted ESRI WKT generated
+ // by older GDAL versions
+ // https://trac.osgeo.org/gdal/changeset/30732
+ // WGS_1984_Web_Mercator: deprecated ESRI:102113
+ if (metadata::Identifier::isEquivalentName(projCRSName.c_str(),
+ "WGS_84_Pseudo_Mercator") ||
+ metadata::Identifier::isEquivalentName(projCRSName.c_str(),
+ "WGS_1984_Web_Mercator")) {
+ toWGS84Parameters_.clear();
+ return createPseudoMercator(props);
+ }
+
+ auto linearUnit = buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR);
+ auto angularUnit = baseGeodCRS->coordinateSystem()->axisList()[0]->unit();
+
+ auto conversion =
+ !isNull(conversionNode)
+ ? buildConversion(conversionNode, linearUnit, angularUnit)
+ : buildProjection(node, projectionNode, linearUnit, angularUnit);
+
+ auto &csNode = nodeP->lookForChild(WKTConstants::CS);
+ const auto &nodeValue = nodeP->value();
+ if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::PROJCS) &&
+ !ci_equal(nodeValue, WKTConstants::BASEPROJCRS)) {
+ ThrowMissing(WKTConstants::CS);
+ }
+ auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
+ auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
+
+ // No explicit AXIS node ? (WKT1)
+ if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) {
+ props.set("IMPLICIT_CS", true);
+ }
+
+ if (isNull(csNode) && node->countChildrenOfName(WKTConstants::AXIS) == 0) {
+
+ const auto methodCode = conversion->method()->getEPSGCode();
+ // Krovak south oriented ?
+ if (methodCode == EPSG_CODE_METHOD_KROVAK) {
+ cartesianCS =
+ CartesianCS::create(
+ PropertyMap(),
+ CoordinateSystemAxis::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY,
+ AxisName::Southing),
+ emptyString, AxisDirection::SOUTH, linearUnit),
+ CoordinateSystemAxis::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY,
+ AxisName::Westing),
+ emptyString, AxisDirection::WEST, linearUnit))
+ .as_nullable();
+ } else if (methodCode ==
+ EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A ||
+ methodCode ==
+ EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) {
+ // It is likely that the ESRI definition of EPSG:32661 (UPS North) &
+ // EPSG:32761 (UPS South) uses the easting-northing order, instead
+ // of the EPSG northing-easting order.
+ // Same for WKT1_GDAL
+ const double lat0 = conversion->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ common::UnitOfMeasure::DEGREE);
+ if (std::fabs(lat0 - 90) < 1e-10) {
+ cartesianCS =
+ CartesianCS::createNorthPoleEastingSouthNorthingSouth(
+ linearUnit)
+ .as_nullable();
+ } else if (std::fabs(lat0 - -90) < 1e-10) {
+ cartesianCS =
+ CartesianCS::createSouthPoleEastingNorthNorthingNorth(
+ linearUnit)
+ .as_nullable();
+ }
+ } else if (methodCode ==
+ EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
+ const double lat_ts = conversion->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL,
+ common::UnitOfMeasure::DEGREE);
+ if (lat_ts > 0) {
+ cartesianCS =
+ CartesianCS::createNorthPoleEastingSouthNorthingSouth(
+ linearUnit)
+ .as_nullable();
+ } else if (lat_ts < 0) {
+ cartesianCS =
+ CartesianCS::createSouthPoleEastingNorthNorthingNorth(
+ linearUnit)
+ .as_nullable();
+ }
+ } else if (methodCode ==
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
+ cartesianCS =
+ CartesianCS::createWestingSouthing(linearUnit).as_nullable();
+ }
+ }
+ if (!cartesianCS) {
+ ThrowNotExpectedCSType("Cartesian");
+ }
+
+ addExtensionProj4ToProp(nodeP, props);
+
+ return ProjectedCRS::create(props, baseGeodCRS, conversion,
+ NN_NO_CHECK(cartesianCS));
+}
+
+// ---------------------------------------------------------------------------
+
+void WKTParser::Private::parseDynamic(const WKTNodeNNPtr &dynamicNode,
+ double &frameReferenceEpoch,
+ util::optional<std::string> &modelName) {
+ auto &frameEpochNode = dynamicNode->lookForChild(WKTConstants::FRAMEEPOCH);
+ const auto &frameEpochChildren = frameEpochNode->GP()->children();
+ if (frameEpochChildren.empty()) {
+ ThrowMissing(WKTConstants::FRAMEEPOCH);
+ }
+ try {
+ frameReferenceEpoch = asDouble(frameEpochChildren[0]);
+ } catch (const std::exception &) {
+ throw ParsingException("Invalid FRAMEEPOCH node");
+ }
+ auto &modelNode = dynamicNode->GP()->lookForChild(
+ WKTConstants::MODEL, WKTConstants::VELOCITYGRID);
+ const auto &modelChildren = modelNode->GP()->children();
+ if (modelChildren.size() == 1) {
+ modelName = stripQuotes(modelChildren[0]);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+VerticalReferenceFrameNNPtr WKTParser::Private::buildVerticalReferenceFrame(
+ const WKTNodeNNPtr &node, const WKTNodeNNPtr &dynamicNode) {
+
+ if (!isNull(dynamicNode)) {
+ double frameReferenceEpoch = 0.0;
+ util::optional<std::string> modelName;
+ parseDynamic(dynamicNode, frameReferenceEpoch, modelName);
+ return DynamicVerticalReferenceFrame::create(
+ buildProperties(node), getAnchor(node),
+ optional<RealizationMethod>(),
+ common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR),
+ modelName);
+ }
+
+ // WKT1 VERT_DATUM has a datum type after the datum name that we ignore.
+ return VerticalReferenceFrame::create(buildProperties(node),
+ getAnchor(node));
+}
+
+// ---------------------------------------------------------------------------
+
+TemporalDatumNNPtr
+WKTParser::Private::buildTemporalDatum(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ auto &calendarNode = nodeP->lookForChild(WKTConstants::CALENDAR);
+ std::string calendar = TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN;
+ const auto &calendarChildren = calendarNode->GP()->children();
+ if (calendarChildren.size() == 1) {
+ calendar = stripQuotes(calendarChildren[0]);
+ }
+
+ auto &timeOriginNode = nodeP->lookForChild(WKTConstants::TIMEORIGIN);
+ std::string originStr;
+ const auto &timeOriginNodeChildren = timeOriginNode->GP()->children();
+ if (timeOriginNodeChildren.size() == 1) {
+ originStr = stripQuotes(timeOriginNodeChildren[0]);
+ }
+ auto origin = DateTime::create(originStr);
+ return TemporalDatum::create(buildProperties(node), origin, calendar);
+}
+
+// ---------------------------------------------------------------------------
+
+EngineeringDatumNNPtr
+WKTParser::Private::buildEngineeringDatum(const WKTNodeNNPtr &node) {
+ return EngineeringDatum::create(buildProperties(node), getAnchor(node));
+}
+
+// ---------------------------------------------------------------------------
+
+ParametricDatumNNPtr
+WKTParser::Private::buildParametricDatum(const WKTNodeNNPtr &node) {
+ return ParametricDatum::create(buildProperties(node), getAnchor(node));
+}
+
+// ---------------------------------------------------------------------------
+
+CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ auto &datumNode =
+ nodeP->lookForChild(WKTConstants::VDATUM, WKTConstants::VERT_DATUM,
+ WKTConstants::VERTICALDATUM, WKTConstants::VRF);
+ auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE);
+ if (isNull(datumNode) && isNull(ensembleNode)) {
+ throw ParsingException("Missing VDATUM or ENSEMBLE node");
+ }
+
+ auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC);
+ auto datum =
+ !isNull(datumNode)
+ ? buildVerticalReferenceFrame(datumNode, dynamicNode).as_nullable()
+ : nullptr;
+ auto datumEnsemble =
+ !isNull(ensembleNode)
+ ? buildDatumEnsemble(ensembleNode, nullptr, false).as_nullable()
+ : nullptr;
+
+ auto &csNode = nodeP->lookForChild(WKTConstants::CS);
+ const auto &nodeValue = nodeP->value();
+ if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::VERT_CS) &&
+ !ci_equal(nodeValue, WKTConstants::BASEVERTCRS)) {
+ ThrowMissing(WKTConstants::CS);
+ }
+ auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
+ auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(cs);
+ if (!verticalCS) {
+ ThrowNotExpectedCSType("vertical");
+ }
+
+ auto crs = nn_static_pointer_cast<CRS>(VerticalCRS::create(
+ buildProperties(node), datum, datumEnsemble, NN_NO_CHECK(verticalCS)));
+
+ if (!isNull(datumNode)) {
+ auto &extensionNode = datumNode->lookForChild(WKTConstants::EXTENSION);
+ const auto &extensionChildren = extensionNode->GP()->children();
+ if (extensionChildren.size() == 2) {
+ if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) {
+ std::string transformationName(crs->nameStr());
+ if (!ends_with(transformationName, " height")) {
+ transformationName += " height";
+ }
+ transformationName += " to WGS84 ellipsoidal height";
+ auto transformation =
+ Transformation::createGravityRelatedHeightToGeographic3D(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ transformationName),
+ crs, GeographicCRS::EPSG_4979,
+ stripQuotes(extensionChildren[1]),
+ std::vector<PositionalAccuracyNNPtr>());
+ return nn_static_pointer_cast<CRS>(BoundCRS::create(
+ crs, GeographicCRS::EPSG_4979, transformation));
+ }
+ }
+ }
+
+ return crs;
+}
+
+// ---------------------------------------------------------------------------
+
+DerivedVerticalCRSNNPtr
+WKTParser::Private::buildDerivedVerticalCRS(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ auto &baseVertCRSNode = nodeP->lookForChild(WKTConstants::BASEVERTCRS);
+ // given the constraints enforced on calling code path
+ assert(!isNull(baseVertCRSNode));
+
+ auto baseVertCRS_tmp = buildVerticalCRS(baseVertCRSNode);
+ auto baseVertCRS = NN_NO_CHECK(baseVertCRS_tmp->extractVerticalCRS());
+
+ auto &derivingConversionNode =
+ nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
+ if (isNull(derivingConversionNode)) {
+ ThrowMissing(WKTConstants::DERIVINGCONVERSION);
+ }
+ auto derivingConversion = buildConversion(
+ derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
+
+ auto &csNode = nodeP->lookForChild(WKTConstants::CS);
+ if (isNull(csNode)) {
+ ThrowMissing(WKTConstants::CS);
+ }
+ auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
+
+ auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(cs);
+ if (!verticalCS) {
+ throw ParsingException(
+ concat("vertical CS expected, but found ", cs->getWKT2Type(true)));
+ }
+
+ return DerivedVerticalCRS::create(buildProperties(node), baseVertCRS,
+ derivingConversion,
+ NN_NO_CHECK(verticalCS));
+}
+
+// ---------------------------------------------------------------------------
+
+CompoundCRSNNPtr
+WKTParser::Private::buildCompoundCRS(const WKTNodeNNPtr &node) {
+ std::vector<CRSNNPtr> components;
+ for (const auto &child : node->GP()->children()) {
+ auto crs = buildCRS(child);
+ if (crs) {
+ components.push_back(NN_NO_CHECK(crs));
+ }
+ }
+ return CompoundCRS::create(buildProperties(node), components);
+}
+
+// ---------------------------------------------------------------------------
+
+BoundCRSNNPtr WKTParser::Private::buildBoundCRS(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ auto &abridgedNode =
+ nodeP->lookForChild(WKTConstants::ABRIDGEDTRANSFORMATION);
+ if (isNull(abridgedNode)) {
+ ThrowNotEnoughChildren(WKTConstants::ABRIDGEDTRANSFORMATION);
+ }
+
+ auto &methodNode = abridgedNode->GP()->lookForChild(WKTConstants::METHOD);
+ if (isNull(methodNode)) {
+ ThrowMissing(WKTConstants::METHOD);
+ }
+ if (methodNode->GP()->childrenSize() == 0) {
+ ThrowNotEnoughChildren(WKTConstants::METHOD);
+ }
+
+ auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
+ const auto &sourceCRSNodeChildren = sourceCRSNode->GP()->children();
+ if (sourceCRSNodeChildren.size() != 1) {
+ ThrowNotEnoughChildren(WKTConstants::SOURCECRS);
+ }
+ auto sourceCRS = buildCRS(sourceCRSNodeChildren[0]);
+ if (!sourceCRS) {
+ throw ParsingException("Invalid content in SOURCECRS node");
+ }
+
+ auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
+ const auto &targetCRSNodeChildren = targetCRSNode->GP()->children();
+ if (targetCRSNodeChildren.size() != 1) {
+ ThrowNotEnoughChildren(WKTConstants::TARGETCRS);
+ }
+ auto targetCRS = buildCRS(targetCRSNodeChildren[0]);
+ if (!targetCRS) {
+ throw ParsingException("Invalid content in TARGETCRS node");
+ }
+
+ std::vector<OperationParameterNNPtr> parameters;
+ std::vector<ParameterValueNNPtr> values;
+ auto defaultLinearUnit = UnitOfMeasure::NONE;
+ auto defaultAngularUnit = UnitOfMeasure::NONE;
+ consumeParameters(abridgedNode, true, parameters, values, defaultLinearUnit,
+ defaultAngularUnit);
+
+ CRSPtr sourceTransformationCRS;
+ if (dynamic_cast<GeographicCRS *>(targetCRS.get())) {
+ sourceTransformationCRS = sourceCRS->extractGeographicCRS();
+ if (!sourceTransformationCRS) {
+ throw ParsingException("Cannot find GeographicCRS in sourceCRS");
+ }
+ } else {
+ sourceTransformationCRS = sourceCRS;
+ }
+
+ auto transformation = Transformation::create(
+ buildProperties(abridgedNode), NN_NO_CHECK(sourceTransformationCRS),
+ NN_NO_CHECK(targetCRS), nullptr, buildProperties(methodNode),
+ parameters, values, std::vector<PositionalAccuracyNNPtr>());
+
+ return BoundCRS::create(NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS),
+ transformation);
+}
+
+// ---------------------------------------------------------------------------
+
+TemporalCSNNPtr
+WKTParser::Private::buildTemporalCS(const WKTNodeNNPtr &parentNode) {
+
+ auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS);
+ if (isNull(csNode) &&
+ !ci_equal(parentNode->GP()->value(), WKTConstants::BASETIMECRS)) {
+ ThrowMissing(WKTConstants::CS);
+ }
+ auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE);
+ auto temporalCS = nn_dynamic_pointer_cast<TemporalCS>(cs);
+ if (!temporalCS) {
+ ThrowNotExpectedCSType("temporal");
+ }
+ return NN_NO_CHECK(temporalCS);
+}
+
+// ---------------------------------------------------------------------------
+
+TemporalCRSNNPtr
+WKTParser::Private::buildTemporalCRS(const WKTNodeNNPtr &node) {
+ auto &datumNode =
+ node->GP()->lookForChild(WKTConstants::TDATUM, WKTConstants::TIMEDATUM);
+ if (isNull(datumNode)) {
+ throw ParsingException("Missing TDATUM / TIMEDATUM node");
+ }
+
+ return TemporalCRS::create(buildProperties(node),
+ buildTemporalDatum(datumNode),
+ buildTemporalCS(node));
+}
+
+// ---------------------------------------------------------------------------
+
+DerivedTemporalCRSNNPtr
+WKTParser::Private::buildDerivedTemporalCRS(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ auto &baseCRSNode = nodeP->lookForChild(WKTConstants::BASETIMECRS);
+ // given the constraints enforced on calling code path
+ assert(!isNull(baseCRSNode));
+
+ auto &derivingConversionNode =
+ nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
+ if (isNull(derivingConversionNode)) {
+ ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
+ }
+
+ return DerivedTemporalCRS::create(
+ buildProperties(node), buildTemporalCRS(baseCRSNode),
+ buildConversion(derivingConversionNode, UnitOfMeasure::NONE,
+ UnitOfMeasure::NONE),
+ buildTemporalCS(node));
+}
+
+// ---------------------------------------------------------------------------
+
+EngineeringCRSNNPtr
+WKTParser::Private::buildEngineeringCRS(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ auto &datumNode = nodeP->lookForChild(WKTConstants::EDATUM,
+ WKTConstants::ENGINEERINGDATUM);
+ if (isNull(datumNode)) {
+ throw ParsingException("Missing EDATUM / ENGINEERINGDATUM node");
+ }
+
+ auto &csNode = nodeP->lookForChild(WKTConstants::CS);
+ if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::BASEENGCRS)) {
+ ThrowMissing(WKTConstants::CS);
+ }
+
+ auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
+ return EngineeringCRS::create(buildProperties(node),
+ buildEngineeringDatum(datumNode), cs);
+}
+
+// ---------------------------------------------------------------------------
+
+EngineeringCRSNNPtr
+WKTParser::Private::buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node) {
+ auto &datumNode = node->GP()->lookForChild(WKTConstants::LOCAL_DATUM);
+ auto cs = buildCS(null_node, node, UnitOfMeasure::NONE);
+ auto datum = EngineeringDatum::create(
+ !isNull(datumNode)
+ ? buildProperties(datumNode)
+ :
+ // In theory OGC 01-009 mandates LOCAL_DATUM, but GDAL has a
+ // tradition of emitting just LOCAL_CS["foo"]
+ emptyPropertyMap);
+ return EngineeringCRS::create(buildProperties(node), datum, cs);
+}
+
+// ---------------------------------------------------------------------------
+
+DerivedEngineeringCRSNNPtr
+WKTParser::Private::buildDerivedEngineeringCRS(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ auto &baseEngCRSNode = nodeP->lookForChild(WKTConstants::BASEENGCRS);
+ // given the constraints enforced on calling code path
+ assert(!isNull(baseEngCRSNode));
+
+ auto baseEngCRS = buildEngineeringCRS(baseEngCRSNode);
+
+ auto &derivingConversionNode =
+ nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
+ if (isNull(derivingConversionNode)) {
+ ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
+ }
+ auto derivingConversion = buildConversion(
+ derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
+
+ auto &csNode = nodeP->lookForChild(WKTConstants::CS);
+ if (isNull(csNode)) {
+ ThrowMissing(WKTConstants::CS);
+ }
+ auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
+
+ return DerivedEngineeringCRS::create(buildProperties(node), baseEngCRS,
+ derivingConversion, cs);
+}
+
+// ---------------------------------------------------------------------------
+
+ParametricCSNNPtr
+WKTParser::Private::buildParametricCS(const WKTNodeNNPtr &parentNode) {
+
+ auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS);
+ if (isNull(csNode) &&
+ !ci_equal(parentNode->GP()->value(), WKTConstants::BASEPARAMCRS)) {
+ ThrowMissing(WKTConstants::CS);
+ }
+ auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE);
+ auto parametricCS = nn_dynamic_pointer_cast<ParametricCS>(cs);
+ if (!parametricCS) {
+ ThrowNotExpectedCSType("parametric");
+ }
+ return NN_NO_CHECK(parametricCS);
+}
+
+// ---------------------------------------------------------------------------
+
+ParametricCRSNNPtr
+WKTParser::Private::buildParametricCRS(const WKTNodeNNPtr &node) {
+ auto &datumNode = node->GP()->lookForChild(WKTConstants::PDATUM,
+ WKTConstants::PARAMETRICDATUM);
+ if (isNull(datumNode)) {
+ throw ParsingException("Missing PDATUM / PARAMETRICDATUM node");
+ }
+
+ return ParametricCRS::create(buildProperties(node),
+ buildParametricDatum(datumNode),
+ buildParametricCS(node));
+}
+
+// ---------------------------------------------------------------------------
+
+DerivedParametricCRSNNPtr
+WKTParser::Private::buildDerivedParametricCRS(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ auto &baseParamCRSNode = nodeP->lookForChild(WKTConstants::BASEPARAMCRS);
+ // given the constraints enforced on calling code path
+ assert(!isNull(baseParamCRSNode));
+
+ auto &derivingConversionNode =
+ nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
+ if (isNull(derivingConversionNode)) {
+ ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
+ }
+
+ return DerivedParametricCRS::create(
+ buildProperties(node), buildParametricCRS(baseParamCRSNode),
+ buildConversion(derivingConversionNode, UnitOfMeasure::NONE,
+ UnitOfMeasure::NONE),
+ buildParametricCS(node));
+}
+
+// ---------------------------------------------------------------------------
+
+DerivedProjectedCRSNNPtr
+WKTParser::Private::buildDerivedProjectedCRS(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ auto &baseProjCRSNode = nodeP->lookForChild(WKTConstants::BASEPROJCRS);
+ if (isNull(baseProjCRSNode)) {
+ ThrowNotEnoughChildren(WKTConstants::BASEPROJCRS);
+ }
+ auto baseProjCRS = buildProjectedCRS(baseProjCRSNode);
+
+ auto &conversionNode =
+ nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
+ if (isNull(conversionNode)) {
+ ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
+ }
+
+ auto linearUnit = buildUnitInSubNode(node);
+ auto angularUnit =
+ baseProjCRS->baseCRS()->coordinateSystem()->axisList()[0]->unit();
+
+ auto conversion = buildConversion(conversionNode, linearUnit, angularUnit);
+
+ auto &csNode = nodeP->lookForChild(WKTConstants::CS);
+ if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::PROJCS)) {
+ ThrowMissing(WKTConstants::CS);
+ }
+ auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
+ return DerivedProjectedCRS::create(buildProperties(node), baseProjCRS,
+ conversion, cs);
+}
+
+// ---------------------------------------------------------------------------
+
+static bool isGeodeticCRS(const std::string &name) {
+ return ci_equal(name, WKTConstants::GEODCRS) || // WKT2
+ ci_equal(name, WKTConstants::GEODETICCRS) || // WKT2
+ ci_equal(name, WKTConstants::GEOGCRS) || // WKT2 2018
+ ci_equal(name, WKTConstants::GEOGRAPHICCRS) || // WKT2 2018
+ ci_equal(name, WKTConstants::GEOGCS) || // WKT1
+ ci_equal(name, WKTConstants::GEOCCS); // WKT1
+}
+
+// ---------------------------------------------------------------------------
+
+CRSPtr WKTParser::Private::buildCRS(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ const std::string &name(nodeP->value());
+
+ if (isGeodeticCRS(name)) {
+ if (!isNull(nodeP->lookForChild(WKTConstants::BASEGEOGCRS,
+ WKTConstants::BASEGEODCRS))) {
+ return buildDerivedGeodeticCRS(node);
+ } else {
+ return util::nn_static_pointer_cast<CRS>(buildGeodeticCRS(node));
+ }
+ }
+
+ if (ci_equal(name, WKTConstants::PROJCS) ||
+ ci_equal(name, WKTConstants::PROJCRS) ||
+ ci_equal(name, WKTConstants::PROJECTEDCRS)) {
+ return util::nn_static_pointer_cast<CRS>(buildProjectedCRS(node));
+ }
+
+ if (ci_equal(name, WKTConstants::VERT_CS) ||
+ ci_equal(name, WKTConstants::VERTCRS) ||
+ ci_equal(name, WKTConstants::VERTICALCRS)) {
+ if (!isNull(nodeP->lookForChild(WKTConstants::BASEVERTCRS))) {
+ return util::nn_static_pointer_cast<CRS>(
+ buildDerivedVerticalCRS(node));
+ } else {
+ return util::nn_static_pointer_cast<CRS>(buildVerticalCRS(node));
+ }
+ }
+
+ if (ci_equal(name, WKTConstants::COMPD_CS) ||
+ ci_equal(name, WKTConstants::COMPOUNDCRS)) {
+ return util::nn_static_pointer_cast<CRS>(buildCompoundCRS(node));
+ }
+
+ if (ci_equal(name, WKTConstants::BOUNDCRS)) {
+ return util::nn_static_pointer_cast<CRS>(buildBoundCRS(node));
+ }
+
+ if (ci_equal(name, WKTConstants::TIMECRS)) {
+ if (!isNull(nodeP->lookForChild(WKTConstants::BASETIMECRS))) {
+ return util::nn_static_pointer_cast<CRS>(
+ buildDerivedTemporalCRS(node));
+ } else {
+ return util::nn_static_pointer_cast<CRS>(buildTemporalCRS(node));
+ }
+ }
+
+ if (ci_equal(name, WKTConstants::DERIVEDPROJCRS)) {
+ return util::nn_static_pointer_cast<CRS>(
+ buildDerivedProjectedCRS(node));
+ }
+
+ if (ci_equal(name, WKTConstants::ENGCRS) ||
+ ci_equal(name, WKTConstants::ENGINEERINGCRS)) {
+ if (!isNull(nodeP->lookForChild(WKTConstants::BASEENGCRS))) {
+ return util::nn_static_pointer_cast<CRS>(
+ buildDerivedEngineeringCRS(node));
+ } else {
+ return util::nn_static_pointer_cast<CRS>(buildEngineeringCRS(node));
+ }
+ }
+
+ if (ci_equal(name, WKTConstants::LOCAL_CS)) {
+ return util::nn_static_pointer_cast<CRS>(
+ buildEngineeringCRSFromLocalCS(node));
+ }
+
+ if (ci_equal(name, WKTConstants::PARAMETRICCRS)) {
+ if (!isNull(nodeP->lookForChild(WKTConstants::BASEPARAMCRS))) {
+ return util::nn_static_pointer_cast<CRS>(
+ buildDerivedParametricCRS(node));
+ } else {
+ return util::nn_static_pointer_cast<CRS>(buildParametricCRS(node));
+ }
+ }
+
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) {
+ const auto *nodeP = node->GP();
+ const std::string &name(nodeP->value());
+
+ auto crs = buildCRS(node);
+ if (crs) {
+ if (!toWGS84Parameters_.empty()) {
+ return util::nn_static_pointer_cast<BaseObject>(
+ BoundCRS::createFromTOWGS84(NN_NO_CHECK(crs),
+ toWGS84Parameters_));
+ }
+ if (!datumPROJ4Grids_.empty()) {
+ return util::nn_static_pointer_cast<BaseObject>(
+ BoundCRS::createFromNadgrids(NN_NO_CHECK(crs),
+ datumPROJ4Grids_));
+ }
+ return util::nn_static_pointer_cast<BaseObject>(NN_NO_CHECK(crs));
+ }
+
+ if (ci_equal(name, WKTConstants::DATUM) ||
+ ci_equal(name, WKTConstants::GEODETICDATUM) ||
+ ci_equal(name, WKTConstants::TRF)) {
+ return util::nn_static_pointer_cast<BaseObject>(
+ buildGeodeticReferenceFrame(node, PrimeMeridian::GREENWICH,
+ null_node));
+ }
+
+ if (ci_equal(name, WKTConstants::ENSEMBLE)) {
+ return util::nn_static_pointer_cast<BaseObject>(buildDatumEnsemble(
+ node, PrimeMeridian::GREENWICH,
+ !isNull(nodeP->lookForChild(WKTConstants::ELLIPSOID))));
+ }
+
+ if (ci_equal(name, WKTConstants::VDATUM) ||
+ ci_equal(name, WKTConstants::VERT_DATUM) ||
+ ci_equal(name, WKTConstants::VERTICALDATUM) ||
+ ci_equal(name, WKTConstants::VRF)) {
+ return util::nn_static_pointer_cast<BaseObject>(
+ buildVerticalReferenceFrame(node, null_node));
+ }
+
+ if (ci_equal(name, WKTConstants::TDATUM) ||
+ ci_equal(name, WKTConstants::TIMEDATUM)) {
+ return util::nn_static_pointer_cast<BaseObject>(
+ buildTemporalDatum(node));
+ }
+
+ if (ci_equal(name, WKTConstants::EDATUM) ||
+ ci_equal(name, WKTConstants::ENGINEERINGDATUM)) {
+ return util::nn_static_pointer_cast<BaseObject>(
+ buildEngineeringDatum(node));
+ }
+
+ if (ci_equal(name, WKTConstants::PDATUM) ||
+ ci_equal(name, WKTConstants::PARAMETRICDATUM)) {
+ return util::nn_static_pointer_cast<BaseObject>(
+ buildParametricDatum(node));
+ }
+
+ if (ci_equal(name, WKTConstants::ELLIPSOID) ||
+ ci_equal(name, WKTConstants::SPHEROID)) {
+ return util::nn_static_pointer_cast<BaseObject>(buildEllipsoid(node));
+ }
+
+ if (ci_equal(name, WKTConstants::COORDINATEOPERATION)) {
+ return util::nn_static_pointer_cast<BaseObject>(
+ buildCoordinateOperation(node));
+ }
+
+ if (ci_equal(name, WKTConstants::CONVERSION)) {
+ return util::nn_static_pointer_cast<BaseObject>(
+ buildConversion(node, UnitOfMeasure::METRE, UnitOfMeasure::DEGREE));
+ }
+
+ if (ci_equal(name, WKTConstants::CONCATENATEDOPERATION)) {
+ return util::nn_static_pointer_cast<BaseObject>(
+ buildConcatenatedOperation(node));
+ }
+
+ if (ci_equal(name, WKTConstants::ID) ||
+ ci_equal(name, WKTConstants::AUTHORITY)) {
+ return util::nn_static_pointer_cast<BaseObject>(
+ NN_NO_CHECK(buildId(node, false)));
+ }
+
+ throw ParsingException(concat("unhandled keyword: ", name));
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a sub-class of BaseObject from a user specified text.
+ *
+ * The text can be a:
+ * <ul>
+ * <li>WKT string</li>
+ * <li>PROJ string</li>
+ * <li>database code, prefixed by its authoriy. e.g. "EPSG:4326"</li>
+ * <li>URN. e.g. "urn:ogc:def:crs:EPSG::4326",
+ * "urn:ogc:def:coordinateOperation:EPSG::1671"</li>
+ * <li>an objet name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
+ * uniqueness is not guaranteed, the function may apply heuristics to
+ * determine the appropriate best match.</li>
+ * </ul>
+ *
+ * @param text One of the above mentionned text format
+ * @param dbContext Database context, or nullptr (in which case database
+ * lookups will not work)
+ * @param usePROJ4InitRules When set to true,
+ * init=epsg:XXXX syntax will be allowed and will be interpreted according to
+ * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
+ * order and will expect/output coordinates in radians. ProjectedCRS will have
+ * easting, northing axis order (except the ones with Transverse Mercator South
+ * Orientated projection). In that mode, the epsg:XXXX syntax will be also
+ * interprated the same way.
+ * @throw ParsingException
+ */
+BaseObjectNNPtr createFromUserInput(const std::string &text,
+ const DatabaseContextPtr &dbContext,
+ bool usePROJ4InitRules) {
+
+ for (const auto &wktConstants : WKTConstants::constants()) {
+ if (ci_starts_with(text, wktConstants)) {
+ return WKTParser()
+ .attachDatabaseContext(dbContext)
+ .setStrict(false)
+ .createFromWKT(text);
+ }
+ }
+ const char *textWithoutPlusPrefix = text.c_str();
+ if (textWithoutPlusPrefix[0] == '+')
+ textWithoutPlusPrefix++;
+
+ if (strncmp(textWithoutPlusPrefix, "proj=", strlen("proj=")) == 0 ||
+ strncmp(textWithoutPlusPrefix, "init=", strlen("init=")) == 0 ||
+ strncmp(textWithoutPlusPrefix, "title=", strlen("title=")) == 0) {
+ return PROJStringParser()
+ .attachDatabaseContext(dbContext)
+ .setUsePROJ4InitRules(usePROJ4InitRules)
+ .createFromPROJString(text);
+ }
+
+ auto tokens = split(text, ':');
+ if (tokens.size() == 2) {
+ if (!dbContext) {
+ throw ParsingException("no database context specified");
+ }
+ DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext));
+ const auto &authName = tokens[0];
+ const auto &code = tokens[1];
+ auto factory = AuthorityFactory::create(dbContextNNPtr, authName);
+ try {
+ return factory->createCoordinateReferenceSystem(code);
+ } catch (...) {
+ const auto authorities = dbContextNNPtr->getAuthorities();
+ for (const auto &authCandidate : authorities) {
+ if (ci_equal(authCandidate, authName)) {
+ return AuthorityFactory::create(dbContextNNPtr,
+ authCandidate)
+ ->createCoordinateReferenceSystem(code);
+ }
+ }
+ throw;
+ }
+ }
+
+ // urn:ogc:def:crs:EPSG::4326
+ if (tokens.size() == 7) {
+ if (!dbContext) {
+ throw ParsingException("no database context specified");
+ }
+ const auto &type = tokens[3];
+ auto factory =
+ AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[4]);
+ const auto &code = tokens[6];
+ if (type == "crs") {
+ return factory->createCoordinateReferenceSystem(code);
+ }
+ if (type == "coordinateOperation") {
+ return factory->createCoordinateOperation(code, true);
+ }
+ if (type == "datum") {
+ return factory->createDatum(code);
+ }
+ if (type == "ellipsoid") {
+ return factory->createEllipsoid(code);
+ }
+ if (type == "meridian") {
+ return factory->createPrimeMeridian(code);
+ }
+ throw ParsingException(concat("unhandled object type: ", type));
+ }
+
+ if (dbContext) {
+ auto factory =
+ AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string());
+ // First pass: exact match on CRS objects
+ // Second pass: exact match on other objects
+ // Third pass: approximate match on CRS objects
+ // Fourth pass: approximate match on other objects
+ constexpr size_t limitResultCount = 10;
+ for (int pass = 0; pass <= 3; ++pass) {
+ const bool approximateMatch = (pass >= 2);
+ auto res = factory->createObjectsFromName(
+ text,
+ (pass == 0 || pass == 2)
+ ? std::vector<
+ AuthorityFactory::ObjectType>{AuthorityFactory::
+ ObjectType::CRS}
+ : std::vector<
+ AuthorityFactory::
+ ObjectType>{AuthorityFactory::ObjectType::
+ ELLIPSOID,
+ AuthorityFactory::ObjectType::DATUM,
+ AuthorityFactory::ObjectType::
+ COORDINATE_OPERATION},
+ approximateMatch, limitResultCount);
+ if (res.size() == 1) {
+ return res.front();
+ }
+ if (res.size() > 1) {
+ if (pass == 0 || pass == 2) {
+ for (size_t ndim = 2; ndim <= 3; ndim++) {
+ for (const auto &obj : res) {
+ auto crs =
+ dynamic_cast<crs::GeographicCRS *>(obj.get());
+ if (crs &&
+ crs->coordinateSystem()->axisList().size() ==
+ ndim) {
+ return obj;
+ }
+ }
+ }
+ }
+
+ std::string msg("several objects matching this name: ");
+ bool first = true;
+ for (const auto &obj : res) {
+ if (msg.size() > 200) {
+ msg += ", ...";
+ break;
+ }
+ if (!first) {
+ msg += ", ";
+ }
+ first = false;
+ msg += obj->nameStr();
+ }
+ throw ParsingException(msg);
+ }
+ }
+ }
+
+ throw ParsingException("unrecognized format / unknown name");
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a sub-class of BaseObject from a WKT string.
+ *
+ * By default, validation is strict (to the extent of the checks that are
+ * actually implemented. Currently only WKT1 strict grammar is checked), and
+ * any issue detected will cause an exception to be thrown, unless
+ * setStrict(false) is called priorly.
+ *
+ * In non-strict mode, non-fatal issues will be recovered and simply listed
+ * in warningList(). This does not prevent more severe errors to cause an
+ * exception to be thrown.
+ *
+ * @throw ParsingException
+ */
+BaseObjectNNPtr WKTParser::createFromWKT(const std::string &wkt) {
+ WKTNodeNNPtr root = WKTNode::createFrom(wkt);
+ auto obj = d->build(root);
+
+ const auto dialect = guessDialect(wkt);
+ if (dialect == WKTGuessedDialect::WKT1_GDAL ||
+ dialect == WKTGuessedDialect::WKT1_ESRI) {
+ auto errorMsg = pj_wkt1_parse(wkt);
+ if (!errorMsg.empty()) {
+ d->emitRecoverableAssertion(errorMsg);
+ }
+ } else if (dialect == WKTGuessedDialect::WKT2_2015 ||
+ dialect == WKTGuessedDialect::WKT2_2018) {
+ auto errorMsg = pj_wkt2_parse(wkt);
+ if (!errorMsg.empty()) {
+ d->emitRecoverableAssertion(errorMsg);
+ }
+ }
+
+ return obj;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Attach a database context, to allow queries in it if needed.
+ */
+WKTParser &
+WKTParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) {
+ d->dbContext_ = dbContext;
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Guess the "dialect" of the WKT string.
+ */
+WKTParser::WKTGuessedDialect
+WKTParser::guessDialect(const std::string &wkt) noexcept {
+ const std::string *const wkt1_keywords[] = {
+ &WKTConstants::GEOCCS, &WKTConstants::GEOGCS, &WKTConstants::COMPD_CS,
+ &WKTConstants::PROJCS, &WKTConstants::VERT_CS, &WKTConstants::LOCAL_CS};
+ for (const auto &pointerKeyword : wkt1_keywords) {
+ if (ci_starts_with(wkt, *pointerKeyword)) {
+
+ if (ci_find(wkt, "GEOGCS[\"GCS_") != std::string::npos) {
+ return WKTGuessedDialect::WKT1_ESRI;
+ }
+
+ return WKTGuessedDialect::WKT1_GDAL;
+ }
+ }
+
+ const std::string *const wkt2_2018_only_keywords[] = {
+ &WKTConstants::GEOGCRS,
+ // contained in previous one
+ // &WKTConstants::BASEGEOGCRS,
+ &WKTConstants::CONCATENATEDOPERATION, &WKTConstants::USAGE,
+ &WKTConstants::DYNAMIC, &WKTConstants::FRAMEEPOCH, &WKTConstants::MODEL,
+ &WKTConstants::VELOCITYGRID, &WKTConstants::ENSEMBLE,
+ &WKTConstants::DERIVEDPROJCRS, &WKTConstants::BASEPROJCRS,
+ &WKTConstants::GEOGRAPHICCRS, &WKTConstants::TRF, &WKTConstants::VRF};
+
+ for (const auto &pointerKeyword : wkt2_2018_only_keywords) {
+ auto pos = ci_find(wkt, *pointerKeyword);
+ if (pos != std::string::npos &&
+ wkt[pos + pointerKeyword->size()] == '[') {
+ return WKTGuessedDialect::WKT2_2018;
+ }
+ }
+ static const char *const wkt2_2018_only_substrings[] = {
+ "CS[TemporalDateTime,", "CS[TemporalCount,", "CS[TemporalMeasure,",
+ };
+ for (const auto &substrings : wkt2_2018_only_substrings) {
+ if (ci_find(wkt, substrings) != std::string::npos) {
+ return WKTGuessedDialect::WKT2_2018;
+ }
+ }
+
+ for (const auto &wktConstants : WKTConstants::constants()) {
+ if (ci_starts_with(wkt, wktConstants)) {
+ return WKTGuessedDialect::WKT2_2015;
+ }
+ }
+
+ return WKTGuessedDialect::NOT_WKT;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+FormattingException::FormattingException(const char *message)
+ : Exception(message) {}
+
+// ---------------------------------------------------------------------------
+
+FormattingException::FormattingException(const std::string &message)
+ : Exception(message) {}
+
+// ---------------------------------------------------------------------------
+
+FormattingException::FormattingException(const FormattingException &) = default;
+
+// ---------------------------------------------------------------------------
+
+FormattingException::~FormattingException() = default;
+
+// ---------------------------------------------------------------------------
+
+void FormattingException::Throw(const char *msg) {
+ throw FormattingException(msg);
+}
+
+// ---------------------------------------------------------------------------
+
+void FormattingException::Throw(const std::string &msg) {
+ throw FormattingException(msg);
+}
+
+// ---------------------------------------------------------------------------
+
+ParsingException::ParsingException(const char *message) : Exception(message) {}
+
+// ---------------------------------------------------------------------------
+
+ParsingException::ParsingException(const std::string &message)
+ : Exception(message) {}
+
+// ---------------------------------------------------------------------------
+
+ParsingException::ParsingException(const ParsingException &) = default;
+
+// ---------------------------------------------------------------------------
+
+ParsingException::~ParsingException() = default;
+
+// ---------------------------------------------------------------------------
+
+IPROJStringExportable::~IPROJStringExportable() = default;
+
+// ---------------------------------------------------------------------------
+
+std::string IPROJStringExportable::exportToPROJString(
+ PROJStringFormatter *formatter) const {
+ _exportToPROJString(formatter);
+ if (formatter->getAddNoDefs() &&
+ formatter->convention() ==
+ io::PROJStringFormatter::Convention::PROJ_4 &&
+ dynamic_cast<const crs::CRS *>(this)) {
+ if (!formatter->hasParam("no_defs")) {
+ formatter->addParam("no_defs");
+ }
+ }
+ return formatter->toString();
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+struct Step {
+ std::string name{};
+ bool isInit = false;
+ bool inverted{false};
+
+ struct KeyValue {
+ std::string key{};
+ std::string value{};
+
+ explicit KeyValue(const std::string &keyIn) : key(keyIn) {}
+
+ KeyValue(const char *keyIn, const std::string &valueIn);
+
+ KeyValue(const std::string &keyIn, const std::string &valueIn)
+ : key(keyIn), value(valueIn) {}
+
+ // cppcheck-suppress functionStatic
+ bool keyEquals(const char *otherKey) const noexcept {
+ return key == otherKey;
+ }
+
+ // cppcheck-suppress functionStatic
+ bool equals(const char *otherKey, const char *otherVal) const noexcept {
+ return key == otherKey && value == otherVal;
+ }
+
+ bool operator!=(const KeyValue &other) const noexcept {
+ return key != other.key || value != other.value;
+ }
+ };
+
+ std::vector<KeyValue> paramValues{};
+};
+
+Step::KeyValue::KeyValue(const char *keyIn, const std::string &valueIn)
+ : key(keyIn), value(valueIn) {}
+
+struct PROJStringFormatter::Private {
+ PROJStringFormatter::Convention convention_ =
+ PROJStringFormatter::Convention::PROJ_5;
+ std::vector<double> toWGS84Parameters_{};
+ std::string vDatumExtension_{};
+ std::string hDatumExtension_{};
+
+ std::list<Step> steps_{};
+ std::vector<Step::KeyValue> globalParamValues_{};
+
+ struct InversionStackElt {
+ std::list<Step>::iterator startIter{};
+ bool iterValid = false;
+ bool currentInversionState = false;
+ };
+ std::vector<InversionStackElt> inversionStack_{InversionStackElt()};
+ bool omitProjLongLatIfPossible_ = false;
+ bool omitZUnitConversion_ = false;
+ DatabaseContextPtr dbContext_{};
+ bool useETMercForTMerc_ = false;
+ bool useETMercForTMercSet_ = false;
+ bool addNoDefs_ = true;
+ bool coordOperationOptimizations_ = false;
+
+ std::string result_{};
+
+ // cppcheck-suppress functionStatic
+ void appendToResult(const char *str);
+
+ // cppcheck-suppress functionStatic
+ void addStep();
+};
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+PROJStringFormatter::PROJStringFormatter(Convention conventionIn,
+ const DatabaseContextPtr &dbContext)
+ : d(internal::make_unique<Private>()) {
+ d->convention_ = conventionIn;
+ d->dbContext_ = dbContext;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+PROJStringFormatter::~PROJStringFormatter() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Constructs a new formatter.
+ *
+ * A formatter can be used only once (its internal state is mutated)
+ *
+ * Its default behaviour can be adjusted with the different setters.
+ *
+ * @param conventionIn PROJ string flavor. Defaults to Convention::PROJ_5
+ * @param dbContext Database context (can help to find alternative grid names).
+ * May be nullptr
+ * @return new formatter.
+ */
+PROJStringFormatterNNPtr
+PROJStringFormatter::create(Convention conventionIn,
+ DatabaseContextPtr dbContext) {
+ return NN_NO_CHECK(PROJStringFormatter::make_unique<PROJStringFormatter>(
+ conventionIn, dbContext));
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set whether Extented Transverse Mercator (etmerc) should be used
+ * instead of tmerc */
+void PROJStringFormatter::setUseETMercForTMerc(bool flag) {
+ d->useETMercForTMerc_ = flag;
+ d->useETMercForTMercSet_ = true;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns the PROJ string. */
+const std::string &PROJStringFormatter::toString() const {
+
+ assert(d->inversionStack_.size() == 1);
+
+ d->result_.clear();
+
+ for (auto iter = d->steps_.begin(); iter != d->steps_.end();) {
+ // Remove no-op helmert
+ auto &step = *iter;
+ const auto paramCount = step.paramValues.size();
+ if (step.name == "helmert" && (paramCount == 3 || paramCount == 8) &&
+ step.paramValues[0].equals("x", "0") &&
+ step.paramValues[1].equals("y", "0") &&
+ step.paramValues[2].equals("z", "0") &&
+ (paramCount == 3 ||
+ (step.paramValues[3].equals("rx", "0") &&
+ step.paramValues[4].equals("ry", "0") &&
+ step.paramValues[5].equals("rz", "0") &&
+ step.paramValues[6].equals("s", "0") &&
+ step.paramValues[7].keyEquals("convention")))) {
+ iter = d->steps_.erase(iter);
+ } else if (d->coordOperationOptimizations_ &&
+ step.name == "unitconvert" && paramCount == 2 &&
+ step.paramValues[0].keyEquals("xy_in") &&
+ step.paramValues[1].keyEquals("xy_out") &&
+ step.paramValues[0].value == step.paramValues[1].value) {
+ iter = d->steps_.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+
+ for (auto &step : d->steps_) {
+ if (!step.inverted) {
+ continue;
+ }
+
+ const auto paramCount = step.paramValues.size();
+
+ // axisswap order=2,1 is its own inverse
+ if (step.name == "axisswap" && paramCount == 1 &&
+ step.paramValues[0].equals("order", "2,1")) {
+ step.inverted = false;
+ continue;
+ }
+
+ // handle unitconvert inverse
+ if (step.name == "unitconvert" && paramCount == 2 &&
+ step.paramValues[0].keyEquals("xy_in") &&
+ step.paramValues[1].keyEquals("xy_out")) {
+ std::swap(step.paramValues[0].value, step.paramValues[1].value);
+ step.inverted = false;
+ continue;
+ }
+
+ if (step.name == "unitconvert" && paramCount == 2 &&
+ step.paramValues[0].keyEquals("z_in") &&
+ step.paramValues[1].keyEquals("z_out")) {
+ std::swap(step.paramValues[0].value, step.paramValues[1].value);
+ step.inverted = false;
+ continue;
+ }
+
+ if (step.name == "unitconvert" && paramCount == 4 &&
+ step.paramValues[0].keyEquals("xy_in") &&
+ step.paramValues[1].keyEquals("z_in") &&
+ step.paramValues[2].keyEquals("xy_out") &&
+ step.paramValues[3].keyEquals("z_out")) {
+ std::swap(step.paramValues[0].value, step.paramValues[2].value);
+ std::swap(step.paramValues[1].value, step.paramValues[3].value);
+ step.inverted = false;
+ continue;
+ }
+ }
+
+ bool changeDone;
+ do {
+ changeDone = false;
+ auto iterPrev = d->steps_.begin();
+ if (iterPrev == d->steps_.end()) {
+ break;
+ }
+ auto iterCur = iterPrev;
+ iterCur++;
+ for (size_t i = 1; i < d->steps_.size(); ++i, ++iterCur, ++iterPrev) {
+
+ auto &prevStep = *iterPrev;
+ auto &curStep = *iterCur;
+
+ const auto curStepParamCount = curStep.paramValues.size();
+ const auto prevStepParamCount = prevStep.paramValues.size();
+
+ // longlat (or its inverse) with ellipsoid only is a no-op
+ // do that only for an internal step
+ if (i + 1 < d->steps_.size() && curStep.name == "longlat" &&
+ curStepParamCount == 1 &&
+ curStep.paramValues[0].keyEquals("ellps")) {
+ d->steps_.erase(iterCur);
+ changeDone = true;
+ break;
+ }
+
+ // unitconvert (xy) followed by its inverse is a no-op
+ if (curStep.name == "unitconvert" &&
+ prevStep.name == "unitconvert" && !curStep.inverted &&
+ !prevStep.inverted && curStepParamCount == 2 &&
+ prevStepParamCount == 2 &&
+ curStep.paramValues[0].keyEquals("xy_in") &&
+ prevStep.paramValues[0].keyEquals("xy_in") &&
+ curStep.paramValues[1].keyEquals("xy_out") &&
+ prevStep.paramValues[1].keyEquals("xy_out") &&
+ curStep.paramValues[0].value == prevStep.paramValues[1].value &&
+ curStep.paramValues[1].value == prevStep.paramValues[0].value) {
+ ++iterCur;
+ d->steps_.erase(iterPrev, iterCur);
+ changeDone = true;
+ break;
+ }
+
+ // unitconvert (z) followed by its inverse is a no-op
+ if (curStep.name == "unitconvert" &&
+ prevStep.name == "unitconvert" && !curStep.inverted &&
+ !prevStep.inverted && curStepParamCount == 2 &&
+ prevStepParamCount == 2 &&
+ curStep.paramValues[0].keyEquals("z_in") &&
+ prevStep.paramValues[0].keyEquals("z_in") &&
+ curStep.paramValues[1].keyEquals("z_out") &&
+ prevStep.paramValues[1].keyEquals("z_out") &&
+ curStep.paramValues[0].value == prevStep.paramValues[1].value &&
+ curStep.paramValues[1].value == prevStep.paramValues[0].value) {
+ ++iterCur;
+ d->steps_.erase(iterPrev, iterCur);
+ changeDone = true;
+ break;
+ }
+
+ // unitconvert (xyz) followed by its inverse is a no-op
+ if (curStep.name == "unitconvert" &&
+ prevStep.name == "unitconvert" && !curStep.inverted &&
+ !prevStep.inverted && curStepParamCount == 4 &&
+ prevStepParamCount == 4 &&
+ curStep.paramValues[0].keyEquals("xy_in") &&
+ prevStep.paramValues[0].keyEquals("xy_in") &&
+ curStep.paramValues[1].keyEquals("z_in") &&
+ prevStep.paramValues[1].keyEquals("z_in") &&
+ curStep.paramValues[2].keyEquals("xy_out") &&
+ prevStep.paramValues[2].keyEquals("xy_out") &&
+ curStep.paramValues[3].keyEquals("z_out") &&
+ prevStep.paramValues[3].keyEquals("z_out") &&
+ curStep.paramValues[0].value == prevStep.paramValues[2].value &&
+ curStep.paramValues[1].value == prevStep.paramValues[3].value &&
+ curStep.paramValues[2].value == prevStep.paramValues[0].value &&
+ curStep.paramValues[3].value == prevStep.paramValues[1].value) {
+ ++iterCur;
+ d->steps_.erase(iterPrev, iterCur);
+ changeDone = true;
+ break;
+ }
+
+ // combine unitconvert (xy) and unitconvert (z)
+ for (int k = 0; k < 2; ++k) {
+ auto &first = (k == 0) ? curStep : prevStep;
+ auto &second = (k == 0) ? prevStep : curStep;
+ if (first.name == "unitconvert" &&
+ second.name == "unitconvert" && !first.inverted &&
+ !second.inverted && first.paramValues.size() == 2 &&
+ second.paramValues.size() == 2 &&
+ second.paramValues[0].keyEquals("xy_in") &&
+ second.paramValues[1].keyEquals("xy_out") &&
+ first.paramValues[0].keyEquals("z_in") &&
+ first.paramValues[1].keyEquals("z_out")) {
+
+ auto xy_in = second.paramValues[0].value;
+ auto xy_out = second.paramValues[1].value;
+ auto z_in = first.paramValues[0].value;
+ auto z_out = first.paramValues[1].value;
+ d->steps_.erase(iterPrev, iterCur);
+ iterCur->paramValues.clear();
+ iterCur->paramValues.emplace_back(
+ Step::KeyValue("xy_in", xy_in));
+ iterCur->paramValues.emplace_back(
+ Step::KeyValue("z_in", z_in));
+ iterCur->paramValues.emplace_back(
+ Step::KeyValue("xy_out", xy_out));
+ iterCur->paramValues.emplace_back(
+ Step::KeyValue("z_out", z_out));
+ changeDone = true;
+ break;
+ }
+ }
+ if (changeDone) {
+ break;
+ }
+
+ // +step +proj=unitconvert +xy_in=X1 +xy_out=X2
+ // +step +proj=unitconvert +xy_in=X2 +z_in=Z1 +xy_out=X1 +z_out=Z2
+ // ==> step +proj=unitconvert +z_in=Z1 +z_out=Z2
+ for (int k = 0; k < 2; ++k) {
+ auto &first = (k == 0) ? curStep : prevStep;
+ auto &second = (k == 0) ? prevStep : curStep;
+ if (first.name == "unitconvert" &&
+ second.name == "unitconvert" && !first.inverted &&
+ !second.inverted && first.paramValues.size() == 4 &&
+ second.paramValues.size() == 2 &&
+ first.paramValues[0].keyEquals("xy_in") &&
+ first.paramValues[1].keyEquals("z_in") &&
+ first.paramValues[2].keyEquals("xy_out") &&
+ first.paramValues[3].keyEquals("z_out") &&
+ second.paramValues[0].keyEquals("xy_in=") &&
+ second.paramValues[1].keyEquals("xy_out") &&
+ first.paramValues[0].value == second.paramValues[1].value &&
+ first.paramValues[2].value == second.paramValues[0].value) {
+ auto z_in = first.paramValues[1].value;
+ auto z_out = first.paramValues[3].value;
+ if (z_in != z_out) {
+ d->steps_.erase(iterPrev, iterCur);
+ iterCur->paramValues.clear();
+ iterCur->paramValues.emplace_back(
+ Step::KeyValue("z_in", z_in));
+ iterCur->paramValues.emplace_back(
+ Step::KeyValue("z_out", z_out));
+ } else {
+ ++iterCur;
+ d->steps_.erase(iterPrev, iterCur);
+ }
+ changeDone = true;
+ break;
+ }
+ }
+ if (changeDone) {
+ break;
+ }
+
+ // axisswap order=2,1 followed by itself is a no-op
+ if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
+ curStepParamCount == 1 && prevStepParamCount == 1 &&
+ curStep.paramValues[0].equals("order", "2,1") &&
+ prevStep.paramValues[0].equals("order", "2,1")) {
+ ++iterCur;
+ d->steps_.erase(iterPrev, iterCur);
+ changeDone = true;
+ break;
+ }
+
+ // axisswap order=2,1, unitconvert, axisswap order=2,1 -> can
+ // suppress axisswap
+ if (i + 1 < d->steps_.size() && prevStep.name == "axisswap" &&
+ curStep.name == "unitconvert" && prevStepParamCount == 1 &&
+ prevStep.paramValues[0].equals("order", "2,1")) {
+ auto iterNext = iterCur;
+ ++iterNext;
+ auto &nextStep = *iterNext;
+ if (nextStep.name == "axisswap" &&
+ nextStep.paramValues.size() == 1 &&
+ nextStep.paramValues[0].equals("order", "2,1")) {
+ d->steps_.erase(iterPrev);
+ d->steps_.erase(iterNext);
+ changeDone = true;
+ break;
+ }
+ }
+
+ // for practical purposes WGS84 and GRS80 ellipsoids are
+ // equivalents (cartesian transform between both lead to differences
+ // of the order of 1e-14 deg..).
+ // No need to do a cart roundtrip for that...
+ // and actually IGNF uses the GRS80 definition for the WGS84 datum
+ if (curStep.name == "cart" && prevStep.name == "cart" &&
+ curStep.inverted == !prevStep.inverted &&
+ curStepParamCount == 1 && prevStepParamCount == 1 &&
+ ((curStep.paramValues[0].equals("ellps", "WGS84") &&
+ prevStep.paramValues[0].equals("ellps", "GRS80")) ||
+ (curStep.paramValues[0].equals("ellps", "GRS80") &&
+ prevStep.paramValues[0].equals("ellps", "WGS84")))) {
+ ++iterCur;
+ d->steps_.erase(iterPrev, iterCur);
+ changeDone = true;
+ break;
+ }
+
+ if (curStep.name == "helmert" && prevStep.name == "helmert" &&
+ !curStep.inverted && !prevStep.inverted &&
+ curStepParamCount == 3 &&
+ curStepParamCount == prevStepParamCount) {
+ std::map<std::string, double> leftParamsMap;
+ std::map<std::string, double> rightParamsMap;
+ try {
+ for (const auto &kv : prevStep.paramValues) {
+ leftParamsMap[kv.key] = c_locale_stod(kv.value);
+ }
+ for (const auto &kv : curStep.paramValues) {
+ rightParamsMap[kv.key] = c_locale_stod(kv.value);
+ }
+ } catch (const std::invalid_argument &) {
+ break;
+ }
+ const std::string x("x");
+ const std::string y("y");
+ const std::string z("z");
+ if (leftParamsMap.find(x) != leftParamsMap.end() &&
+ leftParamsMap.find(y) != leftParamsMap.end() &&
+ leftParamsMap.find(z) != leftParamsMap.end() &&
+ rightParamsMap.find(x) != rightParamsMap.end() &&
+ rightParamsMap.find(y) != rightParamsMap.end() &&
+ rightParamsMap.find(z) != rightParamsMap.end()) {
+
+ const double xSum = leftParamsMap[x] + rightParamsMap[x];
+ const double ySum = leftParamsMap[y] + rightParamsMap[y];
+ const double zSum = leftParamsMap[z] + rightParamsMap[z];
+ if (xSum == 0.0 && ySum == 0.0 && zSum == 0.0) {
+ ++iterCur;
+ d->steps_.erase(iterPrev, iterCur);
+ } else {
+ prevStep.paramValues[0] =
+ Step::KeyValue("x", internal::toString(xSum));
+ prevStep.paramValues[1] =
+ Step::KeyValue("y", internal::toString(ySum));
+ prevStep.paramValues[2] =
+ Step::KeyValue("z", internal::toString(zSum));
+
+ d->steps_.erase(iterCur);
+ }
+ changeDone = true;
+ break;
+ }
+ }
+
+ // hermert followed by its inverse is a no-op
+ if (curStep.name == "helmert" && prevStep.name == "helmert" &&
+ !curStep.inverted && !prevStep.inverted &&
+ curStepParamCount == prevStepParamCount) {
+ std::set<std::string> leftParamsSet;
+ std::set<std::string> rightParamsSet;
+ std::map<std::string, std::string> leftParamsMap;
+ std::map<std::string, std::string> rightParamsMap;
+ for (const auto &kv : prevStep.paramValues) {
+ leftParamsSet.insert(kv.key);
+ leftParamsMap[kv.key] = kv.value;
+ }
+ for (const auto &kv : curStep.paramValues) {
+ rightParamsSet.insert(kv.key);
+ rightParamsMap[kv.key] = kv.value;
+ }
+ if (leftParamsSet == rightParamsSet) {
+ bool doErase = true;
+ try {
+ for (const auto &param : leftParamsSet) {
+ if (param == "convention" || param == "t_epoch" ||
+ param == "t_obs") {
+ if (leftParamsMap[param] !=
+ rightParamsMap[param]) {
+ doErase = false;
+ break;
+ }
+ } else if (c_locale_stod(leftParamsMap[param]) !=
+ -c_locale_stod(rightParamsMap[param])) {
+ doErase = false;
+ break;
+ }
+ }
+ } catch (const std::invalid_argument &) {
+ break;
+ }
+ if (doErase) {
+ ++iterCur;
+ d->steps_.erase(iterPrev, iterCur);
+ changeDone = true;
+ break;
+ }
+ }
+ }
+
+ // detect a step and its inverse
+ if (curStep.inverted != prevStep.inverted &&
+ curStep.name == prevStep.name &&
+ curStepParamCount == prevStepParamCount) {
+ bool allSame = true;
+ for (size_t j = 0; j < curStepParamCount; j++) {
+ if (curStep.paramValues[j] != prevStep.paramValues[j]) {
+ allSame = false;
+ break;
+ }
+ }
+ if (allSame) {
+ ++iterCur;
+ d->steps_.erase(iterPrev, iterCur);
+ changeDone = true;
+ break;
+ }
+ }
+ }
+ } while (changeDone);
+
+ if (d->steps_.size() > 1 ||
+ (d->steps_.size() == 1 &&
+ (d->steps_.front().inverted || !d->globalParamValues_.empty()))) {
+ d->appendToResult("+proj=pipeline");
+
+ for (const auto &paramValue : d->globalParamValues_) {
+ d->appendToResult("+");
+ d->result_ += paramValue.key;
+ if (!paramValue.value.empty()) {
+ d->result_ += "=";
+ d->result_ += paramValue.value;
+ }
+ }
+ }
+
+ for (const auto &step : d->steps_) {
+ if (!d->result_.empty()) {
+ d->appendToResult("+step");
+ }
+ if (step.inverted) {
+ d->appendToResult("+inv");
+ }
+ if (!step.name.empty()) {
+ d->appendToResult(step.isInit ? "+init=" : "+proj=");
+ d->result_ += step.name;
+ }
+ for (const auto &paramValue : step.paramValues) {
+ d->appendToResult("+");
+ d->result_ += paramValue.key;
+ if (!paramValue.value.empty()) {
+ d->result_ += "=";
+ d->result_ += paramValue.value;
+ }
+ }
+ }
+ return d->result_;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+PROJStringFormatter::Convention PROJStringFormatter::convention() const {
+ return d->convention_;
+}
+
+// ---------------------------------------------------------------------------
+
+bool PROJStringFormatter::getUseETMercForTMerc(bool &settingSetOut) const {
+ settingSetOut = d->useETMercForTMercSet_;
+ return d->useETMercForTMerc_;
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::setCoordinateOperationOptimizations(bool enable) {
+ d->coordOperationOptimizations_ = enable;
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::Private::appendToResult(const char *str) {
+ if (!result_.empty()) {
+ result_ += " ";
+ }
+ result_ += str;
+}
+
+// ---------------------------------------------------------------------------
+
+static void
+PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps,
+ std::vector<Step::KeyValue> &globalParamValues,
+ std::string &title, std::string &vunits,
+ std::string &vto_meter) {
+ std::string word;
+ std::istringstream iss(projString, std::istringstream::in);
+ bool inverted = false;
+ bool prevWasStep = false;
+ bool inProj = false;
+ bool inPipeline = false;
+ bool prevWasTitle = false;
+ bool prevWasInit = false;
+
+ while (iss >> word) {
+ if (word[0] == '+') {
+ word = word.substr(1);
+ } else if (prevWasTitle && word.find('=') == std::string::npos) {
+ title += " ";
+ title += word;
+ continue;
+ }
+
+ prevWasTitle = false;
+ if (word == "proj=pipeline") {
+ if (inPipeline) {
+ throw ParsingException("nested pipeline not supported");
+ }
+ inverted = false;
+ prevWasStep = false;
+ inProj = true;
+ inPipeline = true;
+ } else if (word == "step") {
+ if (!inPipeline) {
+ throw ParsingException("+step found outside pipeline");
+ }
+ inverted = false;
+ prevWasStep = true;
+ prevWasInit = false;
+ } else if (word == "inv") {
+ if (prevWasStep) {
+ inverted = true;
+ } else {
+ if (steps.empty()) {
+ throw ParsingException("+inv found at unexpected place");
+ }
+ steps.back().inverted = true;
+ }
+ prevWasStep = false;
+ } else if (starts_with(word, "proj=")) {
+ auto stepName = word.substr(strlen("proj="));
+ if (prevWasInit) {
+ steps.back() = Step();
+ prevWasInit = false;
+ } else {
+ steps.push_back(Step());
+ }
+ steps.back().name = stepName;
+ steps.back().inverted = inverted;
+ prevWasStep = false;
+ inProj = true;
+ } else if (starts_with(word, "init=")) {
+ if (prevWasInit) {
+ throw ParsingException("+init= found at unexpected place");
+ }
+ auto initName = word.substr(strlen("init="));
+ steps.push_back(Step());
+ steps.back().name = initName;
+ steps.back().isInit = true;
+ steps.back().inverted = inverted;
+ prevWasStep = false;
+ prevWasInit = true;
+ inProj = true;
+ } else if (inProj) {
+ const auto pos = word.find('=');
+ auto key = word.substr(0, pos);
+ auto pair = (pos != std::string::npos)
+ ? Step::KeyValue(key, word.substr(pos + 1))
+ : Step::KeyValue(key);
+ if (steps.empty()) {
+ globalParamValues.push_back(pair);
+ } else {
+ steps.back().paramValues.push_back(pair);
+ }
+ prevWasStep = false;
+ } else if (starts_with(word, "vunits=")) {
+ vunits = word.substr(strlen("vunits="));
+ } else if (starts_with(word, "vto_meter=")) {
+ vto_meter = word.substr(strlen("vto_meter="));
+ } else if (starts_with(word, "title=")) {
+ title = word.substr(strlen("title="));
+ prevWasTitle = true;
+ } else {
+ throw ParsingException("Unexpected token: " + word);
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::ingestPROJString(
+ const std::string &str) // throw ParsingException
+{
+ std::vector<Step> steps;
+ std::string title;
+ std::string vunits;
+ std::string vto_meter;
+ PROJStringSyntaxParser(str, steps, d->globalParamValues_, title, vunits,
+ vto_meter);
+ d->steps_.insert(d->steps_.end(), steps.begin(), steps.end());
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::startInversion() {
+ PROJStringFormatter::Private::InversionStackElt elt;
+ elt.startIter = d->steps_.end();
+ if (elt.startIter != d->steps_.begin()) {
+ elt.iterValid = true;
+ --elt.startIter; // point to the last valid element
+ } else {
+ elt.iterValid = false;
+ }
+ elt.currentInversionState =
+ !d->inversionStack_.back().currentInversionState;
+ d->inversionStack_.push_back(elt);
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::stopInversion() {
+ assert(!d->inversionStack_.empty());
+ auto startIter = d->inversionStack_.back().startIter;
+ if (!d->inversionStack_.back().iterValid) {
+ startIter = d->steps_.begin();
+ } else {
+ ++startIter; // advance after the last valid element we marked above
+ }
+ // Invert the inversion status of the steps between the start point and
+ // the current end of steps
+ for (auto iter = startIter; iter != d->steps_.end(); ++iter) {
+ iter->inverted = !iter->inverted;
+ }
+ // And reverse the order of steps in that range as well.
+ std::reverse(startIter, d->steps_.end());
+ d->inversionStack_.pop_back();
+}
+
+// ---------------------------------------------------------------------------
+
+bool PROJStringFormatter::isInverted() const {
+ return d->inversionStack_.back().currentInversionState;
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::Private::addStep() { steps_.emplace_back(Step()); }
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::addStep(const char *stepName) {
+ d->addStep();
+ d->steps_.back().name.assign(stepName);
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::addStep(const std::string &stepName) {
+ d->addStep();
+ d->steps_.back().name = stepName;
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::setCurrentStepInverted(bool inverted) {
+ assert(!d->steps_.empty());
+ d->steps_.back().inverted = inverted;
+}
+
+// ---------------------------------------------------------------------------
+
+bool PROJStringFormatter::hasParam(const char *paramName) const {
+ if (!d->steps_.empty()) {
+ for (const auto &paramValue : d->steps_.back().paramValues) {
+ if (paramValue.keyEquals(paramName)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::addNoDefs(bool b) { d->addNoDefs_ = b; }
+
+// ---------------------------------------------------------------------------
+
+bool PROJStringFormatter::getAddNoDefs() const { return d->addNoDefs_; }
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::addParam(const std::string &paramName) {
+ if (d->steps_.empty()) {
+ d->addStep();
+ }
+ d->steps_.back().paramValues.push_back(Step::KeyValue(paramName));
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::addParam(const char *paramName, int val) {
+ addParam(std::string(paramName), val);
+}
+
+void PROJStringFormatter::addParam(const std::string &paramName, int val) {
+ addParam(paramName, internal::toString(val));
+}
+
+// ---------------------------------------------------------------------------
+
+static std::string formatToString(double val) {
+ if (std::abs(val * 10 - std::round(val * 10)) < 1e-8) {
+ // For the purpose of
+ // https://www.epsg-registry.org/export.htm?wkt=urn:ogc:def:crs:EPSG::27561
+ // Latitude of natural of origin to be properly rounded from 55 grad
+ // to
+ // 49.5 deg
+ val = std::round(val * 10) / 10;
+ }
+ return normalizeSerializedString(internal::toString(val));
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::addParam(const char *paramName, double val) {
+ addParam(std::string(paramName), val);
+}
+
+void PROJStringFormatter::addParam(const std::string &paramName, double val) {
+ addParam(paramName, formatToString(val));
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::addParam(const char *paramName,
+ const std::vector<double> &vals) {
+ std::string paramValue;
+ for (size_t i = 0; i < vals.size(); ++i) {
+ if (i > 0) {
+ paramValue += ',';
+ }
+ paramValue += formatToString(vals[i]);
+ }
+ addParam(paramName, paramValue);
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::addParam(const char *paramName, const char *val) {
+ addParam(std::string(paramName), val);
+}
+
+void PROJStringFormatter::addParam(const char *paramName,
+ const std::string &val) {
+ addParam(std::string(paramName), val);
+}
+
+void PROJStringFormatter::addParam(const std::string &paramName,
+ const char *val) {
+ addParam(paramName, std::string(val));
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::addParam(const std::string &paramName,
+ const std::string &val) {
+ if (d->steps_.empty()) {
+ d->addStep();
+ }
+ d->steps_.back().paramValues.push_back(Step::KeyValue(paramName, val));
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::setTOWGS84Parameters(
+ const std::vector<double> &params) {
+ d->toWGS84Parameters_ = params;
+}
+
+// ---------------------------------------------------------------------------
+
+const std::vector<double> &PROJStringFormatter::getTOWGS84Parameters() const {
+ return d->toWGS84Parameters_;
+}
+
+// ---------------------------------------------------------------------------
+
+std::set<std::string> PROJStringFormatter::getUsedGridNames() const {
+ std::set<std::string> res;
+ for (const auto &step : d->steps_) {
+ for (const auto &param : step.paramValues) {
+ if (param.keyEquals("grids")) {
+ res.insert(param.value);
+ }
+ }
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::setVDatumExtension(const std::string &filename) {
+ d->vDatumExtension_ = filename;
+}
+
+// ---------------------------------------------------------------------------
+
+const std::string &PROJStringFormatter::getVDatumExtension() const {
+ return d->vDatumExtension_;
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::setHDatumExtension(const std::string &filename) {
+ d->hDatumExtension_ = filename;
+}
+
+// ---------------------------------------------------------------------------
+
+const std::string &PROJStringFormatter::getHDatumExtension() const {
+ return d->hDatumExtension_;
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::setOmitProjLongLatIfPossible(bool omit) {
+ assert(d->omitProjLongLatIfPossible_ ^ omit);
+ d->omitProjLongLatIfPossible_ = omit;
+}
+
+// ---------------------------------------------------------------------------
+
+bool PROJStringFormatter::omitProjLongLatIfPossible() const {
+ return d->omitProjLongLatIfPossible_;
+}
+
+// ---------------------------------------------------------------------------
+
+void PROJStringFormatter::setOmitZUnitConversion(bool omit) {
+ assert(d->omitZUnitConversion_ ^ omit);
+ d->omitZUnitConversion_ = omit;
+}
+
+// ---------------------------------------------------------------------------
+
+bool PROJStringFormatter::omitZUnitConversion() const {
+ return d->omitZUnitConversion_;
+}
+
+// ---------------------------------------------------------------------------
+
+const DatabaseContextPtr &PROJStringFormatter::databaseContext() const {
+ return d->dbContext_;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+struct PROJStringParser::Private {
+ DatabaseContextPtr dbContext_{};
+ bool usePROJ4InitRules_ = false;
+ std::vector<std::string> warningList_{};
+
+ std::string projString_{};
+
+ std::vector<Step> steps_{};
+ std::vector<Step::KeyValue> globalParamValues_{};
+ std::string title_{};
+
+ template <class T>
+ // cppcheck-suppress functionStatic
+ bool hasParamValue(const Step &step, const T key) {
+ for (const auto &pair : globalParamValues_) {
+ if (ci_equal(pair.key, key)) {
+ return true;
+ }
+ }
+ for (const auto &pair : step.paramValues) {
+ if (ci_equal(pair.key, key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template <class T>
+ // cppcheck-suppress functionStatic
+ const std::string &getParamValue(const Step &step, const T key) {
+ for (const auto &pair : globalParamValues_) {
+ if (ci_equal(pair.key, key)) {
+ return pair.value;
+ }
+ }
+ for (const auto &pair : step.paramValues) {
+ if (ci_equal(pair.key, key)) {
+ return pair.value;
+ }
+ }
+ return emptyString;
+ }
+
+ // cppcheck-suppress functionStatic
+ const std::string &getParamValueK(const Step &step) {
+ for (const auto &pair : step.paramValues) {
+ if (ci_equal(pair.key, "k") || ci_equal(pair.key, "k_0")) {
+ return pair.value;
+ }
+ }
+ return emptyString;
+ }
+
+ // cppcheck-suppress functionStatic
+ std::string guessBodyName(double a);
+
+ PrimeMeridianNNPtr buildPrimeMeridian(const Step &step);
+ GeodeticReferenceFrameNNPtr buildDatum(const Step &step,
+ const std::string &title);
+ GeographicCRSNNPtr buildGeographicCRS(int iStep, int iUnitConvert,
+ int iAxisSwap, bool ignoreVUnits,
+ bool ignorePROJAxis);
+ GeodeticCRSNNPtr buildGeocentricCRS(int iStep, int iUnitConvert);
+ CRSNNPtr buildProjectedCRS(int iStep, GeographicCRSNNPtr geogCRS,
+ int iUnitConvert, int iAxisSwap);
+ CRSNNPtr buildBoundOrCompoundCRSIfNeeded(int iStep, CRSNNPtr crs);
+ UnitOfMeasure buildUnit(const Step &step, const std::string &unitsParamName,
+ const std::string &toMeterParamName);
+ CoordinateOperationNNPtr buildHelmertTransformation(
+ int iStep, int iFirstAxisSwap = -1, int iFirstUnitConvert = -1,
+ int iFirstGeogStep = -1, int iSecondGeogStep = -1,
+ int iSecondAxisSwap = -1, int iSecondUnitConvert = -1);
+ CoordinateOperationNNPtr buildMolodenskyTransformation(
+ int iStep, int iFirstAxisSwap = -1, int iFirstUnitConvert = -1,
+ int iFirstGeogStep = -1, int iSecondGeogStep = -1,
+ int iSecondAxisSwap = -1, int iSecondUnitConvert = -1);
+
+ enum class AxisType { REGULAR, NORTH_POLE, SOUTH_POLE };
+
+ std::vector<CoordinateSystemAxisNNPtr>
+ processAxisSwap(const Step &step, const UnitOfMeasure &unit, int iAxisSwap,
+ AxisType axisType, bool ignorePROJAxis);
+
+ EllipsoidalCSNNPtr buildEllipsoidalCS(int iStep, int iUnitConvert,
+ int iAxisSwap, bool ignoreVUnits,
+ bool ignorePROJAxis);
+};
+
+// ---------------------------------------------------------------------------
+
+PROJStringParser::PROJStringParser() : d(internal::make_unique<Private>()) {}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+PROJStringParser::~PROJStringParser() = default;
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Attach a database context, to allow queries in it if needed.
+ */
+PROJStringParser &
+PROJStringParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) {
+ d->dbContext_ = dbContext;
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Set how init=epsg:XXXX syntax should be interpreted.
+ *
+ * @param enable When set to true,
+ * init=epsg:XXXX syntax will be allowed and will be interpreted according to
+ * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
+ * order and will expect/output coordinates in radians. ProjectedCRS will have
+ * easting, northing axis order (except the ones with Transverse Mercator South
+ * Orientated projection).
+ */
+PROJStringParser &PROJStringParser::setUsePROJ4InitRules(bool enable) {
+ d->usePROJ4InitRules_ = enable;
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return the list of warnings found during parsing.
+ */
+std::vector<std::string> PROJStringParser::warningList() const {
+ return d->warningList_;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+static const struct LinearUnitDesc {
+ const char *projName;
+ const char *convToMeter;
+ const char *name;
+ int epsgCode;
+} linearUnitDescs[] = {
+ {"mm", "0.001", "millimetre", 1025},
+ {"cm", "0.01", "centimetre", 1033},
+ {"m", "1.0", "metre", 9001},
+ {"meter", "1.0", "metre", 9001}, // alternative
+ {"metre", "1.0", "metre", 9001}, // alternative
+ {"ft", "0.3048", "foot", 9002},
+ {"us-ft", "0.3048006096012192", "US survey foot", 9003},
+ {"fath", "1.8288", "fathom", 9014},
+ {"kmi", "1852", "nautical mile", 9030},
+ {"us-ch", "20.11684023368047", "US survey chain", 9033},
+ {"us-mi", "1609.347218694437", "US survey mile", 9035},
+ {"km", "1000.0", "kilometre", 9036},
+ {"ind-ft", "0.30479841", "Indian foot (1937)", 9081},
+ {"ind-yd", "0.91439523", "Indian yard (1937)", 9085},
+ {"mi", "1609.344", "Statute mile", 9093},
+ {"yd", "0.9144", "yard", 9096},
+ {"ch", "20.1168", "chain", 9097},
+ {"link", "0.201168", "link", 9098},
+ {"dm", "0.1", "decimetre", 0}, // no EPSG equivalent
+ {"in", "0.0254", "inch", 0}, // no EPSG equivalent
+ {"us-in", "0.025400050800101", "US survey inch", 0}, // no EPSG equivalent
+ {"us-yd", "0.914401828803658", "US survey yard", 0}, // no EPSG equivalent
+ {"ind-ch", "20.11669506", "Indian chain", 0}, // no EPSG equivalent
+};
+
+static const LinearUnitDesc *getLinearUnits(const std::string &projName) {
+ for (const auto &desc : linearUnitDescs) {
+ if (desc.projName == projName)
+ return &desc;
+ }
+ return nullptr;
+}
+
+static const LinearUnitDesc *getLinearUnits(double toMeter) {
+ for (const auto &desc : linearUnitDescs) {
+ if (std::fabs(c_locale_stod(desc.convToMeter) - toMeter) <
+ 1e-10 * toMeter) {
+ return &desc;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+static UnitOfMeasure _buildUnit(const LinearUnitDesc *unitsMatch) {
+ std::string unitsCode;
+ if (unitsMatch->epsgCode) {
+ std::ostringstream buffer;
+ buffer.imbue(std::locale::classic());
+ buffer << unitsMatch->epsgCode;
+ unitsCode = buffer.str();
+ }
+ return UnitOfMeasure(
+ unitsMatch->name, c_locale_stod(unitsMatch->convToMeter),
+ UnitOfMeasure::Type::LINEAR,
+ unitsMatch->epsgCode ? Identifier::EPSG : std::string(), unitsCode);
+}
+
+// ---------------------------------------------------------------------------
+
+static UnitOfMeasure _buildUnit(double to_meter_value) {
+ // TODO: look-up in EPSG catalog
+ return UnitOfMeasure("unknown", to_meter_value,
+ UnitOfMeasure::Type::LINEAR);
+}
+
+// ---------------------------------------------------------------------------
+
+UnitOfMeasure
+PROJStringParser::Private::buildUnit(const Step &step,
+ const std::string &unitsParamName,
+ const std::string &toMeterParamName) {
+ UnitOfMeasure unit = UnitOfMeasure::METRE;
+ const LinearUnitDesc *unitsMatch = nullptr;
+ const auto &projUnits = getParamValue(step, unitsParamName);
+ if (!projUnits.empty()) {
+ unitsMatch = getLinearUnits(projUnits);
+ if (unitsMatch == nullptr) {
+ throw ParsingException("unhandled " + unitsParamName + "=" +
+ projUnits);
+ }
+ }
+
+ const auto &toMeter = getParamValue(step, toMeterParamName);
+ if (!toMeter.empty()) {
+ double to_meter_value;
+ try {
+ to_meter_value = c_locale_stod(toMeter);
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("invalid value for " + toMeterParamName);
+ }
+ unitsMatch = getLinearUnits(to_meter_value);
+ if (unitsMatch == nullptr) {
+ unit = _buildUnit(to_meter_value);
+ }
+ }
+
+ if (unitsMatch) {
+ unit = _buildUnit(unitsMatch);
+ }
+
+ return unit;
+}
+
+// ---------------------------------------------------------------------------
+
+static const struct DatumDesc {
+ const char *projName;
+ const char *gcsName;
+ int gcsCode;
+ const char *datumName;
+ int datumCode;
+ const char *ellipsoidName;
+ int ellipsoidCode;
+ double a;
+ double rf;
+} datumDescs[] = {
+ {"GGRS87", "GGRS87", 4121, "Greek Geodetic Reference System 1987", 6121,
+ "GRS 1980", 7019, 6378137, 298.257222101},
+ {"postdam", "DHDN", 4314, "Deutsches Hauptdreiecksnetz", 6314,
+ "Bessel 1841", 7004, 6377397.155, 299.1528128},
+ {"carthage", "Carthage", 4223, "Carthage", 6223, "Clarke 1880 (IGN)", 7011,
+ 6378249.2, 293.4660213},
+ {"hermannskogel", "MGI", 4312, "Militar-Geographische Institut", 6312,
+ "Bessel 1841", 7004, 6377397.155, 299.1528128},
+ {"ire65", "TM65", 4299, "TM65", 6299, "Airy Modified 1849", 7002,
+ 6377340.189, 299.3249646},
+ {"nzgd49", "NZGD49", 4272, "New Zealand Geodetic Datum 1949", 6272,
+ "International 1924", 7022, 6378388, 297},
+ {"OSGB36", "OSGB 1936", 4277, "OSGB 1936", 6277, "Airy 1830", 7001,
+ 6377563.396, 299.3249646},
+};
+
+// ---------------------------------------------------------------------------
+
+static bool isGeographicStep(const std::string &name) {
+ return name == "longlat" || name == "lonlat" || name == "latlong" ||
+ name == "latlon";
+}
+
+// ---------------------------------------------------------------------------
+
+static bool isGeocentricStep(const std::string &name) {
+ return name == "geocent" || name == "cart";
+}
+
+// ---------------------------------------------------------------------------
+
+static bool isProjectedStep(const std::string &name) {
+ if (name == "etmerc" || name == "utm" ||
+ !getMappingsFromPROJName(name).empty()) {
+ return true;
+ }
+ // IMPROVE ME: have a better way of distinguishing projections from
+ // other
+ // transformations.
+ if (name == "pipeline" || name == "geoc" || name == "deformation" ||
+ name == "helmert" || name == "hgridshift" || name == "molodensky" ||
+ name == "vgridshit") {
+ return false;
+ }
+ const auto *operations = proj_list_operations();
+ for (int i = 0; operations[i].id != nullptr; ++i) {
+ if (name == operations[i].id) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+static PropertyMap createMapWithUnknownName() {
+ return PropertyMap().set(common::IdentifiedObject::NAME_KEY, "unknown");
+}
+
+// ---------------------------------------------------------------------------
+
+PrimeMeridianNNPtr
+PROJStringParser::Private::buildPrimeMeridian(const Step &step) {
+
+ PrimeMeridianNNPtr pm = PrimeMeridian::GREENWICH;
+ const auto &pmStr = getParamValue(step, "pm");
+ if (!pmStr.empty()) {
+ char *end;
+ double pmValue = dmstor(pmStr.c_str(), &end) * RAD_TO_DEG;
+ if (pmValue != HUGE_VAL && *end == '\0') {
+ pm = PrimeMeridian::create(createMapWithUnknownName(),
+ Angle(pmValue));
+ } else {
+ bool found = false;
+ if (pmStr == "paris") {
+ found = true;
+ pm = PrimeMeridian::PARIS;
+ }
+ auto proj_prime_meridians = proj_list_prime_meridians();
+ for (int i = 0; !found && proj_prime_meridians[i].id != nullptr;
+ i++) {
+ if (pmStr == proj_prime_meridians[i].id) {
+ found = true;
+ std::string name = static_cast<char>(::toupper(pmStr[0])) +
+ pmStr.substr(1);
+ pmValue = dmstor(proj_prime_meridians[i].defn, nullptr) *
+ RAD_TO_DEG;
+ pm = PrimeMeridian::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, name),
+ Angle(pmValue));
+ break;
+ }
+ }
+ if (!found) {
+ throw ParsingException("unknown pm " + pmStr);
+ }
+ }
+ }
+ return pm;
+}
+
+// ---------------------------------------------------------------------------
+
+std::string PROJStringParser::Private::guessBodyName(double a) {
+ return Ellipsoid::guessBodyName(dbContext_, a);
+}
+
+// ---------------------------------------------------------------------------
+
+GeodeticReferenceFrameNNPtr
+PROJStringParser::Private::buildDatum(const Step &step,
+ const std::string &title) {
+
+ const auto &ellpsStr = getParamValue(step, "ellps");
+ const auto &datumStr = getParamValue(step, "datum");
+ const auto &RStr = getParamValue(step, "R");
+ const auto &aStr = getParamValue(step, "a");
+ const auto &bStr = getParamValue(step, "b");
+ const auto &rfStr = getParamValue(step, "rf");
+ const auto &fStr = getParamValue(step, "f");
+ const auto &esStr = getParamValue(step, "es");
+ const auto &eStr = getParamValue(step, "e");
+ double a = -1.0;
+ double b = -1.0;
+ double rf = -1.0;
+ const util::optional<std::string> optionalEmptyString{};
+ const bool numericParamPresent =
+ !RStr.empty() || !aStr.empty() || !bStr.empty() || !rfStr.empty() ||
+ !fStr.empty() || !esStr.empty() || !eStr.empty();
+
+ PrimeMeridianNNPtr pm(buildPrimeMeridian(step));
+ PropertyMap grfMap;
+
+ // It is arguable that we allow the prime meridian of a datum defined by
+ // its name to be overriden, but this is found at least in a regression test
+ // of GDAL. So let's keep the ellipsoid part of the datum in that case and
+ // use the specified prime meridian.
+ const auto overridePmIfNeeded =
+ [&pm](const GeodeticReferenceFrameNNPtr &grf) {
+ if (pm->_isEquivalentTo(PrimeMeridian::GREENWICH.get())) {
+ return grf;
+ } else {
+ return GeodeticReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "Unknown based on " +
+ grf->ellipsoid()->nameStr() +
+ " ellipsoid"),
+ grf->ellipsoid(), grf->anchorDefinition(), pm);
+ }
+ };
+
+ // R take precedence
+ if (!RStr.empty()) {
+ double R;
+ try {
+ R = c_locale_stod(RStr);
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("Invalid R value");
+ }
+ auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(),
+ Length(R), guessBodyName(R));
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title.c_str()),
+ ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
+ }
+
+ if (!datumStr.empty()) {
+ auto l_datum = [&datumStr, &overridePmIfNeeded, &grfMap,
+ &optionalEmptyString, &pm]() {
+ if (datumStr == "WGS84") {
+ return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
+ } else if (datumStr == "NAD83") {
+ return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6269);
+ } else if (datumStr == "NAD27") {
+ return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6267);
+ } else {
+
+ for (const auto &datumDesc : datumDescs) {
+ if (datumStr == datumDesc.projName) {
+ (void)datumDesc.gcsName; // to please cppcheck
+ (void)datumDesc.gcsCode; // to please cppcheck
+ auto ellipsoid = Ellipsoid::createFlattenedSphere(
+ grfMap
+ .set(IdentifiedObject::NAME_KEY,
+ datumDesc.ellipsoidName)
+ .set(Identifier::CODESPACE_KEY,
+ Identifier::EPSG)
+ .set(Identifier::CODE_KEY,
+ datumDesc.ellipsoidCode),
+ Length(datumDesc.a), Scale(datumDesc.rf));
+ return GeodeticReferenceFrame::create(
+ grfMap
+ .set(IdentifiedObject::NAME_KEY,
+ datumDesc.datumName)
+ .set(Identifier::CODESPACE_KEY,
+ Identifier::EPSG)
+ .set(Identifier::CODE_KEY, datumDesc.datumCode),
+ ellipsoid, optionalEmptyString, pm);
+ }
+ }
+ }
+ throw ParsingException("unknown datum " + datumStr);
+ }();
+ if (!numericParamPresent) {
+ return l_datum;
+ }
+ a = l_datum->ellipsoid()->semiMajorAxis().getSIValue();
+ rf = l_datum->ellipsoid()->computedInverseFlattening();
+ }
+
+ else if (!ellpsStr.empty()) {
+ auto l_datum = [&ellpsStr, &title, &grfMap, &optionalEmptyString,
+ &pm]() {
+ if (ellpsStr == "WGS84") {
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty()
+ ? "Unknown based on WGS84 ellipsoid"
+ : title.c_str()),
+ Ellipsoid::WGS84, optionalEmptyString, pm);
+ } else if (ellpsStr == "GRS80") {
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty()
+ ? "Unknown based on GRS80 ellipsoid"
+ : title.c_str()),
+ Ellipsoid::GRS1980, optionalEmptyString, pm);
+ } else {
+ auto proj_ellps = proj_list_ellps();
+ for (int i = 0; proj_ellps[i].id != nullptr; i++) {
+ if (ellpsStr == proj_ellps[i].id) {
+ assert(strncmp(proj_ellps[i].major, "a=", 2) == 0);
+ const double a_iter =
+ c_locale_stod(proj_ellps[i].major + 2);
+ EllipsoidPtr ellipsoid;
+ PropertyMap ellpsMap;
+ if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) {
+ const double b_iter =
+ c_locale_stod(proj_ellps[i].ell + 2);
+ ellipsoid =
+ Ellipsoid::createTwoAxis(
+ ellpsMap.set(IdentifiedObject::NAME_KEY,
+ proj_ellps[i].name),
+ Length(a_iter), Length(b_iter))
+ .as_nullable();
+ } else {
+ assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0);
+ const double rf_iter =
+ c_locale_stod(proj_ellps[i].ell + 3);
+ ellipsoid =
+ Ellipsoid::createFlattenedSphere(
+ ellpsMap.set(IdentifiedObject::NAME_KEY,
+ proj_ellps[i].name),
+ Length(a_iter), Scale(rf_iter))
+ .as_nullable();
+ }
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty()
+ ? std::string("Unknown based on ") +
+ proj_ellps[i].name +
+ " ellipsoid"
+ : title),
+ NN_NO_CHECK(ellipsoid), optionalEmptyString, pm);
+ }
+ }
+ throw ParsingException("unknown ellipsoid " + ellpsStr);
+ }
+ }();
+ if (!numericParamPresent) {
+ return l_datum;
+ }
+ a = l_datum->ellipsoid()->semiMajorAxis().getSIValue();
+ if (l_datum->ellipsoid()->semiMinorAxis().has_value()) {
+ b = l_datum->ellipsoid()->semiMinorAxis()->getSIValue();
+ } else {
+ rf = l_datum->ellipsoid()->computedInverseFlattening();
+ }
+ }
+
+ if (!aStr.empty()) {
+ try {
+ a = c_locale_stod(aStr);
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("Invalid a value");
+ }
+ }
+
+ if (a > 0 && (b > 0 || !bStr.empty())) {
+ if (!bStr.empty()) {
+ try {
+ b = c_locale_stod(bStr);
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("Invalid b value");
+ }
+ }
+ auto ellipsoid =
+ Ellipsoid::createTwoAxis(createMapWithUnknownName(), Length(a),
+ Length(b), guessBodyName(a))
+ ->identify();
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title.c_str()),
+ ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
+ }
+
+ else if (a > 0 && (rf >= 0 || !rfStr.empty())) {
+ if (!rfStr.empty()) {
+ try {
+ rf = c_locale_stod(rfStr);
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("Invalid rf value");
+ }
+ }
+ auto ellipsoid = Ellipsoid::createFlattenedSphere(
+ createMapWithUnknownName(), Length(a), Scale(rf),
+ guessBodyName(a))
+ ->identify();
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title.c_str()),
+ ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
+ }
+
+ else if (a > 0 && !fStr.empty()) {
+ double f;
+ try {
+ f = c_locale_stod(fStr);
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("Invalid f value");
+ }
+ auto ellipsoid = Ellipsoid::createFlattenedSphere(
+ createMapWithUnknownName(), Length(a),
+ Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
+ ->identify();
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title.c_str()),
+ ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
+ }
+
+ else if (a > 0 && !eStr.empty()) {
+ double e;
+ try {
+ e = c_locale_stod(eStr);
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("Invalid e value");
+ }
+ double alpha = asin(e); /* angular eccentricity */
+ double f = 1 - cos(alpha); /* = 1 - sqrt (1 - es); */
+ auto ellipsoid = Ellipsoid::createFlattenedSphere(
+ createMapWithUnknownName(), Length(a),
+ Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
+ ->identify();
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title.c_str()),
+ ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
+ }
+
+ else if (a > 0 && !esStr.empty()) {
+ double es;
+ try {
+ es = c_locale_stod(esStr);
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("Invalid es value");
+ }
+ double f = 1 - sqrt(1 - es);
+ auto ellipsoid = Ellipsoid::createFlattenedSphere(
+ createMapWithUnknownName(), Length(a),
+ Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
+ ->identify();
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title.c_str()),
+ ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
+ }
+
+ // If only a is specified, create a sphere
+ if (a > 0 && bStr.empty() && rfStr.empty() && eStr.empty() &&
+ esStr.empty()) {
+ auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(),
+ Length(a), guessBodyName(a));
+ return GeodeticReferenceFrame::create(
+ grfMap.set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title.c_str()),
+ ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
+ }
+
+ if (!bStr.empty() && aStr.empty()) {
+ throw ParsingException("b found, but a missing");
+ }
+
+ if (!rfStr.empty() && aStr.empty()) {
+ throw ParsingException("rf found, but a missing");
+ }
+
+ if (!fStr.empty() && aStr.empty()) {
+ throw ParsingException("f found, but a missing");
+ }
+
+ if (!eStr.empty() && aStr.empty()) {
+ throw ParsingException("e found, but a missing");
+ }
+
+ if (!esStr.empty() && aStr.empty()) {
+ throw ParsingException("es found, but a missing");
+ }
+
+ return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
+}
+
+// ---------------------------------------------------------------------------
+
+static const MeridianPtr nullMeridian{};
+
+static CoordinateSystemAxisNNPtr
+createAxis(const std::string &name, const std::string &abbreviation,
+ const AxisDirection &direction, const common::UnitOfMeasure &unit,
+ const MeridianPtr &meridian = nullMeridian) {
+ return CoordinateSystemAxis::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, name), abbreviation,
+ direction, unit, meridian);
+}
+
+std::vector<CoordinateSystemAxisNNPtr>
+PROJStringParser::Private::processAxisSwap(const Step &step,
+ const UnitOfMeasure &unit,
+ int iAxisSwap, AxisType axisType,
+ bool ignorePROJAxis) {
+ assert(iAxisSwap < 0 || ci_equal(steps_[iAxisSwap].name, "axisswap"));
+
+ const bool isGeographic = unit.type() == UnitOfMeasure::Type::ANGULAR;
+ const auto &eastName =
+ isGeographic ? AxisName::Longitude : AxisName::Easting;
+ const auto &eastAbbev =
+ isGeographic ? AxisAbbreviation::lon : AxisAbbreviation::E;
+ const auto &eastDir = isGeographic
+ ? AxisDirection::EAST
+ : (axisType == AxisType::NORTH_POLE)
+ ? AxisDirection::SOUTH
+ : (axisType == AxisType::SOUTH_POLE)
+ ? AxisDirection::NORTH
+ : AxisDirection::EAST;
+ CoordinateSystemAxisNNPtr east = createAxis(
+ eastName, eastAbbev, eastDir, unit,
+ (!isGeographic &&
+ (axisType == AxisType::NORTH_POLE || axisType == AxisType::SOUTH_POLE))
+ ? Meridian::create(Angle(90, UnitOfMeasure::DEGREE)).as_nullable()
+ : nullMeridian);
+
+ const auto &northName =
+ isGeographic ? AxisName::Latitude : AxisName::Northing;
+ const auto &northAbbev =
+ isGeographic ? AxisAbbreviation::lat : AxisAbbreviation::N;
+ const auto &northDir = isGeographic
+ ? AxisDirection::NORTH
+ : (axisType == AxisType::NORTH_POLE)
+ ? AxisDirection::SOUTH
+ : (axisType == AxisType::SOUTH_POLE)
+ ? AxisDirection::NORTH
+ : AxisDirection::NORTH;
+ CoordinateSystemAxisNNPtr north = createAxis(
+ northName, northAbbev, northDir, unit,
+ (!isGeographic && axisType == AxisType::NORTH_POLE)
+ ? Meridian::create(Angle(180, UnitOfMeasure::DEGREE)).as_nullable()
+ : (!isGeographic && axisType == AxisType::SOUTH_POLE)
+ ? Meridian::create(Angle(0, UnitOfMeasure::DEGREE))
+ .as_nullable()
+ : nullMeridian);
+
+ CoordinateSystemAxisNNPtr west =
+ createAxis(isGeographic ? AxisName::Longitude : AxisName::Westing,
+ isGeographic ? AxisAbbreviation::lon : std::string(),
+ AxisDirection::WEST, unit);
+
+ CoordinateSystemAxisNNPtr south =
+ createAxis(isGeographic ? AxisName::Latitude : AxisName::Southing,
+ isGeographic ? AxisAbbreviation::lat : std::string(),
+ AxisDirection::SOUTH, unit);
+
+ std::vector<CoordinateSystemAxisNNPtr> axis{east, north};
+
+ const auto &axisStr = getParamValue(step, "axis");
+ if (!ignorePROJAxis && !axisStr.empty()) {
+ if (axisStr.size() == 3) {
+ for (int i = 0; i < 2; i++) {
+ if (axisStr[i] == 'n') {
+ axis[i] = north;
+ } else if (axisStr[i] == 's') {
+ axis[i] = south;
+ } else if (axisStr[i] == 'e') {
+ axis[i] = east;
+ } else if (axisStr[i] == 'w') {
+ axis[i] = west;
+ } else {
+ throw ParsingException("Unhandled axis=" + axisStr);
+ }
+ }
+ } else {
+ throw ParsingException("Unhandled axis=" + axisStr);
+ }
+ } else if (iAxisSwap >= 0) {
+ const auto &stepAxisSwap = steps_[iAxisSwap];
+ const auto &orderStr = getParamValue(stepAxisSwap, "order");
+ auto orderTab = split(orderStr, ',');
+ if (orderTab.size() != 2) {
+ throw ParsingException("Unhandled order=" + orderStr);
+ }
+ if (stepAxisSwap.inverted) {
+ throw ParsingException("Unhandled +inv for +proj=axisswap");
+ }
+
+ for (size_t i = 0; i < 2; i++) {
+ if (orderTab[i] == "1") {
+ axis[i] = east;
+ } else if (orderTab[i] == "-1") {
+ axis[i] = west;
+ } else if (orderTab[i] == "2") {
+ axis[i] = north;
+ } else if (orderTab[i] == "-2") {
+ axis[i] = south;
+ } else {
+ throw ParsingException("Unhandled order=" + orderStr);
+ }
+ }
+ }
+ return axis;
+}
+
+// ---------------------------------------------------------------------------
+
+EllipsoidalCSNNPtr
+PROJStringParser::Private::buildEllipsoidalCS(int iStep, int iUnitConvert,
+ int iAxisSwap, bool ignoreVUnits,
+ bool ignorePROJAxis) {
+ const auto &step = steps_[iStep];
+ assert(iUnitConvert < 0 ||
+ ci_equal(steps_[iUnitConvert].name, "unitconvert"));
+
+ UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE;
+ if (iUnitConvert >= 0) {
+ const auto &stepUnitConvert = steps_[iUnitConvert];
+ const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
+ const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
+ if (stepUnitConvert.inverted) {
+ std::swap(xy_in, xy_out);
+ }
+ if (iUnitConvert < iStep) {
+ std::swap(xy_in, xy_out);
+ }
+ if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" ||
+ (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) {
+ throw ParsingException("unhandled values for xy_in and/or xy_out");
+ }
+ if (*xy_out == "rad") {
+ angularUnit = UnitOfMeasure::RADIAN;
+ } else if (*xy_out == "grad") {
+ angularUnit = UnitOfMeasure::GRAD;
+ }
+ }
+
+ std::vector<CoordinateSystemAxisNNPtr> axis = processAxisSwap(
+ step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis);
+ CoordinateSystemAxisNNPtr up = CoordinateSystemAxis::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY,
+ AxisName::Ellipsoidal_height),
+ AxisAbbreviation::h, AxisDirection::UP,
+ buildUnit(step, "vunits", "vto_meter"));
+
+ return (!ignoreVUnits && !hasParamValue(step, "geoidgrids") &&
+ (hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter")))
+ ? EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1], up)
+ : EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1]);
+}
+
+// ---------------------------------------------------------------------------
+
+static double getNumericValue(const std::string &paramValue,
+ bool *pHasError = nullptr) {
+ try {
+ double value = c_locale_stod(paramValue);
+ if (pHasError)
+ *pHasError = false;
+ return value;
+ } catch (const std::invalid_argument &) {
+ if (pHasError)
+ *pHasError = true;
+ return 0.0;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+GeographicCRSNNPtr
+PROJStringParser::Private::buildGeographicCRS(int iStep, int iUnitConvert,
+ int iAxisSwap, bool ignoreVUnits,
+ bool ignorePROJAxis) {
+ const auto &step = steps_[iStep];
+
+ const bool l_isGeographicStep = isGeographicStep(step.name);
+ const auto &title = l_isGeographicStep ? title_ : emptyString;
+
+ auto datum = buildDatum(step, title);
+
+ auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title);
+ if (l_isGeographicStep &&
+ (hasParamValue(step, "wktext") ||
+ hasParamValue(step, "lon_wrap") | hasParamValue(step, "geoc") ||
+ getNumericValue(getParamValue(step, "lon_0")) != 0.0)) {
+ props.set("EXTENSION_PROJ4", projString_);
+ }
+
+ return GeographicCRS::create(
+ props, datum, buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap,
+ ignoreVUnits, ignorePROJAxis));
+}
+
+// ---------------------------------------------------------------------------
+
+GeodeticCRSNNPtr
+PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) {
+ const auto &step = steps_[iStep];
+
+ assert(isGeocentricStep(step.name));
+ assert(iUnitConvert < 0 ||
+ ci_equal(steps_[iUnitConvert].name, "unitconvert"));
+
+ const auto &title = title_;
+
+ auto datum = buildDatum(step, title);
+
+ UnitOfMeasure unit = UnitOfMeasure::METRE;
+ if (iUnitConvert >= 0) {
+ const auto &stepUnitConvert = steps_[iUnitConvert];
+ const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
+ const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
+ const std::string *z_in = &getParamValue(stepUnitConvert, "z_in");
+ const std::string *z_out = &getParamValue(stepUnitConvert, "z_out");
+ if (stepUnitConvert.inverted) {
+ std::swap(xy_in, xy_out);
+ std::swap(z_in, z_out);
+ }
+ if (xy_in->empty() || xy_out->empty() || *xy_in != "m" ||
+ *z_in != "m" || *xy_out != *z_out) {
+ throw ParsingException(
+ "unhandled values for xy_in, z_in, xy_out or z_out");
+ }
+
+ const LinearUnitDesc *unitsMatch = nullptr;
+ try {
+ double to_meter_value = c_locale_stod(*xy_out);
+ unitsMatch = getLinearUnits(to_meter_value);
+ if (unitsMatch == nullptr) {
+ unit = _buildUnit(to_meter_value);
+ }
+ } catch (const std::invalid_argument &) {
+ unitsMatch = getLinearUnits(*xy_out);
+ if (!unitsMatch) {
+ throw ParsingException(
+ "unhandled values for xy_in, z_in, xy_out or z_out");
+ }
+ unit = _buildUnit(unitsMatch);
+ }
+ }
+
+ auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title);
+ if (hasParamValue(step, "wktext")) {
+ props.set("EXTENSION_PROJ4", projString_);
+ }
+
+ auto cs = CartesianCS::createGeocentric(unit);
+ return GeodeticCRS::create(props, datum, cs);
+}
+
+// ---------------------------------------------------------------------------
+
+CRSNNPtr
+PROJStringParser::Private::buildBoundOrCompoundCRSIfNeeded(int iStep,
+ CRSNNPtr crs) {
+ const auto &step = steps_[iStep];
+ const auto &towgs84 = getParamValue(step, "towgs84");
+ if (!towgs84.empty()) {
+ std::vector<double> towgs84Values;
+ const auto tokens = split(towgs84, ',');
+ for (const auto &str : tokens) {
+ try {
+ towgs84Values.push_back(c_locale_stod(str));
+ } catch (const std::invalid_argument &) {
+ throw ParsingException("Non numerical value in towgs84 clause");
+ }
+ }
+ crs = BoundCRS::createFromTOWGS84(crs, towgs84Values);
+ }
+
+ const auto &nadgrids = getParamValue(step, "nadgrids");
+ if (!nadgrids.empty()) {
+ crs = BoundCRS::createFromNadgrids(crs, nadgrids);
+ }
+
+ const auto &geoidgrids = getParamValue(step, "geoidgrids");
+ if (!geoidgrids.empty()) {
+ auto vdatum =
+ VerticalReferenceFrame::create(createMapWithUnknownName());
+
+ const UnitOfMeasure unit = buildUnit(step, "vunits", "vto_meter");
+
+ auto vcrs =
+ VerticalCRS::create(createMapWithUnknownName(), vdatum,
+ VerticalCS::createGravityRelatedHeight(unit));
+
+ auto transformation =
+ Transformation::createGravityRelatedHeightToGeographic3D(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "unknown to WGS84 ellipsoidal height"),
+ crs, GeographicCRS::EPSG_4979, geoidgrids,
+ std::vector<PositionalAccuracyNNPtr>());
+ auto boundvcrs =
+ BoundCRS::create(vcrs, GeographicCRS::EPSG_4979, transformation);
+
+ crs = CompoundCRS::create(createMapWithUnknownName(),
+ std::vector<CRSNNPtr>{crs, boundvcrs});
+ }
+
+ return crs;
+}
+
+// ---------------------------------------------------------------------------
+
+static double getAngularValue(const std::string &paramValue,
+ bool *pHasError = nullptr) {
+ char *endptr = nullptr;
+ double value = dmstor(paramValue.c_str(), &endptr) * RAD_TO_DEG;
+ if (value == HUGE_VAL || endptr != paramValue.c_str() + paramValue.size()) {
+ if (pHasError)
+ *pHasError = true;
+ return 0.0;
+ }
+ if (pHasError)
+ *pHasError = false;
+ return value;
+}
+
+// ---------------------------------------------------------------------------
+
+CRSNNPtr PROJStringParser::Private::buildProjectedCRS(
+ int iStep, GeographicCRSNNPtr geogCRS, int iUnitConvert, int iAxisSwap) {
+ auto &step = steps_[iStep];
+ auto mappings = getMappingsFromPROJName(step.name);
+ const MethodMapping *mapping = mappings.empty() ? nullptr : mappings[0];
+
+ assert(isProjectedStep(step.name));
+ assert(iUnitConvert < 0 ||
+ ci_equal(steps_[iUnitConvert].name, "unitconvert"));
+
+ const auto &title = title_;
+
+ if (!buildPrimeMeridian(step)->longitude()._isEquivalentTo(
+ geogCRS->primeMeridian()->longitude(),
+ util::IComparable::Criterion::EQUIVALENT)) {
+ throw ParsingException("inconsistent pm values between projectedCRS "
+ "and its base geographicalCRS");
+ }
+
+ auto axisType = AxisType::REGULAR;
+
+ if (step.name == "tmerc" &&
+ ((getParamValue(step, "axis") == "wsu" && iAxisSwap < 0) ||
+ (iAxisSwap > 0 &&
+ getParamValue(steps_[iAxisSwap], "order") == "-1,-2"))) {
+ mapping =
+ getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED);
+ } else if (step.name == "etmerc") {
+ mapping = getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR);
+ } else if (step.name == "lcc") {
+ const auto &lat_0 = getParamValue(step, "lat_0");
+ const auto &lat_1 = getParamValue(step, "lat_1");
+ const auto &lat_2 = getParamValue(step, "lat_2");
+ const auto &k = getParamValueK(step);
+ if (lat_2.empty() && !lat_0.empty() && !lat_1.empty() &&
+ getAngularValue(lat_0) == getAngularValue(lat_1)) {
+ mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ } else if (!k.empty() && getNumericValue(k) != 1.0) {
+ mapping = getMapping(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN);
+ } else {
+ mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
+ }
+ } else if (step.name == "aeqd" && hasParamValue(step, "guam")) {
+ mapping = getMapping(EPSG_CODE_METHOD_GUAM_PROJECTION);
+ } else if (step.name == "cea" && !geogCRS->ellipsoid()->isSphere()) {
+ mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA);
+ } else if (step.name == "geos" && getParamValue(step, "sweep") == "x") {
+ mapping =
+ getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X);
+ } else if (step.name == "geos") {
+ mapping =
+ getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y);
+ } else if (step.name == "omerc") {
+ if (hasParamValue(step, "no_rot")) {
+ mapping = nullptr;
+ } else if (hasParamValue(step, "no_uoff") ||
+ hasParamValue(step, "no_off")) {
+ mapping =
+ getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A);
+ } else if (hasParamValue(step, "lat_1") &&
+ hasParamValue(step, "lon_1") &&
+ hasParamValue(step, "lat_2") &&
+ hasParamValue(step, "lon_2")) {
+ mapping = getMapping(
+ PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN);
+ } else {
+ mapping =
+ getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B);
+ }
+ } else if (step.name == "somerc") {
+ mapping =
+ getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B);
+ step.paramValues.emplace_back(Step::KeyValue("alpha", "90"));
+ step.paramValues.emplace_back(Step::KeyValue("gamma", "90"));
+ step.paramValues.emplace_back(
+ Step::KeyValue("lonc", getParamValue(step, "lon_0")));
+ } else if (step.name == "krovak" &&
+ ((getParamValue(step, "axis") == "swu" && iAxisSwap < 0) ||
+ (iAxisSwap > 0 &&
+ getParamValue(steps_[iAxisSwap], "order") == "-2,-1"))) {
+ mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
+ } else if (step.name == "merc") {
+ if (hasParamValue(step, "a") && hasParamValue(step, "b") &&
+ getParamValue(step, "a") == getParamValue(step, "b") &&
+ (!hasParamValue(step, "lat_ts") ||
+ getAngularValue(getParamValue(step, "lat_ts")) == 0.0) &&
+ getNumericValue(getParamValueK(step)) == 1.0 &&
+ getParamValue(step, "nadgrids") == "@null") {
+ mapping = getMapping(
+ EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR);
+ for (size_t i = 0; i < step.paramValues.size(); ++i) {
+ if (ci_equal(step.paramValues[i].key, "nadgrids")) {
+ step.paramValues.erase(step.paramValues.begin() + i);
+ break;
+ }
+ }
+ } else if (hasParamValue(step, "lat_ts")) {
+ mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
+ } else {
+ mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A);
+ }
+ } else if (step.name == "stere") {
+ if (hasParamValue(step, "lat_0") &&
+ std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) -
+ 90.0) < 1e-10) {
+ const double lat_0 = getAngularValue(getParamValue(step, "lat_0"));
+ if (lat_0 > 0) {
+ axisType = AxisType::NORTH_POLE;
+ } else {
+ axisType = AxisType::SOUTH_POLE;
+ }
+ const auto &lat_ts = getParamValue(step, "lat_ts");
+ const auto &k = getParamValueK(step);
+ if (!lat_ts.empty() &&
+ std::fabs(getAngularValue(lat_ts) - lat_0) > 1e-10 &&
+ !k.empty() && std::fabs(getNumericValue(k) - 1) > 1e-10) {
+ throw ParsingException("lat_ts != lat_0 and k != 1 not "
+ "supported for Polar Stereographic");
+ }
+ if (!lat_ts.empty() &&
+ (k.empty() || std::fabs(getNumericValue(k) - 1) < 1e-10)) {
+ mapping =
+ getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B);
+ } else {
+ mapping =
+ getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A);
+ }
+ } else {
+ mapping = getMapping(PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC);
+ }
+ } else if (step.name == "laea") {
+ if (hasParamValue(step, "lat_0") &&
+ std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) -
+ 90.0) < 1e-10) {
+ const double lat_0 = getAngularValue(getParamValue(step, "lat_0"));
+ if (lat_0 > 0) {
+ axisType = AxisType::NORTH_POLE;
+ } else {
+ axisType = AxisType::SOUTH_POLE;
+ }
+ }
+ if (geogCRS->ellipsoid()->isSphere()) {
+ mapping = getMapping(
+ EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL);
+ }
+ } else if (step.name == "eqc") {
+ if (geogCRS->ellipsoid()->isSphere()) {
+ mapping =
+ getMapping(EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL);
+ }
+ }
+
+ UnitOfMeasure unit = buildUnit(step, "units", "to_meter");
+ if (iUnitConvert >= 0) {
+ const auto &stepUnitConvert = steps_[iUnitConvert];
+ const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
+ const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
+ if (stepUnitConvert.inverted) {
+ std::swap(xy_in, xy_out);
+ }
+ if (xy_in->empty() || xy_out->empty() || *xy_in != "m") {
+ if (step.name != "ob_tran") {
+ throw ParsingException(
+ "unhandled values for xy_in and/or xy_out");
+ }
+ }
+
+ const LinearUnitDesc *unitsMatch = nullptr;
+ try {
+ double to_meter_value = c_locale_stod(*xy_out);
+ unitsMatch = getLinearUnits(to_meter_value);
+ if (unitsMatch == nullptr) {
+ unit = _buildUnit(to_meter_value);
+ }
+ } catch (const std::invalid_argument &) {
+ unitsMatch = getLinearUnits(*xy_out);
+ if (!unitsMatch) {
+ if (step.name != "ob_tran") {
+ throw ParsingException(
+ "unhandled values for xy_in and/or xy_out");
+ }
+ } else {
+ unit = _buildUnit(unitsMatch);
+ }
+ }
+ }
+
+ ConversionPtr conv;
+
+ auto mapWithUnknownName = createMapWithUnknownName();
+
+ if (step.name == "utm") {
+ const int zone = std::atoi(getParamValue(step, "zone").c_str());
+ const bool north = !hasParamValue(step, "south");
+ conv =
+ Conversion::createUTM(emptyPropertyMap, zone, north).as_nullable();
+ } else if (mapping) {
+
+ auto methodMap =
+ PropertyMap().set(IdentifiedObject::NAME_KEY, mapping->wkt2_name);
+ if (mapping->epsg_code) {
+ methodMap.set(Identifier::CODESPACE_KEY, Identifier::EPSG)
+ .set(Identifier::CODE_KEY, mapping->epsg_code);
+ }
+ std::vector<OperationParameterNNPtr> parameters;
+ std::vector<ParameterValueNNPtr> values;
+ for (int i = 0; mapping->params[i] != nullptr; i++) {
+ const auto *param = mapping->params[i];
+ std::string proj_name(param->proj_name ? param->proj_name : "");
+ const std::string *paramValue =
+ (proj_name == "k" || proj_name == "k_0")
+ ? &getParamValueK(step)
+ : !proj_name.empty() ? &getParamValue(step, proj_name)
+ : &emptyString;
+ double value = 0;
+ if (!paramValue->empty()) {
+ bool hasError = false;
+ if (param->unit_type == UnitOfMeasure::Type::ANGULAR) {
+ value = getAngularValue(*paramValue, &hasError);
+ } else {
+ value = getNumericValue(*paramValue, &hasError);
+ }
+ if (hasError) {
+ throw ParsingException("invalid value for " + proj_name);
+ }
+
+ } else if (param->unit_type == UnitOfMeasure::Type::SCALE) {
+ value = 1;
+ } else {
+ // For omerc, if gamma is missing, the default value is
+ // alpha
+ if (step.name == "omerc" && proj_name == "gamma") {
+ paramValue = &getParamValue(step, "alpha");
+ if (!paramValue->empty()) {
+ value = getAngularValue(*paramValue);
+ }
+ } else if (step.name == "krovak") {
+ if (param->epsg_code ==
+ EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS) {
+ value = 30.28813975277777776;
+ } else if (
+ param->epsg_code ==
+ EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL) {
+ value = 78.5;
+ }
+ }
+ }
+
+ PropertyMap propertiesParameter;
+ propertiesParameter.set(IdentifiedObject::NAME_KEY,
+ param->wkt2_name);
+ if (param->epsg_code) {
+ propertiesParameter.set(Identifier::CODE_KEY, param->epsg_code);
+ propertiesParameter.set(Identifier::CODESPACE_KEY,
+ Identifier::EPSG);
+ }
+ parameters.push_back(
+ OperationParameter::create(propertiesParameter));
+ // In PROJ convention, angular parameters are always in degree
+ // and linear parameters always in metre.
+ double valRounded =
+ param->unit_type == UnitOfMeasure::Type::LINEAR
+ ? Length(value, UnitOfMeasure::METRE).convertToUnit(unit)
+ : value;
+ if (std::fabs(valRounded - std::round(valRounded)) < 1e-8) {
+ valRounded = std::round(valRounded);
+ }
+ values.push_back(ParameterValue::create(Measure(
+ valRounded,
+ param->unit_type == UnitOfMeasure::Type::ANGULAR
+ ? UnitOfMeasure::DEGREE
+ : param->unit_type == UnitOfMeasure::Type::LINEAR
+ ? unit
+ : param->unit_type == UnitOfMeasure::Type::SCALE
+ ? UnitOfMeasure::SCALE_UNITY
+ : UnitOfMeasure::NONE)));
+ }
+
+ if (step.name == "etmerc") {
+ methodMap.set("proj_method", "etmerc");
+ }
+
+ conv = Conversion::create(mapWithUnknownName, methodMap, parameters,
+ values)
+ .as_nullable();
+ } else {
+ std::vector<OperationParameterNNPtr> parameters;
+ std::vector<ParameterValueNNPtr> values;
+ std::string methodName = "PROJ " + step.name;
+ for (const auto &param : step.paramValues) {
+ if (param.key == "wktext" || param.key == "no_defs" ||
+ param.key == "datum" || param.key == "ellps" ||
+ param.key == "a" || param.key == "b" || param.key == "R" ||
+ param.key == "towgs84" || param.key == "nadgrids" ||
+ param.key == "geoidgrids" || param.key == "units" ||
+ param.key == "to_meter" || param.key == "vunits" ||
+ param.key == "vto_meter") {
+ continue;
+ }
+ if (param.value.empty()) {
+ methodName += " " + param.key;
+ } else if (param.key == "o_proj") {
+ methodName += " " + param.key + "=" + param.value;
+ } else {
+ parameters.push_back(OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, param.key)));
+ bool hasError = false;
+ if (param.key == "x_0" || param.key == "y_0") {
+ double value = getNumericValue(param.value, &hasError);
+ values.push_back(ParameterValue::create(
+ Measure(value, UnitOfMeasure::METRE)));
+ } else if (param.key == "k" || param.key == "k_0") {
+ double value = getNumericValue(param.value, &hasError);
+ values.push_back(ParameterValue::create(
+ Measure(value, UnitOfMeasure::SCALE_UNITY)));
+ } else {
+ double value = getAngularValue(param.value, &hasError);
+ values.push_back(ParameterValue::create(
+ Measure(value, UnitOfMeasure::DEGREE)));
+ }
+ if (hasError) {
+ throw ParsingException("invalid value for " + param.key);
+ }
+ }
+ }
+ conv = Conversion::create(
+ mapWithUnknownName,
+ PropertyMap().set(IdentifiedObject::NAME_KEY, methodName),
+ parameters, values)
+ .as_nullable();
+
+ if (methodName == "PROJ ob_tran o_proj=longlat") {
+ return DerivedGeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
+ geogCRS, NN_NO_CHECK(conv),
+ buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, false,
+ false));
+ }
+ }
+
+ std::vector<CoordinateSystemAxisNNPtr> axis =
+ processAxisSwap(step, unit, iAxisSwap, axisType, false);
+
+ auto cs = CartesianCS::create(emptyPropertyMap, axis[0], axis[1]);
+
+ auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
+ title.empty() ? "unknown" : title);
+
+ if (hasParamValue(step, "wktext")) {
+ props.set("EXTENSION_PROJ4", projString_);
+ }
+
+ CRSNNPtr crs = ProjectedCRS::create(props, geogCRS, NN_NO_CHECK(conv), cs);
+
+ if (!hasParamValue(step, "geoidgrids") &&
+ (hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter"))) {
+ auto vdatum = VerticalReferenceFrame::create(mapWithUnknownName);
+
+ const UnitOfMeasure vunit = buildUnit(step, "vunits", "vto_meter");
+
+ auto vcrs =
+ VerticalCRS::create(mapWithUnknownName, vdatum,
+ VerticalCS::createGravityRelatedHeight(vunit));
+
+ crs = CompoundCRS::create(mapWithUnknownName,
+ std::vector<CRSNNPtr>{crs, vcrs});
+ }
+
+ return crs;
+}
+
+// ---------------------------------------------------------------------------
+
+static bool isDatumDefiningParam(const std::string &param) {
+ return (param == "datum" || param == "ellps" || param == "a" ||
+ param == "b" || param == "rf" || param == "f" || param == "R");
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr PROJStringParser::Private::buildHelmertTransformation(
+ int iStep, int iFirstAxisSwap, int iFirstUnitConvert, int iFirstGeogStep,
+ int iSecondGeogStep, int iSecondAxisSwap, int iSecondUnitConvert) {
+ auto &step = steps_[iStep];
+ auto datum = buildDatum(step, std::string());
+ auto cs = CartesianCS::createGeocentric(UnitOfMeasure::METRE);
+
+ auto mapWithUnknownName = createMapWithUnknownName();
+
+ auto sourceCRS =
+ iFirstGeogStep >= 0
+ ? util::nn_static_pointer_cast<crs::CRS>(
+ buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert,
+ iFirstAxisSwap, true, false))
+ : util::nn_static_pointer_cast<crs::CRS>(
+ GeodeticCRS::create(mapWithUnknownName, datum, cs));
+ auto targetCRS =
+ iSecondGeogStep >= 0
+ ? util::nn_static_pointer_cast<crs::CRS>(
+ buildGeographicCRS(iSecondGeogStep, iSecondUnitConvert,
+ iSecondAxisSwap, true, false))
+ : util::nn_static_pointer_cast<crs::CRS>(
+ GeodeticCRS::create(mapWithUnknownName, datum, cs));
+
+ double x = 0;
+ double y = 0;
+ double z = 0;
+ double rx = 0;
+ double ry = 0;
+ double rz = 0;
+ double s = 0;
+ double dx = 0;
+ double dy = 0;
+ double dz = 0;
+ double drx = 0;
+ double dry = 0;
+ double drz = 0;
+ double ds = 0;
+ double t_epoch = 0;
+ bool rotationTerms = false;
+ bool timeDependent = false;
+ bool conventionFound = false;
+ bool positionVectorConvention = false;
+
+ struct Params {
+ double *pValue;
+ const char *name;
+ bool *pPresent;
+ };
+ const Params knownParams[] = {
+ {&x, "x", nullptr},
+ {&y, "y", nullptr},
+ {&z, "z", nullptr},
+ {&rx, "rx", &rotationTerms},
+ {&ry, "ry", &rotationTerms},
+ {&rz, "rz", &rotationTerms},
+ {&s, "s", &rotationTerms},
+ {&dx, "dx", &timeDependent},
+ {&dy, "dy", &timeDependent},
+ {&dz, "dz", &timeDependent},
+ {&drx, "drx", &timeDependent},
+ {&dry, "dry", &timeDependent},
+ {&drz, "drz", &timeDependent},
+ {&ds, "ds", &timeDependent},
+ {&t_epoch, "t_epoch", &timeDependent},
+ {nullptr, "exact", nullptr},
+ };
+
+ for (const auto &param : step.paramValues) {
+ if (isDatumDefiningParam(param.key)) {
+ continue;
+ }
+ if (param.key == "convention") {
+ if (param.value == "position_vector") {
+ positionVectorConvention = true;
+ conventionFound = true;
+ } else if (param.value == "coordinate_frame") {
+ positionVectorConvention = false;
+ conventionFound = true;
+ } else {
+ throw ParsingException("unsupported convention");
+ }
+ } else {
+ bool found = false;
+ for (auto &&knownParam : knownParams) {
+ if (param.key == knownParam.name) {
+ found = true;
+ if (knownParam.pValue)
+ *(knownParam.pValue) = getNumericValue(param.value);
+ if (knownParam.pPresent)
+ *(knownParam.pPresent) = true;
+ break;
+ }
+ }
+ if (!found) {
+ throw ParsingException("unsupported keyword for Helmert: " +
+ param.key);
+ }
+ }
+ }
+
+ rotationTerms |= timeDependent;
+ if (rotationTerms && !conventionFound) {
+ throw ParsingException("missing convention");
+ }
+
+ std::vector<metadata::PositionalAccuracyNNPtr> emptyAccuracies;
+
+ auto transf = ([&]() {
+ if (!rotationTerms) {
+ return Transformation::createGeocentricTranslations(
+ mapWithUnknownName, sourceCRS, targetCRS, x, y, z,
+ emptyAccuracies);
+ } else if (positionVectorConvention) {
+ if (timeDependent) {
+ return Transformation::createTimeDependentPositionVector(
+ mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, ry,
+ rz, s, dx, dy, dz, drx, dry, drz, ds, t_epoch,
+ emptyAccuracies);
+ } else {
+ return Transformation::createPositionVector(
+ mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, ry,
+ rz, s, emptyAccuracies);
+ }
+ } else {
+ if (timeDependent) {
+ return Transformation::
+ createTimeDependentCoordinateFrameRotation(
+ mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx,
+ ry, rz, s, dx, dy, dz, drx, dry, drz, ds, t_epoch,
+ emptyAccuracies);
+ } else {
+ return Transformation::createCoordinateFrameRotation(
+ mapWithUnknownName, sourceCRS, targetCRS, x, y, z, rx, ry,
+ rz, s, emptyAccuracies);
+ }
+ }
+ })();
+
+ if (step.inverted) {
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ transf->inverse());
+ } else {
+ return util::nn_static_pointer_cast<CoordinateOperation>(transf);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+CoordinateOperationNNPtr
+PROJStringParser::Private::buildMolodenskyTransformation(
+ int iStep, int iFirstAxisSwap, int iFirstUnitConvert, int iFirstGeogStep,
+ int iSecondGeogStep, int iSecondAxisSwap, int iSecondUnitConvert) {
+ auto &step = steps_[iStep];
+
+ double dx = 0;
+ double dy = 0;
+ double dz = 0;
+ double da = 0;
+ double df = 0;
+
+ struct Params {
+ double *pValue;
+ const char *name;
+ };
+ const Params knownParams[] = {
+ {&dx, "dx"}, {&dy, "dy"}, {&dz, "dz"}, {&da, "da"}, {&df, "df"},
+ };
+ bool abridged = false;
+
+ for (const auto &param : step.paramValues) {
+ if (isDatumDefiningParam(param.key)) {
+ continue;
+ } else if (param.key == "abridged") {
+ abridged = true;
+ } else {
+ bool found = false;
+ for (auto &&knownParam : knownParams) {
+ if (param.key == knownParam.name) {
+ found = true;
+ if (knownParam.pValue)
+ *(knownParam.pValue) = getNumericValue(param.value);
+ break;
+ }
+ }
+ if (!found) {
+ throw ParsingException("unsupported keyword for Molodensky: " +
+ param.key);
+ }
+ }
+ }
+
+ auto datum = buildDatum(step, std::string());
+ auto sourceCRS = iFirstGeogStep >= 0
+ ? buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert,
+ iFirstAxisSwap, true, false)
+ : buildGeographicCRS(iStep, -1, -1, true, false);
+
+ const auto &ellps = sourceCRS->ellipsoid();
+ const double a = ellps->semiMajorAxis().getSIValue();
+ const double rf = ellps->computedInverseFlattening();
+ const double target_a = a + da;
+ const double target_rf = 1.0 / (1.0 / rf + df);
+
+ auto mapWithUnknownName = createMapWithUnknownName();
+
+ auto target_ellipsoid =
+ Ellipsoid::createFlattenedSphere(mapWithUnknownName, Length(target_a),
+ Scale(target_rf))
+ ->identify();
+ auto target_datum = GeodeticReferenceFrame::create(
+ mapWithUnknownName, target_ellipsoid, util::optional<std::string>(),
+ PrimeMeridian::GREENWICH);
+
+ auto targetCRS = util::nn_static_pointer_cast<crs::CRS>(
+ iSecondGeogStep >= 0
+ ? buildGeographicCRS(iSecondGeogStep, iSecondUnitConvert,
+ iSecondAxisSwap, true, false)
+ : GeographicCRS::create(mapWithUnknownName, target_datum,
+ EllipsoidalCS::createLongitudeLatitude(
+ UnitOfMeasure::DEGREE)));
+
+ auto sourceCRS_as_CRS = util::nn_static_pointer_cast<crs::CRS>(sourceCRS);
+
+ std::vector<metadata::PositionalAccuracyNNPtr> emptyAccuracies;
+
+ auto transf = ([&]() {
+ if (abridged) {
+ return Transformation::createAbridgedMolodensky(
+ mapWithUnknownName, sourceCRS_as_CRS, targetCRS, dx, dy, dz, da,
+ df, emptyAccuracies);
+ } else {
+ return Transformation::createMolodensky(
+ mapWithUnknownName, sourceCRS_as_CRS, targetCRS, dx, dy, dz, da,
+ df, emptyAccuracies);
+ }
+ })();
+
+ if (step.inverted) {
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ transf->inverse());
+ } else {
+ return util::nn_static_pointer_cast<CoordinateOperation>(transf);
+ }
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const metadata::ExtentPtr nullExtent{};
+
+static const metadata::ExtentPtr &getExtent(const crs::CRS *crs) {
+ const auto &domains = crs->domains();
+ if (!domains.empty()) {
+ return domains[0]->domainOfValidity();
+ }
+ return nullExtent;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** \brief Instanciate a sub-class of BaseObject from a PROJ string.
+ * @throw ParsingException
+ */
+BaseObjectNNPtr
+PROJStringParser::createFromPROJString(const std::string &projString) {
+ std::string vunits;
+ std::string vto_meter;
+
+ d->steps_.clear();
+ d->title_.clear();
+ d->globalParamValues_.clear();
+ d->projString_ = projString;
+ PROJStringSyntaxParser(projString, d->steps_, d->globalParamValues_,
+ d->title_, vunits, vto_meter);
+
+ if (d->steps_.empty()) {
+
+ if (!vunits.empty() || !vto_meter.empty()) {
+ Step fakeStep;
+ if (!vunits.empty()) {
+ fakeStep.paramValues.emplace_back(
+ Step::KeyValue("vunits", vunits));
+ }
+ if (!vto_meter.empty()) {
+ fakeStep.paramValues.emplace_back(
+ Step::KeyValue("vto_meter", vto_meter));
+ }
+ auto vdatum =
+ VerticalReferenceFrame::create(createMapWithUnknownName());
+ auto vcrs = VerticalCRS::create(
+ createMapWithUnknownName(), vdatum,
+ VerticalCS::createGravityRelatedHeight(
+ d->buildUnit(fakeStep, "vunits", "vto_meter")));
+ return vcrs;
+ }
+ }
+
+ if ((d->steps_.size() == 1 ||
+ (d->steps_.size() == 2 && d->steps_[1].name == "unitconvert")) &&
+ isGeocentricStep(d->steps_[0].name)) {
+ return d->buildBoundOrCompoundCRSIfNeeded(
+ 0, d->buildGeocentricCRS(0, (d->steps_.size() == 2 &&
+ d->steps_[1].name == "unitconvert")
+ ? 1
+ : -1));
+ }
+
+ // +init=xxxx:yyyy syntax
+ if (d->steps_.size() == 1 && d->steps_[0].isInit &&
+ d->steps_[0].paramValues.size() == 0) {
+
+ // Those used to come from a text init file
+ // We only support them in compatibility mode
+ const std::string &stepName = d->steps_[0].name;
+ if (ci_starts_with(stepName, "epsg:") ||
+ ci_starts_with(stepName, "IGNF:")) {
+ bool usePROJ4InitRules = d->usePROJ4InitRules_;
+ if (!usePROJ4InitRules) {
+ PJ_CONTEXT *ctx = proj_context_create();
+ if (ctx) {
+ usePROJ4InitRules = proj_context_get_use_proj4_init_rules(
+ ctx, FALSE) == TRUE;
+ proj_context_destroy(ctx);
+ }
+ }
+ if (!usePROJ4InitRules) {
+ throw ParsingException("init=epsg:/init=IGNF: syntax not "
+ "supported in non-PROJ4 emulation mode");
+ }
+
+ PJ_CONTEXT *ctx = proj_context_create();
+ char unused[256];
+ std::string initname(stepName);
+ initname.resize(initname.find(':'));
+ int file_found =
+ pj_find_file(ctx, initname.c_str(), unused, sizeof(unused));
+ proj_context_destroy(ctx);
+ if (!file_found) {
+ auto obj = createFromUserInput(stepName, d->dbContext_, true);
+ auto crs = dynamic_cast<CRS *>(obj.get());
+ if (crs) {
+ PropertyMap properties;
+ properties.set(IdentifiedObject::NAME_KEY, crs->nameStr());
+ const auto &extent = getExtent(crs);
+ if (extent) {
+ properties.set(
+ common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
+ NN_NO_CHECK(extent));
+ }
+ auto geogCRS = dynamic_cast<GeographicCRS *>(crs);
+ if (geogCRS) {
+ // Override with longitude latitude in radian
+ return GeographicCRS::create(
+ properties, geogCRS->datum(),
+ geogCRS->datumEnsemble(),
+ EllipsoidalCS::createLongitudeLatitude(
+ UnitOfMeasure::RADIAN));
+ }
+ auto projCRS = dynamic_cast<ProjectedCRS *>(crs);
+ if (projCRS) {
+ // Override with easting northing order
+ const auto &conv = projCRS->derivingConversionRef();
+ if (conv->method()->getEPSGCode() !=
+ EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
+ return ProjectedCRS::create(
+ properties, projCRS->baseCRS(), conv,
+ CartesianCS::createEastingNorthing(
+ projCRS->coordinateSystem()
+ ->axisList()[0]
+ ->unit()));
+ }
+ }
+ }
+ return obj;
+ }
+ }
+
+ paralist *init = pj_mkparam(("init=" + d->steps_[0].name).c_str());
+ if (!init) {
+ throw ParsingException("out of memory");
+ }
+ PJ_CONTEXT *ctx = proj_context_create();
+ if (!ctx) {
+ pj_dealloc(init);
+ throw ParsingException("out of memory");
+ }
+ paralist *list = pj_expand_init(ctx, init);
+ proj_context_destroy(ctx);
+ if (!list) {
+ pj_dealloc(init);
+ throw ParsingException("cannot expand " + projString);
+ }
+ std::string expanded;
+ bool first = true;
+ bool has_init_term = false;
+ for (auto t = list; t;) {
+ if (!expanded.empty()) {
+ expanded += ' ';
+ }
+ if (first) {
+ // first parameter is the init= itself
+ first = false;
+ } else if (starts_with(t->param, "init=")) {
+ has_init_term = true;
+ } else {
+ expanded += t->param;
+ }
+
+ auto n = t->next;
+ pj_dealloc(t);
+ t = n;
+ }
+
+ if (!has_init_term) {
+ return createFromPROJString(expanded);
+ }
+ }
+
+ int iFirstGeogStep = -1;
+ int iSecondGeogStep = -1;
+ int iProjStep = -1;
+ int iFirstUnitConvert = -1;
+ int iSecondUnitConvert = -1;
+ int iFirstAxisSwap = -1;
+ int iSecondAxisSwap = -1;
+ int iHelmert = -1;
+ int iFirstCart = -1;
+ int iSecondCart = -1;
+ int iMolodensky = -1;
+ bool unexpectedStructure = false;
+ for (int i = 0; i < static_cast<int>(d->steps_.size()); i++) {
+ const auto &stepName = d->steps_[i].name;
+ if (isGeographicStep(stepName)) {
+ if (iFirstGeogStep < 0) {
+ iFirstGeogStep = i;
+ } else if (iSecondGeogStep < 0) {
+ iSecondGeogStep = i;
+ } else {
+ unexpectedStructure = true;
+ break;
+ }
+ } else if (ci_equal(stepName, "unitconvert")) {
+ if (iFirstUnitConvert < 0) {
+ iFirstUnitConvert = i;
+ } else if (iSecondUnitConvert < 0) {
+ iSecondUnitConvert = i;
+ } else {
+ unexpectedStructure = true;
+ break;
+ }
+ } else if (ci_equal(stepName, "axisswap")) {
+ if (iFirstAxisSwap < 0) {
+ iFirstAxisSwap = i;
+ } else if (iSecondAxisSwap < 0) {
+ iSecondAxisSwap = i;
+ } else {
+ unexpectedStructure = true;
+ break;
+ }
+ } else if (stepName == "helmert") {
+ if (iHelmert >= 0) {
+ unexpectedStructure = true;
+ break;
+ }
+ iHelmert = i;
+ } else if (stepName == "cart") {
+ if (iFirstCart < 0) {
+ iFirstCart = i;
+ } else if (iSecondCart < 0) {
+ iSecondCart = i;
+ } else {
+ unexpectedStructure = true;
+ break;
+ }
+ } else if (stepName == "molodensky") {
+ if (iMolodensky >= 0) {
+ unexpectedStructure = true;
+ break;
+ }
+ iMolodensky = i;
+ } else if (isProjectedStep(stepName)) {
+ if (iProjStep >= 0) {
+ unexpectedStructure = true;
+ break;
+ }
+ iProjStep = i;
+ } else {
+ unexpectedStructure = true;
+ break;
+ }
+ }
+
+ if (!unexpectedStructure) {
+ if (iFirstGeogStep == 0 && iSecondGeogStep < 0 && iProjStep < 0 &&
+ iHelmert < 0 && iFirstCart < 0 && iMolodensky < 0 &&
+ (iFirstUnitConvert < 0 || iSecondUnitConvert < 0) &&
+ (iFirstAxisSwap < 0 || iSecondAxisSwap < 0)) {
+ const bool ignoreVUnits = false;
+ return d->buildBoundOrCompoundCRSIfNeeded(
+ 0, d->buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert,
+ iFirstAxisSwap, ignoreVUnits, false));
+ }
+ if (iProjStep >= 0 && !d->steps_[iProjStep].inverted &&
+ (iFirstGeogStep < 0 || iFirstGeogStep + 1 == iProjStep) &&
+ iMolodensky < 0 && iSecondGeogStep < 0 && iFirstCart < 0 &&
+ iHelmert < 0) {
+ if (iFirstGeogStep < 0)
+ iFirstGeogStep = iProjStep;
+ const bool ignoreVUnits = true;
+ return d->buildBoundOrCompoundCRSIfNeeded(
+ iProjStep,
+ d->buildProjectedCRS(
+ iProjStep,
+ d->buildGeographicCRS(
+ iFirstGeogStep,
+ iFirstUnitConvert < iFirstGeogStep ? iFirstUnitConvert
+ : -1,
+ iFirstAxisSwap < iFirstGeogStep ? iFirstAxisSwap : -1,
+ ignoreVUnits, true),
+ iFirstUnitConvert < iFirstGeogStep ? iSecondUnitConvert
+ : iFirstUnitConvert,
+ iFirstAxisSwap < iFirstGeogStep ? iSecondAxisSwap
+ : iFirstAxisSwap));
+ }
+ if (d->steps_.size() == 1 && iHelmert == 0) {
+ return d->buildHelmertTransformation(iHelmert);
+ }
+
+ if (iProjStep < 0 && iHelmert > 0 && iMolodensky < 0 &&
+ (iFirstGeogStep < 0 || iFirstGeogStep == iFirstCart - 1 ||
+ (iFirstGeogStep == iSecondCart + 1 && iSecondGeogStep < 0)) &&
+ iFirstCart == iHelmert - 1 && iSecondCart == iHelmert + 1 &&
+ (iSecondGeogStep < 0 || iSecondGeogStep == iSecondCart + 1) &&
+ !d->steps_[iFirstCart].inverted &&
+ d->steps_[iSecondCart].inverted && iFirstAxisSwap < iHelmert &&
+ iFirstUnitConvert < iHelmert &&
+ (iSecondAxisSwap < 0 || iSecondAxisSwap > iHelmert) &&
+ (iSecondUnitConvert < 0 || iSecondUnitConvert > iHelmert)) {
+ return d->buildHelmertTransformation(
+ iHelmert, iFirstAxisSwap, iFirstUnitConvert,
+ iFirstGeogStep >= 0 && iFirstGeogStep == iFirstCart - 1
+ ? iFirstGeogStep
+ : iFirstCart,
+ iFirstGeogStep == iSecondCart + 1
+ ? iFirstGeogStep
+ : iSecondGeogStep == iSecondCart + 1 ? iSecondGeogStep
+ : iSecondCart,
+ iSecondAxisSwap, iSecondUnitConvert);
+ }
+
+ if (d->steps_.size() == 1 && iMolodensky == 0) {
+ return d->buildMolodenskyTransformation(iMolodensky);
+ }
+
+ if (iProjStep < 0 && iHelmert < 0 && iMolodensky > 0 &&
+ (iFirstGeogStep < 0 || iFirstGeogStep == iMolodensky - 1 ||
+ (iFirstGeogStep == iMolodensky + 1 && iSecondGeogStep < 0)) &&
+ (iSecondGeogStep < 0 || iSecondGeogStep == iMolodensky + 1) &&
+ iFirstAxisSwap < iMolodensky && iFirstUnitConvert < iMolodensky &&
+ (iSecondAxisSwap < 0 || iSecondAxisSwap > iMolodensky) &&
+ (iSecondUnitConvert < 0 || iSecondUnitConvert > iMolodensky)) {
+ return d->buildMolodenskyTransformation(
+ iMolodensky, iFirstAxisSwap, iFirstUnitConvert,
+ iFirstGeogStep >= 0 && iFirstGeogStep == iMolodensky - 1
+ ? iFirstGeogStep
+ : iMolodensky,
+ iFirstGeogStep == iMolodensky + 1
+ ? iFirstGeogStep
+ : iSecondGeogStep == iMolodensky + 1 ? iSecondGeogStep
+ : iMolodensky,
+ iSecondAxisSwap, iSecondUnitConvert);
+ }
+ }
+
+ struct Logger {
+ std::string msg{};
+
+ // cppcheck-suppress functionStatic
+ void setMessage(const char *msgIn) noexcept {
+ try {
+ msg = msgIn;
+ } catch (const std::exception &) {
+ }
+ }
+
+ static void log(void *user_data, int level, const char *msg) {
+ if (level == PJ_LOG_ERROR) {
+ static_cast<Logger *>(user_data)->setMessage(msg);
+ }
+ }
+ };
+
+ // If the structure is not recognized, then try to instanciate the
+ // pipeline, and if successful, wrap it in a PROJBasedOperation
+ Logger logger;
+ auto pj_context = proj_context_create();
+ if (!pj_context) {
+ throw ParsingException("out of memory");
+ }
+ proj_log_func(pj_context, &logger, Logger::log);
+ proj_context_use_proj4_init_rules(pj_context, d->usePROJ4InitRules_);
+ auto pj = proj_create(pj_context, projString.c_str());
+ bool valid = pj != nullptr;
+ proj_destroy(pj);
+
+ if (!valid && logger.msg.empty()) {
+ logger.setMessage(proj_errno_string(proj_context_errno(pj_context)));
+ }
+
+ proj_context_destroy(pj_context);
+
+ if (!valid) {
+ throw ParsingException(logger.msg);
+ }
+
+ auto props = PropertyMap();
+ if (!d->title_.empty()) {
+ props.set(IdentifiedObject::NAME_KEY, d->title_);
+ }
+ return operation::SingleOperation::createPROJBased(props, projString,
+ nullptr, nullptr, {});
+}
+
+} // namespace io
+NS_PROJ_END