diff options
| author | Even Rouault <even.rouault@spatialys.com> | 2018-12-19 12:25:33 +0100 |
|---|---|---|
| committer | Even Rouault <even.rouault@spatialys.com> | 2018-12-26 10:08:54 +0100 |
| commit | e6de172371ea203f6393d745641d66c82b5b13e2 (patch) | |
| tree | 791fa07f431a2d1db6f6e813ab984db982587278 /src/io.cpp | |
| parent | ce8075076b4e4ffebd32afaba419e1d9ab27cd03 (diff) | |
| download | PROJ-e6de172371ea203f6393d745641d66c82b5b13e2.tar.gz PROJ-e6de172371ea203f6393d745641d66c82b5b13e2.zip | |
cpp conversion: move source files in apps/ iso19111/ conversions/ projections/ transformations/ tests/ subdirectories
Diffstat (limited to 'src/io.cpp')
| -rw-r--r-- | src/io.cpp | 7501 |
1 files changed, 0 insertions, 7501 deletions
diff --git a/src/io.cpp b/src/io.cpp deleted file mode 100644 index fe3680fb..00000000 --- a/src/io.cpp +++ /dev/null @@ -1,7501 +0,0 @@ -/****************************************************************************** - * - * 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> ¶ms) { - 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 ¶mName, - const UnitOfMeasure &defaultLinearUnit, - const UnitOfMeasure &defaultAngularUnit); - - void consumeParameters(const WKTNodeNNPtr &node, bool isAbridged, - std::vector<OperationParameterNNPtr> ¶meters, - 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 ¶mName, 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> ¶meters, - 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 ¶mValue = 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 ¶mName = - childNodeChildren[0]->GP()->value(); - unit = guessUnitForParameter( - paramName, defaultLinearUnit, defaultAngularUnit); - } - - if (isAbridged) { - const auto ¶mName = parameters.back()->nameStr(); - int paramEPSGCode = 0; - const auto ¶mIds = 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 ¶mValue = 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 ¶mValue = 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 ¶m : 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 ¶mValue : 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 ¶mValue : 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 ¶mValue : 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 ¶mName) { - 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 ¶mName, 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 ¶mName, 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 ¶mName, - const char *val) { - addParam(paramName, std::string(val)); -} - -// --------------------------------------------------------------------------- - -void PROJStringFormatter::addParam(const std::string ¶mName, - 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> ¶ms) { - 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 ¶m : 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 ¶mValue, - 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 ¶mValue, - 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 ¶m : 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 ¶m) { - 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 ¶m : 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 ¶m : 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 |
