1 /******************************************************************************
2  *
3  * Project:  PROJ
4  * Purpose:  ISO19111:2019 implementation
5  * Author:   Even Rouault <even dot rouault at spatialys dot com>
6  *
7  ******************************************************************************
8  * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  ****************************************************************************/
28 
29 #ifndef FROM_PROJ_CPP
30 #define FROM_PROJ_CPP
31 #endif
32 
33 #include <algorithm>
34 #include <cassert>
35 #include <cctype>
36 #include <cmath>
37 #include <cstring>
38 #include <list>
39 #include <locale>
40 #include <map>
41 #include <set>
42 #include <sstream> // std::istringstream
43 #include <string>
44 #include <utility>
45 #include <vector>
46 
47 #include "proj/common.hpp"
48 #include "proj/coordinateoperation.hpp"
49 #include "proj/coordinatesystem.hpp"
50 #include "proj/crs.hpp"
51 #include "proj/datum.hpp"
52 #include "proj/io.hpp"
53 #include "proj/metadata.hpp"
54 #include "proj/util.hpp"
55 
56 #include "operation/coordinateoperation_internal.hpp"
57 #include "operation/esriparammappings.hpp"
58 #include "operation/oputils.hpp"
59 #include "operation/parammappings.hpp"
60 
61 #include "proj/internal/coordinatesystem_internal.hpp"
62 #include "proj/internal/internal.hpp"
63 #include "proj/internal/io_internal.hpp"
64 
65 #include "proj/internal/include_nlohmann_json.hpp"
66 
67 #include "proj_constants.h"
68 
69 #include "proj_json_streaming_writer.hpp"
70 #include "wkt1_parser.h"
71 #include "wkt2_parser.h"
72 
73 // PROJ include order is sensitive
74 // clang-format off
75 #include "proj.h"
76 #include "proj_internal.h"
77 #include "proj_api.h"
78 // clang-format on
79 
80 using namespace NS_PROJ::common;
81 using namespace NS_PROJ::crs;
82 using namespace NS_PROJ::cs;
83 using namespace NS_PROJ::datum;
84 using namespace NS_PROJ::internal;
85 using namespace NS_PROJ::metadata;
86 using namespace NS_PROJ::operation;
87 using namespace NS_PROJ::util;
88 
89 using json = nlohmann::json;
90 
91 //! @cond Doxygen_Suppress
92 static const std::string emptyString{};
93 
94 // If changing that value, change it in data/projjson.schema.json as well
95 #define PROJJSON_CURRENT_VERSION                                               \
96     "https://proj.org/schemas/v0.2/projjson.schema.json"
97 //! @endcond
98 
99 #if 0
100 namespace dropbox{ namespace oxygen {
101 template<> nn<NS_PROJ::io::DatabaseContextPtr>::~nn() = default;
102 template<> nn<NS_PROJ::io::AuthorityFactoryPtr>::~nn() = default;
103 template<> nn<std::shared_ptr<NS_PROJ::io::IPROJStringExportable>>::~nn() = default;
104 template<> nn<std::unique_ptr<NS_PROJ::io::PROJStringFormatter, std::default_delete<NS_PROJ::io::PROJStringFormatter> > >::~nn() = default;
105 template<> nn<std::unique_ptr<NS_PROJ::io::WKTFormatter, std::default_delete<NS_PROJ::io::WKTFormatter> > >::~nn() = default;
106 template<> nn<std::unique_ptr<NS_PROJ::io::WKTNode, std::default_delete<NS_PROJ::io::WKTNode> > >::~nn() = default;
107 }}
108 #endif
109 
110 NS_PROJ_START
111 namespace io {
112 
113 // ---------------------------------------------------------------------------
114 
115 //! @cond Doxygen_Suppress
116 IWKTExportable::~IWKTExportable() = default;
117 
118 // ---------------------------------------------------------------------------
119 
exportToWKT(WKTFormatter * formatter) const120 std::string IWKTExportable::exportToWKT(WKTFormatter *formatter) const {
121     _exportToWKT(formatter);
122     return formatter->toString();
123 }
124 
125 //! @endcond
126 
127 // ---------------------------------------------------------------------------
128 
129 //! @cond Doxygen_Suppress
130 struct WKTFormatter::Private {
131     struct Params {
132         WKTFormatter::Convention convention_ = WKTFormatter::Convention::WKT2;
133         WKTFormatter::Version version_ = WKTFormatter::Version::WKT2;
134         bool multiLine_ = true;
135         bool strict_ = true;
136         int indentWidth_ = 4;
137         bool idOnTopLevelOnly_ = false;
138         bool outputAxisOrder_ = false;
139         bool primeMeridianOmittedIfGreenwich_ = false;
140         bool ellipsoidUnitOmittedIfMetre_ = false;
141         bool primeMeridianOrParameterUnitOmittedIfSameAsAxis_ = false;
142         bool forceUNITKeyword_ = false;
143         bool outputCSUnitOnlyOnceIfSame_ = false;
144         bool primeMeridianInDegree_ = false;
145         bool use2019Keywords_ = false;
146         bool useESRIDialect_ = false;
147         bool allowEllipsoidalHeightAsVerticalCRS_ = false;
148         OutputAxisRule outputAxis_ = WKTFormatter::OutputAxisRule::YES;
149     };
150     Params params_{};
151     DatabaseContextPtr dbContext_{};
152 
153     int indentLevel_ = 0;
154     int level_ = 0;
155     std::vector<bool> stackHasChild_{};
156     std::vector<bool> stackHasId_{false};
157     std::vector<bool> stackEmptyKeyword_{};
158     std::vector<bool> stackDisableUsage_{};
159     std::vector<bool> outputUnitStack_{true};
160     std::vector<bool> outputIdStack_{true};
161     std::vector<UnitOfMeasureNNPtr> axisLinearUnitStack_{
162         util::nn_make_shared<UnitOfMeasure>(UnitOfMeasure::METRE)};
163     std::vector<UnitOfMeasureNNPtr> axisAngularUnitStack_{
164         util::nn_make_shared<UnitOfMeasure>(UnitOfMeasure::DEGREE)};
165     bool abridgedTransformation_ = false;
166     bool useDerivingConversion_ = false;
167     std::vector<double> toWGS84Parameters_{};
168     std::string hDatumExtension_{};
169     std::string vDatumExtension_{};
170     std::vector<bool> inversionStack_{false};
171     std::string result_{};
172 
173     // cppcheck-suppress functionStatic
174     void addNewLine();
175     void addIndentation();
176     // cppcheck-suppress functionStatic
177     void startNewChild();
178 };
179 //! @endcond
180 
181 // ---------------------------------------------------------------------------
182 
183 /** \brief Constructs a new formatter.
184  *
185  * A formatter can be used only once (its internal state is mutated)
186  *
187  * Its default behavior can be adjusted with the different setters.
188  *
189  * @param convention WKT flavor. Defaults to Convention::WKT2
190  * @param dbContext Database context, to allow queries in it if needed.
191  * This is used for example for WKT1_ESRI output to do name substitutions.
192  *
193  * @return new formatter.
194  */
create(Convention convention,DatabaseContextPtr dbContext)195 WKTFormatterNNPtr WKTFormatter::create(Convention convention,
196                                        // cppcheck-suppress passedByValue
197                                        DatabaseContextPtr dbContext) {
198     auto ret = NN_NO_CHECK(WKTFormatter::make_unique<WKTFormatter>(convention));
199     ret->d->dbContext_ = dbContext;
200     return ret;
201 }
202 
203 // ---------------------------------------------------------------------------
204 
205 /** \brief Constructs a new formatter from another one.
206  *
207  * A formatter can be used only once (its internal state is mutated)
208  *
209  * Its default behavior can be adjusted with the different setters.
210  *
211  * @param other source formatter.
212  * @return new formatter.
213  */
create(const WKTFormatterNNPtr & other)214 WKTFormatterNNPtr WKTFormatter::create(const WKTFormatterNNPtr &other) {
215     auto f = create(other->d->params_.convention_, other->d->dbContext_);
216     f->d->params_ = other->d->params_;
217     return f;
218 }
219 
220 // ---------------------------------------------------------------------------
221 
222 //! @cond Doxygen_Suppress
223 WKTFormatter::~WKTFormatter() = default;
224 //! @endcond
225 
226 // ---------------------------------------------------------------------------
227 
228 /** \brief Whether to use multi line output or not. */
setMultiLine(bool multiLine)229 WKTFormatter &WKTFormatter::setMultiLine(bool multiLine) noexcept {
230     d->params_.multiLine_ = multiLine;
231     return *this;
232 }
233 
234 // ---------------------------------------------------------------------------
235 
236 /** \brief Set number of spaces for each indentation level (defaults to 4).
237  */
setIndentationWidth(int width)238 WKTFormatter &WKTFormatter::setIndentationWidth(int width) noexcept {
239     d->params_.indentWidth_ = width;
240     return *this;
241 }
242 
243 // ---------------------------------------------------------------------------
244 
245 /** \brief Set whether AXIS nodes should be output.
246  */
247 WKTFormatter &
setOutputAxis(OutputAxisRule outputAxisIn)248 WKTFormatter::setOutputAxis(OutputAxisRule outputAxisIn) noexcept {
249     d->params_.outputAxis_ = outputAxisIn;
250     return *this;
251 }
252 
253 // ---------------------------------------------------------------------------
254 
255 /** \brief Set whether the formatter should operate on strict more or not.
256  *
257  * The default is strict mode, in which case a FormattingException can be
258  * thrown.
259  * In non-strict mode, a Geographic 3D CRS can be for example exported as
260  * WKT1_GDAL with 3 axes, whereas this is normally not allowed.
261  */
setStrict(bool strictIn)262 WKTFormatter &WKTFormatter::setStrict(bool strictIn) noexcept {
263     d->params_.strict_ = strictIn;
264     return *this;
265 }
266 
267 // ---------------------------------------------------------------------------
268 
269 /** \brief Returns whether the formatter is in strict mode. */
isStrict() const270 bool WKTFormatter::isStrict() const noexcept { return d->params_.strict_; }
271 
272 // ---------------------------------------------------------------------------
273 
274 //! @cond Doxygen_Suppress
275 
276 /** \brief Set whether the formatter should export, in WKT1, a Geographic or
277  * Projected 3D CRS as a compound CRS whose vertical part represents an
278  * ellipsoidal height.
279  */
280 WKTFormatter &
setAllowEllipsoidalHeightAsVerticalCRS(bool allow)281 WKTFormatter::setAllowEllipsoidalHeightAsVerticalCRS(bool allow) noexcept {
282     d->params_.allowEllipsoidalHeightAsVerticalCRS_ = allow;
283     return *this;
284 }
285 
286 // ---------------------------------------------------------------------------
287 
288 /** \brief Return whether the formatter should export, in WKT1, a Geographic or
289  * Projected 3D CRS as a compound CRS whose vertical part represents an
290  * ellipsoidal height.
291  */
isAllowedEllipsoidalHeightAsVerticalCRS() const292 bool WKTFormatter::isAllowedEllipsoidalHeightAsVerticalCRS() const noexcept {
293     return d->params_.allowEllipsoidalHeightAsVerticalCRS_;
294 }
295 
296 //! @endcond
297 
298 // ---------------------------------------------------------------------------
299 
300 /** Returns the WKT string from the formatter. */
toString() const301 const std::string &WKTFormatter::toString() const {
302     if (d->indentLevel_ > 0 || d->level_ > 0) {
303         // For intermediary nodes, the formatter is in a inconsistent
304         // state.
305         throw FormattingException("toString() called on intermediate nodes");
306     }
307     if (d->axisLinearUnitStack_.size() != 1)
308         throw FormattingException(
309             "Unbalanced pushAxisLinearUnit() / popAxisLinearUnit()");
310     if (d->axisAngularUnitStack_.size() != 1)
311         throw FormattingException(
312             "Unbalanced pushAxisAngularUnit() / popAxisAngularUnit()");
313     if (d->outputIdStack_.size() != 1)
314         throw FormattingException("Unbalanced pushOutputId() / popOutputId()");
315     if (d->outputUnitStack_.size() != 1)
316         throw FormattingException(
317             "Unbalanced pushOutputUnit() / popOutputUnit()");
318     if (d->stackHasId_.size() != 1)
319         throw FormattingException("Unbalanced pushHasId() / popHasId()");
320     if (!d->stackDisableUsage_.empty())
321         throw FormattingException(
322             "Unbalanced pushDisableUsage() / popDisableUsage()");
323 
324     return d->result_;
325 }
326 
327 //! @cond Doxygen_Suppress
328 
329 // ---------------------------------------------------------------------------
330 
WKTFormatter(Convention convention)331 WKTFormatter::WKTFormatter(Convention convention)
332     : d(internal::make_unique<Private>()) {
333     d->params_.convention_ = convention;
334     switch (convention) {
335     case Convention::WKT2_2019:
336         d->params_.use2019Keywords_ = true;
337         PROJ_FALLTHROUGH
338     case Convention::WKT2:
339         d->params_.version_ = WKTFormatter::Version::WKT2;
340         d->params_.outputAxisOrder_ = true;
341         break;
342 
343     case Convention::WKT2_2019_SIMPLIFIED:
344         d->params_.use2019Keywords_ = true;
345         PROJ_FALLTHROUGH
346     case Convention::WKT2_SIMPLIFIED:
347         d->params_.version_ = WKTFormatter::Version::WKT2;
348         d->params_.idOnTopLevelOnly_ = true;
349         d->params_.outputAxisOrder_ = false;
350         d->params_.primeMeridianOmittedIfGreenwich_ = true;
351         d->params_.ellipsoidUnitOmittedIfMetre_ = true;
352         d->params_.primeMeridianOrParameterUnitOmittedIfSameAsAxis_ = true;
353         d->params_.forceUNITKeyword_ = true;
354         d->params_.outputCSUnitOnlyOnceIfSame_ = true;
355         break;
356 
357     case Convention::WKT1_GDAL:
358         d->params_.version_ = WKTFormatter::Version::WKT1;
359         d->params_.outputAxisOrder_ = false;
360         d->params_.forceUNITKeyword_ = true;
361         d->params_.primeMeridianInDegree_ = true;
362         d->params_.outputAxis_ =
363             WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE;
364         break;
365 
366     case Convention::WKT1_ESRI:
367         d->params_.version_ = WKTFormatter::Version::WKT1;
368         d->params_.outputAxisOrder_ = false;
369         d->params_.forceUNITKeyword_ = true;
370         d->params_.primeMeridianInDegree_ = true;
371         d->params_.useESRIDialect_ = true;
372         d->params_.multiLine_ = false;
373         d->params_.outputAxis_ = WKTFormatter::OutputAxisRule::NO;
374         break;
375 
376     default:
377         assert(false);
378         break;
379     }
380 }
381 
382 // ---------------------------------------------------------------------------
383 
setOutputId(bool outputIdIn)384 WKTFormatter &WKTFormatter::setOutputId(bool outputIdIn) {
385     if (d->indentLevel_ != 0) {
386         throw Exception(
387             "setOutputId() shall only be called when the stack state is empty");
388     }
389     d->outputIdStack_[0] = outputIdIn;
390     return *this;
391 }
392 
393 // ---------------------------------------------------------------------------
394 
addNewLine()395 void WKTFormatter::Private::addNewLine() { result_ += '\n'; }
396 
397 // ---------------------------------------------------------------------------
398 
addIndentation()399 void WKTFormatter::Private::addIndentation() {
400     result_ += std::string(indentLevel_ * params_.indentWidth_, ' ');
401 }
402 
403 // ---------------------------------------------------------------------------
404 
enter()405 void WKTFormatter::enter() {
406     if (d->indentLevel_ == 0 && d->level_ == 0) {
407         d->stackHasChild_.push_back(false);
408     }
409     ++d->level_;
410 }
411 
412 // ---------------------------------------------------------------------------
413 
leave()414 void WKTFormatter::leave() {
415     assert(d->level_ > 0);
416     --d->level_;
417     if (d->indentLevel_ == 0 && d->level_ == 0) {
418         d->stackHasChild_.pop_back();
419     }
420 }
421 
422 // ---------------------------------------------------------------------------
423 
isAtTopLevel() const424 bool WKTFormatter::isAtTopLevel() const {
425     return d->level_ == 0 && d->indentLevel_ == 0;
426 }
427 
428 // ---------------------------------------------------------------------------
429 
startNode(const std::string & keyword,bool hasId)430 void WKTFormatter::startNode(const std::string &keyword, bool hasId) {
431     if (!d->stackHasChild_.empty()) {
432         d->startNewChild();
433     } else if (!d->result_.empty()) {
434         d->result_ += ',';
435         if (d->params_.multiLine_ && !keyword.empty()) {
436             d->addNewLine();
437         }
438     }
439 
440     if (d->params_.multiLine_) {
441         if ((d->indentLevel_ || d->level_) && !keyword.empty()) {
442             if (!d->result_.empty()) {
443                 d->addNewLine();
444             }
445             d->addIndentation();
446         }
447     }
448 
449     if (!keyword.empty()) {
450         d->result_ += keyword;
451         d->result_ += '[';
452     }
453     d->indentLevel_++;
454     d->stackHasChild_.push_back(false);
455     d->stackEmptyKeyword_.push_back(keyword.empty());
456 
457     // Starting from a node that has a ID, we should emit ID nodes for :
458     // - this node
459     // - and for METHOD&PARAMETER nodes in WKT2, unless idOnTopLevelOnly_ is
460     // set.
461     // For WKT2, all other intermediate nodes shouldn't have ID ("not
462     // recommended")
463     if (!d->params_.idOnTopLevelOnly_ && d->indentLevel_ >= 2 &&
464         d->params_.version_ == WKTFormatter::Version::WKT2 &&
465         (keyword == WKTConstants::METHOD ||
466          keyword == WKTConstants::PARAMETER)) {
467         pushOutputId(d->outputIdStack_[0]);
468     } else if (d->indentLevel_ >= 2 &&
469                d->params_.version_ == WKTFormatter::Version::WKT2) {
470         pushOutputId(d->outputIdStack_[0] && !d->stackHasId_.back());
471     } else {
472         pushOutputId(outputId());
473     }
474 
475     d->stackHasId_.push_back(hasId || d->stackHasId_.back());
476 }
477 
478 // ---------------------------------------------------------------------------
479 
endNode()480 void WKTFormatter::endNode() {
481     assert(d->indentLevel_ > 0);
482     d->stackHasId_.pop_back();
483     popOutputId();
484     d->indentLevel_--;
485     bool emptyKeyword = d->stackEmptyKeyword_.back();
486     d->stackEmptyKeyword_.pop_back();
487     d->stackHasChild_.pop_back();
488     if (!emptyKeyword)
489         d->result_ += ']';
490 }
491 
492 // ---------------------------------------------------------------------------
493 
simulCurNodeHasId()494 WKTFormatter &WKTFormatter::simulCurNodeHasId() {
495     d->stackHasId_.back() = true;
496     return *this;
497 }
498 
499 // ---------------------------------------------------------------------------
500 
startNewChild()501 void WKTFormatter::Private::startNewChild() {
502     assert(!stackHasChild_.empty());
503     if (stackHasChild_.back()) {
504         result_ += ',';
505     }
506     stackHasChild_.back() = true;
507 }
508 
509 // ---------------------------------------------------------------------------
510 
addQuotedString(const char * str)511 void WKTFormatter::addQuotedString(const char *str) {
512     addQuotedString(std::string(str));
513 }
514 
addQuotedString(const std::string & str)515 void WKTFormatter::addQuotedString(const std::string &str) {
516     d->startNewChild();
517     d->result_ += '"';
518     d->result_ += replaceAll(str, "\"", "\"\"");
519     d->result_ += '"';
520 }
521 
522 // ---------------------------------------------------------------------------
523 
add(const std::string & str)524 void WKTFormatter::add(const std::string &str) {
525     d->startNewChild();
526     d->result_ += str;
527 }
528 
529 // ---------------------------------------------------------------------------
530 
add(int number)531 void WKTFormatter::add(int number) {
532     d->startNewChild();
533     d->result_ += internal::toString(number);
534 }
535 
536 // ---------------------------------------------------------------------------
537 
538 #ifdef __MINGW32__
normalizeExponent(const std::string & in)539 static std::string normalizeExponent(const std::string &in) {
540     // mingw will output 1e-0xy instead of 1e-xy. Fix that
541     auto pos = in.find("e-0");
542     if (pos == std::string::npos) {
543         return in;
544     }
545     if (pos + 4 < in.size() && isdigit(in[pos + 3]) && isdigit(in[pos + 4])) {
546         return in.substr(0, pos + 2) + in.substr(pos + 3);
547     }
548     return in;
549 }
550 #else
normalizeExponent(const std::string & in)551 static inline std::string normalizeExponent(const std::string &in) {
552     return in;
553 }
554 #endif
555 
normalizeSerializedString(const std::string & in)556 static inline std::string normalizeSerializedString(const std::string &in) {
557     auto ret(normalizeExponent(in));
558     return ret;
559 }
560 
561 // ---------------------------------------------------------------------------
562 
add(double number,int precision)563 void WKTFormatter::add(double number, int precision) {
564     d->startNewChild();
565     if (number == 0.0) {
566         if (d->params_.useESRIDialect_) {
567             d->result_ += "0.0";
568         } else {
569             d->result_ += '0';
570         }
571     } else {
572         std::string val(
573             normalizeSerializedString(internal::toString(number, precision)));
574         d->result_ += replaceAll(val, "e", "E");
575         if (d->params_.useESRIDialect_ && val.find('.') == std::string::npos) {
576             d->result_ += ".0";
577         }
578     }
579 }
580 
581 // ---------------------------------------------------------------------------
582 
pushOutputUnit(bool outputUnitIn)583 void WKTFormatter::pushOutputUnit(bool outputUnitIn) {
584     d->outputUnitStack_.push_back(outputUnitIn);
585 }
586 
587 // ---------------------------------------------------------------------------
588 
popOutputUnit()589 void WKTFormatter::popOutputUnit() { d->outputUnitStack_.pop_back(); }
590 
591 // ---------------------------------------------------------------------------
592 
outputUnit() const593 bool WKTFormatter::outputUnit() const { return d->outputUnitStack_.back(); }
594 
595 // ---------------------------------------------------------------------------
596 
pushOutputId(bool outputIdIn)597 void WKTFormatter::pushOutputId(bool outputIdIn) {
598     d->outputIdStack_.push_back(outputIdIn);
599 }
600 
601 // ---------------------------------------------------------------------------
602 
popOutputId()603 void WKTFormatter::popOutputId() { d->outputIdStack_.pop_back(); }
604 
605 // ---------------------------------------------------------------------------
606 
outputId() const607 bool WKTFormatter::outputId() const {
608     return !d->params_.useESRIDialect_ && d->outputIdStack_.back();
609 }
610 
611 // ---------------------------------------------------------------------------
612 
pushHasId(bool hasId)613 void WKTFormatter::pushHasId(bool hasId) { d->stackHasId_.push_back(hasId); }
614 
615 // ---------------------------------------------------------------------------
616 
popHasId()617 void WKTFormatter::popHasId() { d->stackHasId_.pop_back(); }
618 
619 // ---------------------------------------------------------------------------
620 
pushDisableUsage()621 void WKTFormatter::pushDisableUsage() { d->stackDisableUsage_.push_back(true); }
622 
623 // ---------------------------------------------------------------------------
624 
popDisableUsage()625 void WKTFormatter::popDisableUsage() { d->stackDisableUsage_.pop_back(); }
626 
627 // ---------------------------------------------------------------------------
628 
outputUsage() const629 bool WKTFormatter::outputUsage() const {
630     return outputId() && d->stackDisableUsage_.empty();
631 }
632 
633 // ---------------------------------------------------------------------------
634 
pushAxisLinearUnit(const UnitOfMeasureNNPtr & unit)635 void WKTFormatter::pushAxisLinearUnit(const UnitOfMeasureNNPtr &unit) {
636     d->axisLinearUnitStack_.push_back(unit);
637 }
638 // ---------------------------------------------------------------------------
639 
popAxisLinearUnit()640 void WKTFormatter::popAxisLinearUnit() { d->axisLinearUnitStack_.pop_back(); }
641 
642 // ---------------------------------------------------------------------------
643 
axisLinearUnit() const644 const UnitOfMeasureNNPtr &WKTFormatter::axisLinearUnit() const {
645     return d->axisLinearUnitStack_.back();
646 }
647 
648 // ---------------------------------------------------------------------------
649 
pushAxisAngularUnit(const UnitOfMeasureNNPtr & unit)650 void WKTFormatter::pushAxisAngularUnit(const UnitOfMeasureNNPtr &unit) {
651     d->axisAngularUnitStack_.push_back(unit);
652 }
653 // ---------------------------------------------------------------------------
654 
popAxisAngularUnit()655 void WKTFormatter::popAxisAngularUnit() { d->axisAngularUnitStack_.pop_back(); }
656 
657 // ---------------------------------------------------------------------------
658 
axisAngularUnit() const659 const UnitOfMeasureNNPtr &WKTFormatter::axisAngularUnit() const {
660     return d->axisAngularUnitStack_.back();
661 }
662 
663 // ---------------------------------------------------------------------------
664 
outputAxis() const665 WKTFormatter::OutputAxisRule WKTFormatter::outputAxis() const {
666     return d->params_.outputAxis_;
667 }
668 
669 // ---------------------------------------------------------------------------
670 
outputAxisOrder() const671 bool WKTFormatter::outputAxisOrder() const {
672     return d->params_.outputAxisOrder_;
673 }
674 
675 // ---------------------------------------------------------------------------
676 
primeMeridianOmittedIfGreenwich() const677 bool WKTFormatter::primeMeridianOmittedIfGreenwich() const {
678     return d->params_.primeMeridianOmittedIfGreenwich_;
679 }
680 
681 // ---------------------------------------------------------------------------
682 
ellipsoidUnitOmittedIfMetre() const683 bool WKTFormatter::ellipsoidUnitOmittedIfMetre() const {
684     return d->params_.ellipsoidUnitOmittedIfMetre_;
685 }
686 
687 // ---------------------------------------------------------------------------
688 
primeMeridianOrParameterUnitOmittedIfSameAsAxis() const689 bool WKTFormatter::primeMeridianOrParameterUnitOmittedIfSameAsAxis() const {
690     return d->params_.primeMeridianOrParameterUnitOmittedIfSameAsAxis_;
691 }
692 
693 // ---------------------------------------------------------------------------
694 
outputCSUnitOnlyOnceIfSame() const695 bool WKTFormatter::outputCSUnitOnlyOnceIfSame() const {
696     return d->params_.outputCSUnitOnlyOnceIfSame_;
697 }
698 
699 // ---------------------------------------------------------------------------
700 
forceUNITKeyword() const701 bool WKTFormatter::forceUNITKeyword() const {
702     return d->params_.forceUNITKeyword_;
703 }
704 
705 // ---------------------------------------------------------------------------
706 
primeMeridianInDegree() const707 bool WKTFormatter::primeMeridianInDegree() const {
708     return d->params_.primeMeridianInDegree_;
709 }
710 
711 // ---------------------------------------------------------------------------
712 
idOnTopLevelOnly() const713 bool WKTFormatter::idOnTopLevelOnly() const {
714     return d->params_.idOnTopLevelOnly_;
715 }
716 
717 // ---------------------------------------------------------------------------
718 
topLevelHasId() const719 bool WKTFormatter::topLevelHasId() const {
720     return d->stackHasId_.size() >= 2 && d->stackHasId_[1];
721 }
722 
723 // ---------------------------------------------------------------------------
724 
version() const725 WKTFormatter::Version WKTFormatter::version() const {
726     return d->params_.version_;
727 }
728 
729 // ---------------------------------------------------------------------------
730 
use2019Keywords() const731 bool WKTFormatter::use2019Keywords() const {
732     return d->params_.use2019Keywords_;
733 }
734 
735 // ---------------------------------------------------------------------------
736 
useESRIDialect() const737 bool WKTFormatter::useESRIDialect() const { return d->params_.useESRIDialect_; }
738 
739 // ---------------------------------------------------------------------------
740 
databaseContext() const741 const DatabaseContextPtr &WKTFormatter::databaseContext() const {
742     return d->dbContext_;
743 }
744 
745 // ---------------------------------------------------------------------------
746 
setAbridgedTransformation(bool outputIn)747 void WKTFormatter::setAbridgedTransformation(bool outputIn) {
748     d->abridgedTransformation_ = outputIn;
749 }
750 
751 // ---------------------------------------------------------------------------
752 
abridgedTransformation() const753 bool WKTFormatter::abridgedTransformation() const {
754     return d->abridgedTransformation_;
755 }
756 
757 // ---------------------------------------------------------------------------
758 
setUseDerivingConversion(bool useDerivingConversionIn)759 void WKTFormatter::setUseDerivingConversion(bool useDerivingConversionIn) {
760     d->useDerivingConversion_ = useDerivingConversionIn;
761 }
762 
763 // ---------------------------------------------------------------------------
764 
useDerivingConversion() const765 bool WKTFormatter::useDerivingConversion() const {
766     return d->useDerivingConversion_;
767 }
768 
769 // ---------------------------------------------------------------------------
770 
setTOWGS84Parameters(const std::vector<double> & params)771 void WKTFormatter::setTOWGS84Parameters(const std::vector<double> &params) {
772     d->toWGS84Parameters_ = params;
773 }
774 
775 // ---------------------------------------------------------------------------
776 
getTOWGS84Parameters() const777 const std::vector<double> &WKTFormatter::getTOWGS84Parameters() const {
778     return d->toWGS84Parameters_;
779 }
780 
781 // ---------------------------------------------------------------------------
782 
setVDatumExtension(const std::string & filename)783 void WKTFormatter::setVDatumExtension(const std::string &filename) {
784     d->vDatumExtension_ = filename;
785 }
786 
787 // ---------------------------------------------------------------------------
788 
getVDatumExtension() const789 const std::string &WKTFormatter::getVDatumExtension() const {
790     return d->vDatumExtension_;
791 }
792 
793 // ---------------------------------------------------------------------------
794 
setHDatumExtension(const std::string & filename)795 void WKTFormatter::setHDatumExtension(const std::string &filename) {
796     d->hDatumExtension_ = filename;
797 }
798 
799 // ---------------------------------------------------------------------------
800 
getHDatumExtension() const801 const std::string &WKTFormatter::getHDatumExtension() const {
802     return d->hDatumExtension_;
803 }
804 
805 // ---------------------------------------------------------------------------
806 
morphNameToESRI(const std::string & name)807 std::string WKTFormatter::morphNameToESRI(const std::string &name) {
808 
809     for (const auto *suffix : {"(m)", "(ftUS)", "(E-N)", "(N-E)"}) {
810         if (ends_with(name, suffix)) {
811             return morphNameToESRI(
812                        name.substr(0, name.size() - strlen(suffix))) +
813                    suffix;
814         }
815     }
816 
817     std::string ret;
818     bool insertUnderscore = false;
819     // Replace any special character by underscore, except at the beginning
820     // and of the name where those characters are removed.
821     for (char ch : name) {
822         if (ch == '+' || ch == '-' || (ch >= '0' && ch <= '9') ||
823             (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
824             if (insertUnderscore && !ret.empty()) {
825                 ret += '_';
826             }
827             ret += ch;
828             insertUnderscore = false;
829         } else {
830             insertUnderscore = true;
831         }
832     }
833     return ret;
834 }
835 
836 // ---------------------------------------------------------------------------
837 
ingestWKTNode(const WKTNodeNNPtr & node)838 void WKTFormatter::ingestWKTNode(const WKTNodeNNPtr &node) {
839     startNode(node->value(), true);
840     for (const auto &child : node->children()) {
841         if (!child->children().empty()) {
842             ingestWKTNode(child);
843         } else {
844             add(child->value());
845         }
846     }
847     endNode();
848 }
849 
850 #ifdef unused
851 // ---------------------------------------------------------------------------
852 
startInversion()853 void WKTFormatter::startInversion() {
854     d->inversionStack_.push_back(!d->inversionStack_.back());
855 }
856 
857 // ---------------------------------------------------------------------------
858 
stopInversion()859 void WKTFormatter::stopInversion() {
860     assert(!d->inversionStack_.empty());
861     d->inversionStack_.pop_back();
862 }
863 
864 // ---------------------------------------------------------------------------
865 
isInverted() const866 bool WKTFormatter::isInverted() const { return d->inversionStack_.back(); }
867 #endif
868 
869 // ---------------------------------------------------------------------------
870 
871 //! @cond Doxygen_Suppress
872 
873 static WKTNodeNNPtr
874     null_node(NN_NO_CHECK(internal::make_unique<WKTNode>(std::string())));
875 
isNull(const WKTNodeNNPtr & node)876 static inline bool isNull(const WKTNodeNNPtr &node) {
877     return &node == &null_node;
878 }
879 
880 struct WKTNode::Private {
881     std::string value_{};
882     std::vector<WKTNodeNNPtr> children_{};
883 
Privateio::WKTNode::Private884     explicit Private(const std::string &valueIn) : value_(valueIn) {}
885 
886     // cppcheck-suppress functionStatic
valueio::WKTNode::Private887     inline const std::string &value() PROJ_PURE_DEFN { return value_; }
888 
889     // cppcheck-suppress functionStatic
childrenio::WKTNode::Private890     inline const std::vector<WKTNodeNNPtr> &children() PROJ_PURE_DEFN {
891         return children_;
892     }
893 
894     // cppcheck-suppress functionStatic
childrenSizeio::WKTNode::Private895     inline size_t childrenSize() PROJ_PURE_DEFN { return children_.size(); }
896 
897     // cppcheck-suppress functionStatic
898     const WKTNodeNNPtr &lookForChild(const std::string &childName,
899                                      int occurrence) const noexcept;
900 
901     // cppcheck-suppress functionStatic
902     const WKTNodeNNPtr &lookForChild(const std::string &name) const noexcept;
903 
904     // cppcheck-suppress functionStatic
905     const WKTNodeNNPtr &lookForChild(const std::string &name,
906                                      const std::string &name2) const noexcept;
907 
908     // cppcheck-suppress functionStatic
909     const WKTNodeNNPtr &lookForChild(const std::string &name,
910                                      const std::string &name2,
911                                      const std::string &name3) const noexcept;
912 
913     // cppcheck-suppress functionStatic
914     const WKTNodeNNPtr &lookForChild(const std::string &name,
915                                      const std::string &name2,
916                                      const std::string &name3,
917                                      const std::string &name4) const noexcept;
918 };
919 
920 #define GP() getPrivate()
921 
922 // ---------------------------------------------------------------------------
923 
lookForChild(const std::string & childName,int occurrence) const924 const WKTNodeNNPtr &WKTNode::Private::lookForChild(const std::string &childName,
925                                                    int occurrence) const
926     noexcept {
927     int occCount = 0;
928     for (const auto &child : children_) {
929         if (ci_equal(child->GP()->value(), childName)) {
930             if (occurrence == occCount) {
931                 return child;
932             }
933             occCount++;
934         }
935     }
936     return null_node;
937 }
938 
939 const WKTNodeNNPtr &
lookForChild(const std::string & name) const940 WKTNode::Private::lookForChild(const std::string &name) const noexcept {
941     for (const auto &child : children_) {
942         const auto &v = child->GP()->value();
943         if (ci_equal(v, name)) {
944             return child;
945         }
946     }
947     return null_node;
948 }
949 
950 const WKTNodeNNPtr &
lookForChild(const std::string & name,const std::string & name2) const951 WKTNode::Private::lookForChild(const std::string &name,
952                                const std::string &name2) const noexcept {
953     for (const auto &child : children_) {
954         const auto &v = child->GP()->value();
955         if (ci_equal(v, name) || ci_equal(v, name2)) {
956             return child;
957         }
958     }
959     return null_node;
960 }
961 
962 const WKTNodeNNPtr &
lookForChild(const std::string & name,const std::string & name2,const std::string & name3) const963 WKTNode::Private::lookForChild(const std::string &name,
964                                const std::string &name2,
965                                const std::string &name3) const noexcept {
966     for (const auto &child : children_) {
967         const auto &v = child->GP()->value();
968         if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3)) {
969             return child;
970         }
971     }
972     return null_node;
973 }
974 
lookForChild(const std::string & name,const std::string & name2,const std::string & name3,const std::string & name4) const975 const WKTNodeNNPtr &WKTNode::Private::lookForChild(
976     const std::string &name, const std::string &name2, const std::string &name3,
977     const std::string &name4) const noexcept {
978     for (const auto &child : children_) {
979         const auto &v = child->GP()->value();
980         if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3) ||
981             ci_equal(v, name4)) {
982             return child;
983         }
984     }
985     return null_node;
986 }
987 
988 //! @endcond
989 
990 // ---------------------------------------------------------------------------
991 
992 /** \brief Instantiate a WKTNode.
993  *
994  * @param valueIn the name of the node.
995  */
WKTNode(const std::string & valueIn)996 WKTNode::WKTNode(const std::string &valueIn)
997     : d(internal::make_unique<Private>(valueIn)) {}
998 
999 // ---------------------------------------------------------------------------
1000 
1001 //! @cond Doxygen_Suppress
1002 WKTNode::~WKTNode() = default;
1003 //! @endcond
1004 
1005 // ---------------------------------------------------------------------------
1006 
1007 /** \brief Adds a child to the current node.
1008  *
1009  * @param child child to add. This should not be a parent of this node.
1010  */
addChild(WKTNodeNNPtr && child)1011 void WKTNode::addChild(WKTNodeNNPtr &&child) {
1012     d->children_.push_back(std::move(child));
1013 }
1014 
1015 // ---------------------------------------------------------------------------
1016 
1017 /** \brief Return the (occurrence-1)th sub-node of name childName.
1018  *
1019  * @param childName name of the child.
1020  * @param occurrence occurrence index (starting at 0)
1021  * @return the child, or nullptr.
1022  */
lookForChild(const std::string & childName,int occurrence) const1023 const WKTNodePtr &WKTNode::lookForChild(const std::string &childName,
1024                                         int occurrence) const noexcept {
1025     int occCount = 0;
1026     for (const auto &child : d->children_) {
1027         if (ci_equal(child->GP()->value(), childName)) {
1028             if (occurrence == occCount) {
1029                 return child;
1030             }
1031             occCount++;
1032         }
1033     }
1034     return null_node;
1035 }
1036 
1037 // ---------------------------------------------------------------------------
1038 
1039 /** \brief Return the count of children of given name.
1040  *
1041  * @param childName name of the children to look for.
1042  * @return count
1043  */
countChildrenOfName(const std::string & childName) const1044 int WKTNode::countChildrenOfName(const std::string &childName) const noexcept {
1045     int occCount = 0;
1046     for (const auto &child : d->children_) {
1047         if (ci_equal(child->GP()->value(), childName)) {
1048             occCount++;
1049         }
1050     }
1051     return occCount;
1052 }
1053 
1054 // ---------------------------------------------------------------------------
1055 
1056 /** \brief Return the value of a node.
1057  */
value() const1058 const std::string &WKTNode::value() const { return d->value_; }
1059 
1060 // ---------------------------------------------------------------------------
1061 
1062 /** \brief Return the children of a node.
1063  */
children() const1064 const std::vector<WKTNodeNNPtr> &WKTNode::children() const {
1065     return d->children_;
1066 }
1067 
1068 // ---------------------------------------------------------------------------
1069 
1070 //! @cond Doxygen_Suppress
skipSpace(const std::string & str,size_t start)1071 static size_t skipSpace(const std::string &str, size_t start) {
1072     size_t i = start;
1073     while (i < str.size() && ::isspace(static_cast<unsigned char>(str[i]))) {
1074         ++i;
1075     }
1076     return i;
1077 }
1078 //! @endcond
1079 
1080 // ---------------------------------------------------------------------------
1081 
1082 //! @cond Doxygen_Suppress
1083 // As used in examples of OGC 12-063r5
1084 static const std::string startPrintedQuote("\xE2\x80\x9C");
1085 static const std::string endPrintedQuote("\xE2\x80\x9D");
1086 //! @endcond
1087 
createFrom(const std::string & wkt,size_t indexStart,int recLevel,size_t & indexEnd)1088 WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart,
1089                                  int recLevel, size_t &indexEnd) {
1090     if (recLevel == 16) {
1091         throw ParsingException("too many nesting levels");
1092     }
1093     std::string value;
1094     size_t i = skipSpace(wkt, indexStart);
1095     if (i == wkt.size()) {
1096         throw ParsingException("whitespace only string");
1097     }
1098     std::string closingStringMarker;
1099     bool inString = false;
1100 
1101     for (; i < wkt.size() &&
1102            (inString ||
1103             (wkt[i] != '[' && wkt[i] != '(' && wkt[i] != ',' && wkt[i] != ']' &&
1104              wkt[i] != ')' && !::isspace(static_cast<unsigned char>(wkt[i]))));
1105          ++i) {
1106         if (wkt[i] == '"') {
1107             if (!inString) {
1108                 inString = true;
1109                 closingStringMarker = "\"";
1110             } else if (closingStringMarker == "\"") {
1111                 if (i + 1 < wkt.size() && wkt[i + 1] == '"') {
1112                     i++;
1113                 } else {
1114                     inString = false;
1115                     closingStringMarker.clear();
1116                 }
1117             }
1118         } else if (i + 3 <= wkt.size() &&
1119                    wkt.substr(i, 3) == startPrintedQuote) {
1120             if (!inString) {
1121                 inString = true;
1122                 closingStringMarker = endPrintedQuote;
1123                 value += '"';
1124                 i += 2;
1125                 continue;
1126             }
1127         } else if (i + 3 <= wkt.size() &&
1128                    closingStringMarker == endPrintedQuote &&
1129                    wkt.substr(i, 3) == endPrintedQuote) {
1130             inString = false;
1131             closingStringMarker.clear();
1132             value += '"';
1133             i += 2;
1134             continue;
1135         }
1136         value += wkt[i];
1137     }
1138     i = skipSpace(wkt, i);
1139     if (i == wkt.size()) {
1140         if (indexStart == 0) {
1141             throw ParsingException("missing [");
1142         } else {
1143             throw ParsingException("missing , or ]");
1144         }
1145     }
1146 
1147     auto node = NN_NO_CHECK(internal::make_unique<WKTNode>(value));
1148 
1149     if (indexStart > 0) {
1150         if (wkt[i] == ',') {
1151             indexEnd = i + 1;
1152             return node;
1153         }
1154         if (wkt[i] == ']' || wkt[i] == ')') {
1155             indexEnd = i;
1156             return node;
1157         }
1158     }
1159     if (wkt[i] != '[' && wkt[i] != '(') {
1160         throw ParsingException("missing [");
1161     }
1162     ++i; // skip [
1163     i = skipSpace(wkt, i);
1164     while (i < wkt.size() && wkt[i] != ']' && wkt[i] != ')') {
1165         size_t indexEndChild;
1166         node->addChild(createFrom(wkt, i, recLevel + 1, indexEndChild));
1167         assert(indexEndChild > i);
1168         i = indexEndChild;
1169         i = skipSpace(wkt, i);
1170         if (i < wkt.size() && wkt[i] == ',') {
1171             ++i;
1172             i = skipSpace(wkt, i);
1173         }
1174     }
1175     if (i == wkt.size() || (wkt[i] != ']' && wkt[i] != ')')) {
1176         throw ParsingException("missing ]");
1177     }
1178     indexEnd = i + 1;
1179     return node;
1180 }
1181 // ---------------------------------------------------------------------------
1182 
1183 /** \brief Instantiate a WKTNode hierarchy from a WKT string.
1184  *
1185  * @param wkt the WKT string to parse.
1186  * @param indexStart the start index in the wkt string.
1187  * @throw ParsingException
1188  */
createFrom(const std::string & wkt,size_t indexStart)1189 WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart) {
1190     size_t indexEnd;
1191     return createFrom(wkt, indexStart, 0, indexEnd);
1192 }
1193 
1194 // ---------------------------------------------------------------------------
1195 
1196 //! @cond Doxygen_Suppress
escapeIfQuotedString(const std::string & str)1197 static std::string escapeIfQuotedString(const std::string &str) {
1198     if (str.size() > 2 && str[0] == '"' && str.back() == '"') {
1199         std::string res("\"");
1200         res += replaceAll(str.substr(1, str.size() - 2), "\"", "\"\"");
1201         res += '"';
1202         return res;
1203     } else {
1204         return str;
1205     }
1206 }
1207 //! @endcond
1208 
1209 // ---------------------------------------------------------------------------
1210 
1211 /** \brief Return a WKT representation of the tree structure.
1212  */
toString() const1213 std::string WKTNode::toString() const {
1214     std::string str(escapeIfQuotedString(d->value_));
1215     if (!d->children_.empty()) {
1216         str += "[";
1217         bool first = true;
1218         for (auto &child : d->children_) {
1219             if (!first) {
1220                 str += ',';
1221             }
1222             first = false;
1223             str += child->toString();
1224         }
1225         str += "]";
1226     }
1227     return str;
1228 }
1229 
1230 // ---------------------------------------------------------------------------
1231 
1232 //! @cond Doxygen_Suppress
1233 struct WKTParser::Private {
1234     bool strict_ = true;
1235     std::list<std::string> warningList_{};
1236     std::vector<double> toWGS84Parameters_{};
1237     std::string datumPROJ4Grids_{};
1238     bool esriStyle_ = false;
1239     DatabaseContextPtr dbContext_{};
1240 
1241     static constexpr int MAX_PROPERTY_SIZE = 1024;
1242     PropertyMap **properties_{};
1243     int propertyCount_ = 0;
1244 
Privateio::WKTParser::Private1245     Private() { properties_ = new PropertyMap *[MAX_PROPERTY_SIZE]; }
1246 
~Privateio::WKTParser::Private1247     ~Private() {
1248         for (int i = 0; i < propertyCount_; i++) {
1249             delete properties_[i];
1250         }
1251         delete[] properties_;
1252     }
1253     Private(const Private &) = delete;
1254     Private &operator=(const Private &) = delete;
1255 
1256     void emitRecoverableWarning(const std::string &errorMsg);
1257 
1258     BaseObjectNNPtr build(const WKTNodeNNPtr &node);
1259 
1260     IdentifierPtr buildId(const WKTNodeNNPtr &node, bool tolerant,
1261                           bool removeInverseOf);
1262 
1263     PropertyMap &buildProperties(const WKTNodeNNPtr &node,
1264                                  bool removeInverseOf = false);
1265 
1266     ObjectDomainPtr buildObjectDomain(const WKTNodeNNPtr &node);
1267 
1268     static std::string stripQuotes(const WKTNodeNNPtr &node);
1269 
1270     static double asDouble(const WKTNodeNNPtr &node);
1271 
1272     UnitOfMeasure
1273     buildUnit(const WKTNodeNNPtr &node,
1274               UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN);
1275 
1276     UnitOfMeasure buildUnitInSubNode(
1277         const WKTNodeNNPtr &node,
1278         common::UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN);
1279 
1280     EllipsoidNNPtr buildEllipsoid(const WKTNodeNNPtr &node);
1281 
1282     PrimeMeridianNNPtr
1283     buildPrimeMeridian(const WKTNodeNNPtr &node,
1284                        const UnitOfMeasure &defaultAngularUnit);
1285 
1286     static optional<std::string> getAnchor(const WKTNodeNNPtr &node);
1287 
1288     static void parseDynamic(const WKTNodeNNPtr &dynamicNode,
1289                              double &frameReferenceEpoch,
1290                              util::optional<std::string> &modelName);
1291 
1292     GeodeticReferenceFrameNNPtr
1293     buildGeodeticReferenceFrame(const WKTNodeNNPtr &node,
1294                                 const PrimeMeridianNNPtr &primeMeridian,
1295                                 const WKTNodeNNPtr &dynamicNode);
1296 
1297     DatumEnsembleNNPtr buildDatumEnsemble(const WKTNodeNNPtr &node,
1298                                           const PrimeMeridianPtr &primeMeridian,
1299                                           bool expectEllipsoid);
1300 
1301     MeridianNNPtr buildMeridian(const WKTNodeNNPtr &node);
1302     CoordinateSystemAxisNNPtr buildAxis(const WKTNodeNNPtr &node,
1303                                         const UnitOfMeasure &unitIn,
1304                                         const UnitOfMeasure::Type &unitType,
1305                                         bool isGeocentric,
1306                                         int expectedOrderNum);
1307 
1308     CoordinateSystemNNPtr buildCS(const WKTNodeNNPtr &node, /* maybe null */
1309                                   const WKTNodeNNPtr &parentNode,
1310                                   const UnitOfMeasure &defaultAngularUnit);
1311 
1312     GeodeticCRSNNPtr buildGeodeticCRS(const WKTNodeNNPtr &node);
1313 
1314     CRSNNPtr buildDerivedGeodeticCRS(const WKTNodeNNPtr &node);
1315 
1316     static UnitOfMeasure
1317     guessUnitForParameter(const std::string &paramName,
1318                           const UnitOfMeasure &defaultLinearUnit,
1319                           const UnitOfMeasure &defaultAngularUnit);
1320 
1321     void consumeParameters(const WKTNodeNNPtr &node, bool isAbridged,
1322                            std::vector<OperationParameterNNPtr> &parameters,
1323                            std::vector<ParameterValueNNPtr> &values,
1324                            const UnitOfMeasure &defaultLinearUnit,
1325                            const UnitOfMeasure &defaultAngularUnit);
1326 
1327     static std::string getExtensionProj4(const WKTNode::Private *nodeP);
1328 
1329     static void addExtensionProj4ToProp(const WKTNode::Private *nodeP,
1330                                         PropertyMap &props);
1331 
1332     ConversionNNPtr buildConversion(const WKTNodeNNPtr &node,
1333                                     const UnitOfMeasure &defaultLinearUnit,
1334                                     const UnitOfMeasure &defaultAngularUnit);
1335 
1336     static bool hasWebMercPROJ4String(const WKTNodeNNPtr &projCRSNode,
1337                                       const WKTNodeNNPtr &projectionNode);
1338 
1339     static std::string projectionGetParameter(const WKTNodeNNPtr &projCRSNode,
1340                                               const char *paramName);
1341 
1342     ConversionNNPtr buildProjection(const GeodeticCRSNNPtr &baseGeodCRS,
1343                                     const WKTNodeNNPtr &projCRSNode,
1344                                     const WKTNodeNNPtr &projectionNode,
1345                                     const UnitOfMeasure &defaultLinearUnit,
1346                                     const UnitOfMeasure &defaultAngularUnit);
1347 
1348     ConversionNNPtr
1349     buildProjectionStandard(const GeodeticCRSNNPtr &baseGeodCRS,
1350                             const WKTNodeNNPtr &projCRSNode,
1351                             const WKTNodeNNPtr &projectionNode,
1352                             const UnitOfMeasure &defaultLinearUnit,
1353                             const UnitOfMeasure &defaultAngularUnit);
1354 
1355     ConversionNNPtr
1356     buildProjectionFromESRI(const GeodeticCRSNNPtr &baseGeodCRS,
1357                             const WKTNodeNNPtr &projCRSNode,
1358                             const WKTNodeNNPtr &projectionNode,
1359                             const UnitOfMeasure &defaultLinearUnit,
1360                             const UnitOfMeasure &defaultAngularUnit);
1361 
1362     ProjectedCRSNNPtr buildProjectedCRS(const WKTNodeNNPtr &node);
1363 
1364     VerticalReferenceFrameNNPtr
1365     buildVerticalReferenceFrame(const WKTNodeNNPtr &node,
1366                                 const WKTNodeNNPtr &dynamicNode);
1367 
1368     TemporalDatumNNPtr buildTemporalDatum(const WKTNodeNNPtr &node);
1369 
1370     EngineeringDatumNNPtr buildEngineeringDatum(const WKTNodeNNPtr &node);
1371 
1372     ParametricDatumNNPtr buildParametricDatum(const WKTNodeNNPtr &node);
1373 
1374     CRSNNPtr buildVerticalCRS(const WKTNodeNNPtr &node);
1375 
1376     DerivedVerticalCRSNNPtr buildDerivedVerticalCRS(const WKTNodeNNPtr &node);
1377 
1378     CRSNNPtr buildCompoundCRS(const WKTNodeNNPtr &node);
1379 
1380     BoundCRSNNPtr buildBoundCRS(const WKTNodeNNPtr &node);
1381 
1382     TemporalCSNNPtr buildTemporalCS(const WKTNodeNNPtr &parentNode);
1383 
1384     TemporalCRSNNPtr buildTemporalCRS(const WKTNodeNNPtr &node);
1385 
1386     DerivedTemporalCRSNNPtr buildDerivedTemporalCRS(const WKTNodeNNPtr &node);
1387 
1388     EngineeringCRSNNPtr buildEngineeringCRS(const WKTNodeNNPtr &node);
1389 
1390     EngineeringCRSNNPtr
1391     buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node);
1392 
1393     DerivedEngineeringCRSNNPtr
1394     buildDerivedEngineeringCRS(const WKTNodeNNPtr &node);
1395 
1396     ParametricCSNNPtr buildParametricCS(const WKTNodeNNPtr &parentNode);
1397 
1398     ParametricCRSNNPtr buildParametricCRS(const WKTNodeNNPtr &node);
1399 
1400     DerivedParametricCRSNNPtr
1401     buildDerivedParametricCRS(const WKTNodeNNPtr &node);
1402 
1403     DerivedProjectedCRSNNPtr buildDerivedProjectedCRS(const WKTNodeNNPtr &node);
1404 
1405     CRSPtr buildCRS(const WKTNodeNNPtr &node);
1406 
1407     TransformationNNPtr buildCoordinateOperation(const WKTNodeNNPtr &node);
1408 
1409     ConcatenatedOperationNNPtr
1410     buildConcatenatedOperation(const WKTNodeNNPtr &node);
1411 };
1412 
1413 // ---------------------------------------------------------------------------
1414 
WKTParser()1415 WKTParser::WKTParser() : d(internal::make_unique<Private>()) {}
1416 
1417 // ---------------------------------------------------------------------------
1418 
1419 //! @cond Doxygen_Suppress
1420 WKTParser::~WKTParser() = default;
1421 //! @endcond
1422 
1423 // ---------------------------------------------------------------------------
1424 
1425 /** \brief Set whether parsing should be done in strict mode.
1426  */
setStrict(bool strict)1427 WKTParser &WKTParser::setStrict(bool strict) {
1428     d->strict_ = strict;
1429     return *this;
1430 }
1431 
1432 // ---------------------------------------------------------------------------
1433 
1434 /** \brief Return the list of warnings found during parsing.
1435  *
1436  * \note The list might be non-empty only is setStrict(false) has been called.
1437  */
warningList() const1438 std::list<std::string> WKTParser::warningList() const {
1439     return d->warningList_;
1440 }
1441 
1442 // ---------------------------------------------------------------------------
1443 
1444 //! @cond Doxygen_Suppress
emitRecoverableWarning(const std::string & errorMsg)1445 void WKTParser::Private::emitRecoverableWarning(const std::string &errorMsg) {
1446     if (strict_) {
1447         throw ParsingException(errorMsg);
1448     } else {
1449         warningList_.push_back(errorMsg);
1450     }
1451 }
1452 
1453 // ---------------------------------------------------------------------------
1454 
asDouble(const std::string & val)1455 static double asDouble(const std::string &val) { return c_locale_stod(val); }
1456 
1457 // ---------------------------------------------------------------------------
1458 
ThrowNotEnoughChildren(const std::string & nodeName)1459 PROJ_NO_RETURN static void ThrowNotEnoughChildren(const std::string &nodeName) {
1460     throw ParsingException(
1461         concat("not enough children in ", nodeName, " node"));
1462 }
1463 
1464 // ---------------------------------------------------------------------------
1465 
1466 PROJ_NO_RETURN static void
ThrowNotRequiredNumberOfChildren(const std::string & nodeName)1467 ThrowNotRequiredNumberOfChildren(const std::string &nodeName) {
1468     throw ParsingException(
1469         concat("not required number of children in ", nodeName, " node"));
1470 }
1471 
1472 // ---------------------------------------------------------------------------
1473 
ThrowMissing(const std::string & nodeName)1474 PROJ_NO_RETURN static void ThrowMissing(const std::string &nodeName) {
1475     throw ParsingException(concat("missing ", nodeName, " node"));
1476 }
1477 
1478 // ---------------------------------------------------------------------------
1479 
1480 PROJ_NO_RETURN static void
ThrowNotExpectedCSType(const std::string & expectedCSType)1481 ThrowNotExpectedCSType(const std::string &expectedCSType) {
1482     throw ParsingException(concat("CS node is not of type ", expectedCSType));
1483 }
1484 
1485 // ---------------------------------------------------------------------------
1486 
buildRethrow(const char * funcName,const std::exception & e)1487 static ParsingException buildRethrow(const char *funcName,
1488                                      const std::exception &e) {
1489     std::string res(funcName);
1490     res += ": ";
1491     res += e.what();
1492     return ParsingException(res);
1493 }
1494 
1495 // ---------------------------------------------------------------------------
1496 
stripQuotes(const WKTNodeNNPtr & node)1497 std::string WKTParser::Private::stripQuotes(const WKTNodeNNPtr &node) {
1498     return ::stripQuotes(node->GP()->value());
1499 }
1500 
1501 // ---------------------------------------------------------------------------
1502 
asDouble(const WKTNodeNNPtr & node)1503 double WKTParser::Private::asDouble(const WKTNodeNNPtr &node) {
1504     return io::asDouble(node->GP()->value());
1505 }
1506 
1507 // ---------------------------------------------------------------------------
1508 
buildId(const WKTNodeNNPtr & node,bool tolerant,bool removeInverseOf)1509 IdentifierPtr WKTParser::Private::buildId(const WKTNodeNNPtr &node,
1510                                           bool tolerant, bool removeInverseOf) {
1511     const auto *nodeP = node->GP();
1512     const auto &nodeChidren = nodeP->children();
1513     if (nodeChidren.size() >= 2) {
1514         auto codeSpace = stripQuotes(nodeChidren[0]);
1515         if (removeInverseOf && starts_with(codeSpace, "INVERSE(") &&
1516             codeSpace.back() == ')') {
1517             codeSpace = codeSpace.substr(strlen("INVERSE("));
1518             codeSpace.resize(codeSpace.size() - 1);
1519         }
1520         auto code = stripQuotes(nodeChidren[1]);
1521         auto &citationNode = nodeP->lookForChild(WKTConstants::CITATION);
1522         auto &uriNode = nodeP->lookForChild(WKTConstants::URI);
1523         PropertyMap propertiesId;
1524         propertiesId.set(Identifier::CODESPACE_KEY, codeSpace);
1525         bool authoritySet = false;
1526         /*if (!isNull(citationNode))*/ {
1527             const auto *citationNodeP = citationNode->GP();
1528             if (citationNodeP->childrenSize() == 1) {
1529                 authoritySet = true;
1530                 propertiesId.set(Identifier::AUTHORITY_KEY,
1531                                  stripQuotes(citationNodeP->children()[0]));
1532             }
1533         }
1534         if (!authoritySet) {
1535             propertiesId.set(Identifier::AUTHORITY_KEY, codeSpace);
1536         }
1537         /*if (!isNull(uriNode))*/ {
1538             const auto *uriNodeP = uriNode->GP();
1539             if (uriNodeP->childrenSize() == 1) {
1540                 propertiesId.set(Identifier::URI_KEY,
1541                                  stripQuotes(uriNodeP->children()[0]));
1542             }
1543         }
1544         if (nodeChidren.size() >= 3 &&
1545             nodeChidren[2]->GP()->childrenSize() == 0) {
1546             auto version = stripQuotes(nodeChidren[2]);
1547             propertiesId.set(Identifier::VERSION_KEY, version);
1548         }
1549         return Identifier::create(code, propertiesId);
1550     } else if (strict_ || !tolerant) {
1551         ThrowNotEnoughChildren(nodeP->value());
1552     } else {
1553         std::string msg("not enough children in ");
1554         msg += nodeP->value();
1555         msg += " node";
1556         warningList_.emplace_back(std::move(msg));
1557     }
1558     return nullptr;
1559 }
1560 
1561 // ---------------------------------------------------------------------------
1562 
buildProperties(const WKTNodeNNPtr & node,bool removeInverseOf)1563 PropertyMap &WKTParser::Private::buildProperties(const WKTNodeNNPtr &node,
1564                                                  bool removeInverseOf) {
1565 
1566     if (propertyCount_ == MAX_PROPERTY_SIZE) {
1567         throw ParsingException("MAX_PROPERTY_SIZE reached");
1568     }
1569     properties_[propertyCount_] = new PropertyMap();
1570     auto &&properties = properties_[propertyCount_];
1571     propertyCount_++;
1572 
1573     std::string authNameFromAlias;
1574     std::string codeFromAlias;
1575     const auto *nodeP = node->GP();
1576     const auto &nodeChildren = nodeP->children();
1577 
1578     auto identifiers = ArrayOfBaseObject::create();
1579     for (const auto &subNode : nodeChildren) {
1580         const auto &subNodeName(subNode->GP()->value());
1581         if (ci_equal(subNodeName, WKTConstants::ID) ||
1582             ci_equal(subNodeName, WKTConstants::AUTHORITY)) {
1583             auto id = buildId(subNode, true, removeInverseOf);
1584             if (id) {
1585                 identifiers->add(NN_NO_CHECK(id));
1586             }
1587         }
1588     }
1589 
1590     if (!nodeChildren.empty()) {
1591         const auto &nodeName(nodeP->value());
1592         auto name(stripQuotes(nodeChildren[0]));
1593         if (removeInverseOf && starts_with(name, "Inverse of ")) {
1594             name = name.substr(strlen("Inverse of "));
1595         }
1596 
1597         if (ends_with(name, " (deprecated)")) {
1598             name.resize(name.size() - strlen(" (deprecated)"));
1599             properties->set(common::IdentifiedObject::DEPRECATED_KEY, true);
1600         }
1601 
1602         // Oracle WKT can contain names like
1603         // "Reseau Geodesique Francais 1993 (EPSG ID 6171)"
1604         // for WKT attributes to the auth_name = "IGN - Paris"
1605         // Strip that suffix from the name and assign a true EPSG code to the
1606         // object
1607         if (identifiers->empty()) {
1608             const auto pos = name.find(" (EPSG ID ");
1609             if (pos != std::string::npos && name.back() == ')') {
1610                 const auto code =
1611                     name.substr(pos + strlen(" (EPSG ID "),
1612                                 name.size() - 1 - pos - strlen(" (EPSG ID "));
1613                 name.resize(pos);
1614 
1615                 PropertyMap propertiesId;
1616                 propertiesId.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
1617                 propertiesId.set(Identifier::AUTHORITY_KEY, Identifier::EPSG);
1618                 identifiers->add(Identifier::create(code, propertiesId));
1619             }
1620         }
1621 
1622         const char *tableNameForAlias = nullptr;
1623         if (ci_equal(nodeName, WKTConstants::GEOGCS)) {
1624             if (starts_with(name, "GCS_")) {
1625                 esriStyle_ = true;
1626                 if (name == "GCS_WGS_1984") {
1627                     name = "WGS 84";
1628                 } else {
1629                     tableNameForAlias = "geodetic_crs";
1630                 }
1631             }
1632         } else if (esriStyle_ && ci_equal(nodeName, WKTConstants::SPHEROID)) {
1633             if (name == "WGS_1984") {
1634                 name = "WGS 84";
1635                 authNameFromAlias = Identifier::EPSG;
1636                 codeFromAlias = "7030";
1637             } else {
1638                 tableNameForAlias = "ellipsoid";
1639             }
1640         }
1641 
1642         if (dbContext_ && tableNameForAlias) {
1643             std::string outTableName;
1644             auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
1645                                                         std::string());
1646             auto officialName = authFactory->getOfficialNameFromAlias(
1647                 name, tableNameForAlias, "ESRI", false, outTableName,
1648                 authNameFromAlias, codeFromAlias);
1649             if (!officialName.empty()) {
1650                 name = officialName;
1651 
1652                 // Clearing authority for geodetic_crs because of
1653                 // potential axis order mismatch.
1654                 if (strcmp(tableNameForAlias, "geodetic_crs") == 0) {
1655                     authNameFromAlias.clear();
1656                     codeFromAlias.clear();
1657                 }
1658             }
1659         }
1660 
1661         properties->set(IdentifiedObject::NAME_KEY, name);
1662     }
1663 
1664     if (identifiers->empty() && !authNameFromAlias.empty()) {
1665         identifiers->add(Identifier::create(
1666             codeFromAlias,
1667             PropertyMap()
1668                 .set(Identifier::CODESPACE_KEY, authNameFromAlias)
1669                 .set(Identifier::AUTHORITY_KEY, authNameFromAlias)));
1670     }
1671     if (!identifiers->empty()) {
1672         properties->set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
1673     }
1674 
1675     auto &remarkNode = nodeP->lookForChild(WKTConstants::REMARK);
1676     if (!isNull(remarkNode)) {
1677         const auto &remarkChildren = remarkNode->GP()->children();
1678         if (remarkChildren.size() == 1) {
1679             properties->set(IdentifiedObject::REMARKS_KEY,
1680                             stripQuotes(remarkChildren[0]));
1681         } else {
1682             ThrowNotRequiredNumberOfChildren(remarkNode->GP()->value());
1683         }
1684     }
1685 
1686     ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create();
1687     for (const auto &subNode : nodeP->children()) {
1688         const auto &subNodeName(subNode->GP()->value());
1689         if (ci_equal(subNodeName, WKTConstants::USAGE)) {
1690             auto objectDomain = buildObjectDomain(subNode);
1691             if (!objectDomain) {
1692                 throw ParsingException(
1693                     concat("missing children in ", subNodeName, " node"));
1694             }
1695             array->add(NN_NO_CHECK(objectDomain));
1696         }
1697     }
1698     if (!array->empty()) {
1699         properties->set(ObjectUsage::OBJECT_DOMAIN_KEY, array);
1700     } else {
1701         auto objectDomain = buildObjectDomain(node);
1702         if (objectDomain) {
1703             properties->set(ObjectUsage::OBJECT_DOMAIN_KEY,
1704                             NN_NO_CHECK(objectDomain));
1705         }
1706     }
1707 
1708     auto &versionNode = nodeP->lookForChild(WKTConstants::VERSION);
1709     if (!isNull(versionNode)) {
1710         const auto &versionChildren = versionNode->GP()->children();
1711         if (versionChildren.size() == 1) {
1712             properties->set(CoordinateOperation::OPERATION_VERSION_KEY,
1713                             stripQuotes(versionChildren[0]));
1714         } else {
1715             ThrowNotRequiredNumberOfChildren(versionNode->GP()->value());
1716         }
1717     }
1718 
1719     return *properties;
1720 }
1721 
1722 // ---------------------------------------------------------------------------
1723 
1724 ObjectDomainPtr
buildObjectDomain(const WKTNodeNNPtr & node)1725 WKTParser::Private::buildObjectDomain(const WKTNodeNNPtr &node) {
1726 
1727     const auto *nodeP = node->GP();
1728     auto &scopeNode = nodeP->lookForChild(WKTConstants::SCOPE);
1729     auto &areaNode = nodeP->lookForChild(WKTConstants::AREA);
1730     auto &bboxNode = nodeP->lookForChild(WKTConstants::BBOX);
1731     auto &verticalExtentNode =
1732         nodeP->lookForChild(WKTConstants::VERTICALEXTENT);
1733     auto &temporalExtentNode = nodeP->lookForChild(WKTConstants::TIMEEXTENT);
1734     if (!isNull(scopeNode) || !isNull(areaNode) || !isNull(bboxNode) ||
1735         !isNull(verticalExtentNode) || !isNull(temporalExtentNode)) {
1736         optional<std::string> scope;
1737         const auto *scopeNodeP = scopeNode->GP();
1738         const auto &scopeChildren = scopeNodeP->children();
1739         if (scopeChildren.size() == 1) {
1740             scope = stripQuotes(scopeChildren[0]);
1741         }
1742         ExtentPtr extent;
1743         if (!isNull(areaNode) || !isNull(bboxNode)) {
1744             util::optional<std::string> description;
1745             std::vector<GeographicExtentNNPtr> geogExtent;
1746             std::vector<VerticalExtentNNPtr> verticalExtent;
1747             std::vector<TemporalExtentNNPtr> temporalExtent;
1748             if (!isNull(areaNode)) {
1749                 const auto &areaChildren = areaNode->GP()->children();
1750                 if (areaChildren.size() == 1) {
1751                     description = stripQuotes(areaChildren[0]);
1752                 } else {
1753                     ThrowNotRequiredNumberOfChildren(areaNode->GP()->value());
1754                 }
1755             }
1756             if (!isNull(bboxNode)) {
1757                 const auto &bboxChildren = bboxNode->GP()->children();
1758                 if (bboxChildren.size() == 4) {
1759                     try {
1760                         double south = asDouble(bboxChildren[0]);
1761                         double west = asDouble(bboxChildren[1]);
1762                         double north = asDouble(bboxChildren[2]);
1763                         double east = asDouble(bboxChildren[3]);
1764                         auto bbox = GeographicBoundingBox::create(west, south,
1765                                                                   east, north);
1766                         geogExtent.emplace_back(bbox);
1767                     } catch (const std::exception &) {
1768                         throw ParsingException(concat("not 4 double values in ",
1769                                                       bboxNode->GP()->value(),
1770                                                       " node"));
1771                     }
1772                 } else {
1773                     ThrowNotRequiredNumberOfChildren(bboxNode->GP()->value());
1774                 }
1775             }
1776 
1777             if (!isNull(verticalExtentNode)) {
1778                 const auto &verticalExtentChildren =
1779                     verticalExtentNode->GP()->children();
1780                 const auto verticalExtentChildrenSize =
1781                     verticalExtentChildren.size();
1782                 if (verticalExtentChildrenSize == 2 ||
1783                     verticalExtentChildrenSize == 3) {
1784                     double min;
1785                     double max;
1786                     try {
1787                         min = asDouble(verticalExtentChildren[0]);
1788                         max = asDouble(verticalExtentChildren[1]);
1789                     } catch (const std::exception &) {
1790                         throw ParsingException(
1791                             concat("not 2 double values in ",
1792                                    verticalExtentNode->GP()->value(), " node"));
1793                     }
1794                     UnitOfMeasure unit = UnitOfMeasure::METRE;
1795                     if (verticalExtentChildrenSize == 3) {
1796                         unit = buildUnit(verticalExtentChildren[2],
1797                                          UnitOfMeasure::Type::LINEAR);
1798                     }
1799                     verticalExtent.emplace_back(VerticalExtent::create(
1800                         min, max, util::nn_make_shared<UnitOfMeasure>(unit)));
1801                 } else {
1802                     ThrowNotRequiredNumberOfChildren(
1803                         verticalExtentNode->GP()->value());
1804                 }
1805             }
1806 
1807             if (!isNull(temporalExtentNode)) {
1808                 const auto &temporalExtentChildren =
1809                     temporalExtentNode->GP()->children();
1810                 if (temporalExtentChildren.size() == 2) {
1811                     temporalExtent.emplace_back(TemporalExtent::create(
1812                         stripQuotes(temporalExtentChildren[0]),
1813                         stripQuotes(temporalExtentChildren[1])));
1814                 } else {
1815                     ThrowNotRequiredNumberOfChildren(
1816                         temporalExtentNode->GP()->value());
1817                 }
1818             }
1819             extent = Extent::create(description, geogExtent, verticalExtent,
1820                                     temporalExtent)
1821                          .as_nullable();
1822         }
1823         return ObjectDomain::create(scope, extent).as_nullable();
1824     }
1825 
1826     return nullptr;
1827 }
1828 
1829 // ---------------------------------------------------------------------------
1830 
buildUnit(const WKTNodeNNPtr & node,UnitOfMeasure::Type type)1831 UnitOfMeasure WKTParser::Private::buildUnit(const WKTNodeNNPtr &node,
1832                                             UnitOfMeasure::Type type) {
1833     const auto *nodeP = node->GP();
1834     const auto &children = nodeP->children();
1835     if ((type != UnitOfMeasure::Type::TIME && children.size() < 2) ||
1836         (type == UnitOfMeasure::Type::TIME && children.size() < 1)) {
1837         ThrowNotEnoughChildren(nodeP->value());
1838     }
1839     try {
1840         std::string unitName(stripQuotes(children[0]));
1841         PropertyMap properties(buildProperties(node));
1842         auto &idNode =
1843             nodeP->lookForChild(WKTConstants::ID, WKTConstants::AUTHORITY);
1844         if (!isNull(idNode) && idNode->GP()->childrenSize() < 2) {
1845             emitRecoverableWarning("not enough children in " +
1846                                    idNode->GP()->value() + " node");
1847         }
1848         const bool hasValidIdNode =
1849             !isNull(idNode) && idNode->GP()->childrenSize() >= 2;
1850 
1851         const auto &idNodeChildren(idNode->GP()->children());
1852         std::string codeSpace(hasValidIdNode ? stripQuotes(idNodeChildren[0])
1853                                              : std::string());
1854         std::string code(hasValidIdNode ? stripQuotes(idNodeChildren[1])
1855                                         : std::string());
1856 
1857         bool queryDb = true;
1858         if (type == UnitOfMeasure::Type::UNKNOWN) {
1859             if (ci_equal(unitName, "METER") || ci_equal(unitName, "METRE")) {
1860                 type = UnitOfMeasure::Type::LINEAR;
1861                 unitName = "metre";
1862                 if (codeSpace.empty()) {
1863                     codeSpace = Identifier::EPSG;
1864                     code = "9001";
1865                     queryDb = false;
1866                 }
1867             } else if (ci_equal(unitName, "DEGREE") ||
1868                        ci_equal(unitName, "GRAD")) {
1869                 type = UnitOfMeasure::Type::ANGULAR;
1870             }
1871         }
1872 
1873         if (esriStyle_ && dbContext_ && queryDb) {
1874             std::string outTableName;
1875             std::string authNameFromAlias;
1876             std::string codeFromAlias;
1877             auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
1878                                                         std::string());
1879             auto officialName = authFactory->getOfficialNameFromAlias(
1880                 unitName, "unit_of_measure", "ESRI", false, outTableName,
1881                 authNameFromAlias, codeFromAlias);
1882             if (!officialName.empty()) {
1883                 unitName = officialName;
1884                 codeSpace = authNameFromAlias;
1885                 code = codeFromAlias;
1886             }
1887         }
1888 
1889         double convFactor = children.size() >= 2 ? asDouble(children[1]) : 0.0;
1890         constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37;
1891         constexpr double REL_ERROR = 1e-10;
1892         // Fix common rounding errors
1893         if (std::fabs(convFactor - UnitOfMeasure::DEGREE.conversionToSI()) <
1894             REL_ERROR * convFactor) {
1895             convFactor = UnitOfMeasure::DEGREE.conversionToSI();
1896         } else if (std::fabs(convFactor - US_FOOT_CONV_FACTOR) <
1897                    REL_ERROR * convFactor) {
1898             convFactor = US_FOOT_CONV_FACTOR;
1899         }
1900 
1901         return UnitOfMeasure(unitName, convFactor, type, codeSpace, code);
1902     } catch (const std::exception &e) {
1903         throw buildRethrow(__FUNCTION__, e);
1904     }
1905 }
1906 
1907 // ---------------------------------------------------------------------------
1908 
1909 // node here is a parent node, not a UNIT/LENGTHUNIT/ANGLEUNIT/TIMEUNIT/... node
buildUnitInSubNode(const WKTNodeNNPtr & node,UnitOfMeasure::Type type)1910 UnitOfMeasure WKTParser::Private::buildUnitInSubNode(const WKTNodeNNPtr &node,
1911                                                      UnitOfMeasure::Type type) {
1912     const auto *nodeP = node->GP();
1913     {
1914         auto &unitNode = nodeP->lookForChild(WKTConstants::LENGTHUNIT);
1915         if (!isNull(unitNode)) {
1916             return buildUnit(unitNode, UnitOfMeasure::Type::LINEAR);
1917         }
1918     }
1919 
1920     {
1921         auto &unitNode = nodeP->lookForChild(WKTConstants::ANGLEUNIT);
1922         if (!isNull(unitNode)) {
1923             return buildUnit(unitNode, UnitOfMeasure::Type::ANGULAR);
1924         }
1925     }
1926 
1927     {
1928         auto &unitNode = nodeP->lookForChild(WKTConstants::SCALEUNIT);
1929         if (!isNull(unitNode)) {
1930             return buildUnit(unitNode, UnitOfMeasure::Type::SCALE);
1931         }
1932     }
1933 
1934     {
1935         auto &unitNode = nodeP->lookForChild(WKTConstants::TIMEUNIT);
1936         if (!isNull(unitNode)) {
1937             return buildUnit(unitNode, UnitOfMeasure::Type::TIME);
1938         }
1939     }
1940     {
1941         auto &unitNode = nodeP->lookForChild(WKTConstants::TEMPORALQUANTITY);
1942         if (!isNull(unitNode)) {
1943             return buildUnit(unitNode, UnitOfMeasure::Type::TIME);
1944         }
1945     }
1946 
1947     {
1948         auto &unitNode = nodeP->lookForChild(WKTConstants::PARAMETRICUNIT);
1949         if (!isNull(unitNode)) {
1950             return buildUnit(unitNode, UnitOfMeasure::Type::PARAMETRIC);
1951         }
1952     }
1953 
1954     {
1955         auto &unitNode = nodeP->lookForChild(WKTConstants::UNIT);
1956         if (!isNull(unitNode)) {
1957             return buildUnit(unitNode, type);
1958         }
1959     }
1960 
1961     return UnitOfMeasure::NONE;
1962 }
1963 
1964 // ---------------------------------------------------------------------------
1965 
buildEllipsoid(const WKTNodeNNPtr & node)1966 EllipsoidNNPtr WKTParser::Private::buildEllipsoid(const WKTNodeNNPtr &node) {
1967     const auto *nodeP = node->GP();
1968     const auto &children = nodeP->children();
1969     if (children.size() < 3) {
1970         ThrowNotEnoughChildren(nodeP->value());
1971     }
1972     try {
1973         UnitOfMeasure unit =
1974             buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR);
1975         if (unit == UnitOfMeasure::NONE) {
1976             unit = UnitOfMeasure::METRE;
1977         }
1978         Length semiMajorAxis(asDouble(children[1]), unit);
1979         Scale invFlattening(asDouble(children[2]));
1980         const auto celestialBody(
1981             Ellipsoid::guessBodyName(dbContext_, semiMajorAxis.getSIValue()));
1982         if (invFlattening.getSIValue() == 0) {
1983             return Ellipsoid::createSphere(buildProperties(node), semiMajorAxis,
1984                                            celestialBody);
1985         } else {
1986             return Ellipsoid::createFlattenedSphere(
1987                 buildProperties(node), semiMajorAxis, invFlattening,
1988                 celestialBody);
1989         }
1990     } catch (const std::exception &e) {
1991         throw buildRethrow(__FUNCTION__, e);
1992     }
1993 }
1994 
1995 // ---------------------------------------------------------------------------
1996 
buildPrimeMeridian(const WKTNodeNNPtr & node,const UnitOfMeasure & defaultAngularUnit)1997 PrimeMeridianNNPtr WKTParser::Private::buildPrimeMeridian(
1998     const WKTNodeNNPtr &node, const UnitOfMeasure &defaultAngularUnit) {
1999     const auto *nodeP = node->GP();
2000     const auto &children = nodeP->children();
2001     if (children.size() < 2) {
2002         ThrowNotEnoughChildren(nodeP->value());
2003     }
2004     auto name = stripQuotes(children[0]);
2005     UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR);
2006     if (unit == UnitOfMeasure::NONE) {
2007         unit = defaultAngularUnit;
2008         if (unit == UnitOfMeasure::NONE) {
2009             unit = UnitOfMeasure::DEGREE;
2010         }
2011     }
2012     try {
2013         double angleValue = asDouble(children[1]);
2014 
2015         // Correct for GDAL WKT1 and WKT1-ESRI departure
2016         if (name == "Paris" && std::fabs(angleValue - 2.33722917) < 1e-8 &&
2017             unit._isEquivalentTo(UnitOfMeasure::GRAD,
2018                                  util::IComparable::Criterion::EQUIVALENT)) {
2019             angleValue = 2.5969213;
2020         } else {
2021             static const struct {
2022                 const char *name;
2023                 int deg;
2024                 int min;
2025                 double sec;
2026             } primeMeridiansDMS[] = {
2027                 {"Lisbon", -9, 7, 54.862},  {"Bogota", -74, 4, 51.3},
2028                 {"Madrid", -3, 41, 14.55},  {"Rome", 12, 27, 8.4},
2029                 {"Bern", 7, 26, 22.5},      {"Jakarta", 106, 48, 27.79},
2030                 {"Ferro", -17, 40, 0},      {"Brussels", 4, 22, 4.71},
2031                 {"Stockholm", 18, 3, 29.8}, {"Athens", 23, 42, 58.815},
2032                 {"Oslo", 10, 43, 22.5},     {"Paris RGS", 2, 20, 13.95},
2033                 {"Paris_RGS", 2, 20, 13.95}};
2034 
2035             // Current epsg.org output may use the EPSG:9110 "sexagesimal DMS"
2036             // unit and a DD.MMSSsss value, but this will likely be changed to
2037             // use decimal degree.
2038             // Or WKT1 may for example use the Paris RGS decimal degree value
2039             // but with a GEOGCS with UNIT["Grad"]
2040             for (const auto &pmDef : primeMeridiansDMS) {
2041                 if (name == pmDef.name) {
2042                     double dmsAsDecimalValue =
2043                         (pmDef.deg >= 0 ? 1 : -1) *
2044                         (std::abs(pmDef.deg) + pmDef.min / 100. +
2045                          pmDef.sec / 10000.);
2046                     double dmsAsDecimalDegreeValue =
2047                         (pmDef.deg >= 0 ? 1 : -1) *
2048                         (std::abs(pmDef.deg) + pmDef.min / 60. +
2049                          pmDef.sec / 3600.);
2050                     if (std::fabs(angleValue - dmsAsDecimalValue) < 1e-8 ||
2051                         std::fabs(angleValue - dmsAsDecimalDegreeValue) <
2052                             1e-8) {
2053                         angleValue = dmsAsDecimalDegreeValue;
2054                         unit = UnitOfMeasure::DEGREE;
2055                     }
2056                     break;
2057                 }
2058             }
2059         }
2060 
2061         auto &properties = buildProperties(node);
2062         if (dbContext_ && esriStyle_) {
2063             std::string outTableName;
2064             std::string codeFromAlias;
2065             std::string authNameFromAlias;
2066             auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2067                                                         std::string());
2068             auto officialName = authFactory->getOfficialNameFromAlias(
2069                 name, "prime_meridian", "ESRI", false, outTableName,
2070                 authNameFromAlias, codeFromAlias);
2071             if (!officialName.empty()) {
2072                 properties.set(IdentifiedObject::NAME_KEY, officialName);
2073                 if (!authNameFromAlias.empty()) {
2074                     auto identifiers = ArrayOfBaseObject::create();
2075                     identifiers->add(Identifier::create(
2076                         codeFromAlias,
2077                         PropertyMap()
2078                             .set(Identifier::CODESPACE_KEY, authNameFromAlias)
2079                             .set(Identifier::AUTHORITY_KEY,
2080                                  authNameFromAlias)));
2081                     properties.set(IdentifiedObject::IDENTIFIERS_KEY,
2082                                    identifiers);
2083                 }
2084             }
2085         }
2086 
2087         Angle angle(angleValue, unit);
2088         return PrimeMeridian::create(properties, angle);
2089     } catch (const std::exception &e) {
2090         throw buildRethrow(__FUNCTION__, e);
2091     }
2092 }
2093 
2094 // ---------------------------------------------------------------------------
2095 
getAnchor(const WKTNodeNNPtr & node)2096 optional<std::string> WKTParser::Private::getAnchor(const WKTNodeNNPtr &node) {
2097 
2098     auto &anchorNode = node->GP()->lookForChild(WKTConstants::ANCHOR);
2099     if (anchorNode->GP()->childrenSize() == 1) {
2100         return optional<std::string>(
2101             stripQuotes(anchorNode->GP()->children()[0]));
2102     }
2103     return optional<std::string>();
2104 }
2105 
2106 // ---------------------------------------------------------------------------
2107 
2108 static const PrimeMeridianNNPtr &
fixupPrimeMeridan(const EllipsoidNNPtr & ellipsoid,const PrimeMeridianNNPtr & pm)2109 fixupPrimeMeridan(const EllipsoidNNPtr &ellipsoid,
2110                   const PrimeMeridianNNPtr &pm) {
2111     return (ellipsoid->celestialBody() != Ellipsoid::EARTH &&
2112             pm.get() == PrimeMeridian::GREENWICH.get())
2113                ? PrimeMeridian::REFERENCE_MERIDIAN
2114                : pm;
2115 }
2116 
2117 // ---------------------------------------------------------------------------
2118 
buildGeodeticReferenceFrame(const WKTNodeNNPtr & node,const PrimeMeridianNNPtr & primeMeridian,const WKTNodeNNPtr & dynamicNode)2119 GeodeticReferenceFrameNNPtr WKTParser::Private::buildGeodeticReferenceFrame(
2120     const WKTNodeNNPtr &node, const PrimeMeridianNNPtr &primeMeridian,
2121     const WKTNodeNNPtr &dynamicNode) {
2122     const auto *nodeP = node->GP();
2123     auto &ellipsoidNode =
2124         nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID);
2125     if (isNull(ellipsoidNode)) {
2126         ThrowMissing(WKTConstants::ELLIPSOID);
2127     }
2128     auto &properties = buildProperties(node);
2129 
2130     // do that before buildEllipsoid() so that esriStyle_ can be set
2131     auto name = stripQuotes(nodeP->children()[0]);
2132 
2133     const auto identifyFromName = [&](const std::string &l_name) {
2134         if (dbContext_) {
2135             auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2136                                                         std::string());
2137             auto res = authFactory->createObjectsFromName(
2138                 l_name,
2139                 {AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, true,
2140                 1);
2141             if (!res.empty()) {
2142                 bool foundDatumName = false;
2143                 const auto &refDatum = res.front();
2144                 if (metadata::Identifier::isEquivalentName(
2145                         l_name.c_str(), refDatum->nameStr().c_str())) {
2146                     foundDatumName = true;
2147                 } else if (refDatum->identifiers().size() == 1) {
2148                     const auto &id = refDatum->identifiers()[0];
2149                     const auto aliases =
2150                         authFactory->databaseContext()->getAliases(
2151                             *id->codeSpace(), id->code(), refDatum->nameStr(),
2152                             "geodetic_datum", std::string());
2153                     for (const auto &alias : aliases) {
2154                         if (metadata::Identifier::isEquivalentName(
2155                                 l_name.c_str(), alias.c_str())) {
2156                             foundDatumName = true;
2157                             break;
2158                         }
2159                     }
2160                 }
2161                 if (foundDatumName) {
2162                     properties.set(IdentifiedObject::NAME_KEY,
2163                                    refDatum->nameStr());
2164                     if (!properties.get(Identifier::CODESPACE_KEY) &&
2165                         refDatum->identifiers().size() == 1) {
2166                         const auto &id = refDatum->identifiers()[0];
2167                         auto identifiers = ArrayOfBaseObject::create();
2168                         identifiers->add(Identifier::create(
2169                             id->code(), PropertyMap()
2170                                             .set(Identifier::CODESPACE_KEY,
2171                                                  *id->codeSpace())
2172                                             .set(Identifier::AUTHORITY_KEY,
2173                                                  *id->codeSpace())));
2174                         properties.set(IdentifiedObject::IDENTIFIERS_KEY,
2175                                        identifiers);
2176                     }
2177                     return true;
2178                 }
2179             } else {
2180                 // Get official name from database if AUTHORITY is present
2181                 auto &idNode = nodeP->lookForChild(WKTConstants::AUTHORITY);
2182                 if (!isNull(idNode)) {
2183                     try {
2184                         auto id = buildId(idNode, false, false);
2185                         auto authFactory2 = AuthorityFactory::create(
2186                             NN_NO_CHECK(dbContext_), *id->codeSpace());
2187                         auto dbDatum =
2188                             authFactory2->createGeodeticDatum(id->code());
2189                         properties.set(IdentifiedObject::NAME_KEY,
2190                                        dbDatum->nameStr());
2191                         return true;
2192                     } catch (const std::exception &) {
2193                     }
2194                 }
2195             }
2196         }
2197         return false;
2198     };
2199 
2200     // Remap GDAL WGS_1984 to EPSG v9 "World Geodetic System 1984" official
2201     // name.
2202     // Also remap EPSG v10 datum ensemble names to non-ensemble EPSG v9
2203     if (name == "WGS_1984" || name == "World Geodetic System 1984 ensemble") {
2204         properties.set(IdentifiedObject::NAME_KEY,
2205                        GeodeticReferenceFrame::EPSG_6326->nameStr());
2206     } else if (name == "European Terrestrial Reference System 1989 ensemble") {
2207         properties.set(IdentifiedObject::NAME_KEY,
2208                        "European Terrestrial Reference System 1989");
2209     } else if (starts_with(name, "D_")) {
2210         esriStyle_ = true;
2211         const char *tableNameForAlias = nullptr;
2212         std::string authNameFromAlias;
2213         std::string codeFromAlias;
2214         if (name == "D_WGS_1984") {
2215             name = "World Geodetic System 1984";
2216             authNameFromAlias = Identifier::EPSG;
2217             codeFromAlias = "6326";
2218         } else {
2219             tableNameForAlias = "geodetic_datum";
2220         }
2221 
2222         bool setNameAndId = true;
2223         if (dbContext_ && tableNameForAlias) {
2224             std::string outTableName;
2225             auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2226                                                         std::string());
2227             auto officialName = authFactory->getOfficialNameFromAlias(
2228                 name, tableNameForAlias, "ESRI", false, outTableName,
2229                 authNameFromAlias, codeFromAlias);
2230             if (officialName.empty()) {
2231                 // For the case of "D_GDA2020" where there is no D_GDA2020 ESRI
2232                 // alias, so just try without the D_ prefix.
2233                 const auto nameWithoutDPrefix = name.substr(2);
2234                 if (identifyFromName(nameWithoutDPrefix)) {
2235                     setNameAndId = false; // already done in identifyFromName()
2236                 }
2237             } else {
2238                 if (primeMeridian->nameStr() !=
2239                     PrimeMeridian::GREENWICH->nameStr()) {
2240                     auto nameWithPM =
2241                         officialName + " (" + primeMeridian->nameStr() + ")";
2242                     if (dbContext_->isKnownName(nameWithPM, "geodetic_datum")) {
2243                         officialName = nameWithPM;
2244                     }
2245                 }
2246                 name = officialName;
2247             }
2248         }
2249 
2250         if (setNameAndId) {
2251             properties.set(IdentifiedObject::NAME_KEY, name);
2252             if (!authNameFromAlias.empty()) {
2253                 auto identifiers = ArrayOfBaseObject::create();
2254                 identifiers->add(Identifier::create(
2255                     codeFromAlias,
2256                     PropertyMap()
2257                         .set(Identifier::CODESPACE_KEY, authNameFromAlias)
2258                         .set(Identifier::AUTHORITY_KEY, authNameFromAlias)));
2259                 properties.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
2260             }
2261         }
2262     } else if (name.find('_') != std::string::npos) {
2263         // Likely coming from WKT1
2264         identifyFromName(name);
2265     }
2266 
2267     auto ellipsoid = buildEllipsoid(ellipsoidNode);
2268     const auto &primeMeridianModified =
2269         fixupPrimeMeridan(ellipsoid, primeMeridian);
2270 
2271     auto &TOWGS84Node = nodeP->lookForChild(WKTConstants::TOWGS84);
2272     if (!isNull(TOWGS84Node)) {
2273         const auto &TOWGS84Children = TOWGS84Node->GP()->children();
2274         const size_t TOWGS84Size = TOWGS84Children.size();
2275         if (TOWGS84Size == 3 || TOWGS84Size == 7) {
2276             try {
2277                 for (const auto &child : TOWGS84Children) {
2278                     toWGS84Parameters_.push_back(asDouble(child));
2279                 }
2280                 for (size_t i = TOWGS84Size; i < 7; ++i) {
2281                     toWGS84Parameters_.push_back(0.0);
2282                 }
2283             } catch (const std::exception &) {
2284                 throw ParsingException("Invalid TOWGS84 node");
2285             }
2286         } else {
2287             throw ParsingException("Invalid TOWGS84 node");
2288         }
2289     }
2290 
2291     auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
2292     const auto &extensionChildren = extensionNode->GP()->children();
2293     if (extensionChildren.size() == 2) {
2294         if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) {
2295             datumPROJ4Grids_ = stripQuotes(extensionChildren[1]);
2296         }
2297     }
2298 
2299     if (!isNull(dynamicNode)) {
2300         double frameReferenceEpoch = 0.0;
2301         util::optional<std::string> modelName;
2302         parseDynamic(dynamicNode, frameReferenceEpoch, modelName);
2303         return DynamicGeodeticReferenceFrame::create(
2304             properties, ellipsoid, getAnchor(node), primeMeridianModified,
2305             common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR),
2306             modelName);
2307     }
2308 
2309     return GeodeticReferenceFrame::create(
2310         properties, ellipsoid, getAnchor(node), primeMeridianModified);
2311 }
2312 
2313 // ---------------------------------------------------------------------------
2314 
2315 DatumEnsembleNNPtr
buildDatumEnsemble(const WKTNodeNNPtr & node,const PrimeMeridianPtr & primeMeridian,bool expectEllipsoid)2316 WKTParser::Private::buildDatumEnsemble(const WKTNodeNNPtr &node,
2317                                        const PrimeMeridianPtr &primeMeridian,
2318                                        bool expectEllipsoid) {
2319     const auto *nodeP = node->GP();
2320     auto &ellipsoidNode =
2321         nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID);
2322     if (expectEllipsoid && isNull(ellipsoidNode)) {
2323         ThrowMissing(WKTConstants::ELLIPSOID);
2324     }
2325 
2326     std::vector<DatumNNPtr> datums;
2327     for (const auto &subNode : nodeP->children()) {
2328         if (ci_equal(subNode->GP()->value(), WKTConstants::MEMBER)) {
2329             if (subNode->GP()->childrenSize() == 0) {
2330                 throw ParsingException("Invalid MEMBER node");
2331             }
2332             if (expectEllipsoid) {
2333                 datums.emplace_back(GeodeticReferenceFrame::create(
2334                     buildProperties(subNode), buildEllipsoid(ellipsoidNode),
2335                     optional<std::string>(),
2336                     primeMeridian ? NN_NO_CHECK(primeMeridian)
2337                                   : PrimeMeridian::GREENWICH));
2338             } else {
2339                 datums.emplace_back(
2340                     VerticalReferenceFrame::create(buildProperties(subNode)));
2341             }
2342         }
2343     }
2344 
2345     auto &accuracyNode = nodeP->lookForChild(WKTConstants::ENSEMBLEACCURACY);
2346     auto &accuracyNodeChildren = accuracyNode->GP()->children();
2347     if (accuracyNodeChildren.empty()) {
2348         ThrowMissing(WKTConstants::ENSEMBLEACCURACY);
2349     }
2350     auto accuracy =
2351         PositionalAccuracy::create(accuracyNodeChildren[0]->GP()->value());
2352 
2353     try {
2354         return DatumEnsemble::create(buildProperties(node), datums, accuracy);
2355     } catch (const util::Exception &e) {
2356         throw buildRethrow(__FUNCTION__, e);
2357     }
2358 }
2359 
2360 // ---------------------------------------------------------------------------
2361 
buildMeridian(const WKTNodeNNPtr & node)2362 MeridianNNPtr WKTParser::Private::buildMeridian(const WKTNodeNNPtr &node) {
2363     const auto *nodeP = node->GP();
2364     const auto &children = nodeP->children();
2365     if (children.size() < 2) {
2366         ThrowNotEnoughChildren(nodeP->value());
2367     }
2368     UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR);
2369     try {
2370         double angleValue = asDouble(children[0]);
2371         Angle angle(angleValue, unit);
2372         return Meridian::create(angle);
2373     } catch (const std::exception &e) {
2374         throw buildRethrow(__FUNCTION__, e);
2375     }
2376 }
2377 
2378 // ---------------------------------------------------------------------------
2379 
ThrowParsingExceptionMissingUNIT()2380 PROJ_NO_RETURN static void ThrowParsingExceptionMissingUNIT() {
2381     throw ParsingException("buildCS: missing UNIT");
2382 }
2383 
2384 // ---------------------------------------------------------------------------
2385 
2386 CoordinateSystemAxisNNPtr
buildAxis(const WKTNodeNNPtr & node,const UnitOfMeasure & unitIn,const UnitOfMeasure::Type & unitType,bool isGeocentric,int expectedOrderNum)2387 WKTParser::Private::buildAxis(const WKTNodeNNPtr &node,
2388                               const UnitOfMeasure &unitIn,
2389                               const UnitOfMeasure::Type &unitType,
2390                               bool isGeocentric, int expectedOrderNum) {
2391     const auto *nodeP = node->GP();
2392     const auto &children = nodeP->children();
2393     if (children.size() < 2) {
2394         ThrowNotEnoughChildren(nodeP->value());
2395     }
2396 
2397     auto &orderNode = nodeP->lookForChild(WKTConstants::ORDER);
2398     if (!isNull(orderNode)) {
2399         const auto &orderNodeChildren = orderNode->GP()->children();
2400         if (orderNodeChildren.size() != 1) {
2401             ThrowNotEnoughChildren(WKTConstants::ORDER);
2402         }
2403         const auto &order = orderNodeChildren[0]->GP()->value();
2404         int orderNum;
2405         try {
2406             orderNum = std::stoi(order);
2407         } catch (const std::exception &) {
2408             throw ParsingException(
2409                 concat("buildAxis: invalid ORDER value: ", order));
2410         }
2411         if (orderNum != expectedOrderNum) {
2412             throw ParsingException(
2413                 concat("buildAxis: did not get expected ORDER value: ", order));
2414         }
2415     }
2416 
2417     // The axis designation in WK2 can be: "name", "(abbrev)" or "name
2418     // (abbrev)"
2419     std::string axisDesignation(stripQuotes(children[0]));
2420     size_t sepPos = axisDesignation.find(" (");
2421     std::string axisName;
2422     std::string abbreviation;
2423     if (sepPos != std::string::npos && axisDesignation.back() == ')') {
2424         axisName = CoordinateSystemAxis::normalizeAxisName(
2425             axisDesignation.substr(0, sepPos));
2426         abbreviation = axisDesignation.substr(sepPos + 2);
2427         abbreviation.resize(abbreviation.size() - 1);
2428     } else if (!axisDesignation.empty() && axisDesignation[0] == '(' &&
2429                axisDesignation.back() == ')') {
2430         abbreviation = axisDesignation.substr(1, axisDesignation.size() - 2);
2431         if (abbreviation == AxisAbbreviation::E) {
2432             axisName = AxisName::Easting;
2433         } else if (abbreviation == AxisAbbreviation::N) {
2434             axisName = AxisName::Northing;
2435         } else if (abbreviation == AxisAbbreviation::lat) {
2436             axisName = AxisName::Latitude;
2437         } else if (abbreviation == AxisAbbreviation::lon) {
2438             axisName = AxisName::Longitude;
2439         }
2440     } else {
2441         axisName = CoordinateSystemAxis::normalizeAxisName(axisDesignation);
2442         if (axisName == AxisName::Latitude) {
2443             abbreviation = AxisAbbreviation::lat;
2444         } else if (axisName == AxisName::Longitude) {
2445             abbreviation = AxisAbbreviation::lon;
2446         } else if (axisName == AxisName::Ellipsoidal_height) {
2447             abbreviation = AxisAbbreviation::h;
2448         }
2449     }
2450     const std::string &dirString = children[1]->GP()->value();
2451     const AxisDirection *direction = AxisDirection::valueOf(dirString);
2452 
2453     // WKT2, geocentric CS: axis names are omitted
2454     if (axisName.empty()) {
2455         if (direction == &AxisDirection::GEOCENTRIC_X &&
2456             abbreviation == AxisAbbreviation::X) {
2457             axisName = AxisName::Geocentric_X;
2458         } else if (direction == &AxisDirection::GEOCENTRIC_Y &&
2459                    abbreviation == AxisAbbreviation::Y) {
2460             axisName = AxisName::Geocentric_Y;
2461         } else if (direction == &AxisDirection::GEOCENTRIC_Z &&
2462                    abbreviation == AxisAbbreviation::Z) {
2463             axisName = AxisName::Geocentric_Z;
2464         }
2465     }
2466 
2467     // WKT1
2468     if (!direction && isGeocentric && axisName == AxisName::Geocentric_X) {
2469         abbreviation = AxisAbbreviation::X;
2470         direction = &AxisDirection::GEOCENTRIC_X;
2471     } else if (!direction && isGeocentric &&
2472                axisName == AxisName::Geocentric_Y) {
2473         abbreviation = AxisAbbreviation::Y;
2474         direction = &AxisDirection::GEOCENTRIC_Y;
2475     } else if (isGeocentric && axisName == AxisName::Geocentric_Z &&
2476                (dirString == AxisDirectionWKT1::NORTH.toString() ||
2477                 dirString == AxisDirectionWKT1::OTHER.toString())) {
2478         abbreviation = AxisAbbreviation::Z;
2479         direction = &AxisDirection::GEOCENTRIC_Z;
2480     } else if (dirString == AxisDirectionWKT1::OTHER.toString()) {
2481         direction = &AxisDirection::UNSPECIFIED;
2482     } else if (!direction &&
2483                AxisDirectionWKT1::valueOf(toupper(dirString)) != nullptr) {
2484         direction = AxisDirection::valueOf(tolower(dirString));
2485     }
2486 
2487     if (!direction) {
2488         throw ParsingException(
2489             concat("unhandled axis direction: ", children[1]->GP()->value()));
2490     }
2491     UnitOfMeasure unit(buildUnitInSubNode(node));
2492     if (unit == UnitOfMeasure::NONE) {
2493         // If no unit in the AXIS node, use the one potentially coming from
2494         // the CS.
2495         unit = unitIn;
2496         if (unit == UnitOfMeasure::NONE &&
2497             unitType != UnitOfMeasure::Type::NONE &&
2498             unitType != UnitOfMeasure::Type::TIME) {
2499             ThrowParsingExceptionMissingUNIT();
2500         }
2501     }
2502 
2503     auto &meridianNode = nodeP->lookForChild(WKTConstants::MERIDIAN);
2504 
2505     return CoordinateSystemAxis::create(
2506         buildProperties(node).set(IdentifiedObject::NAME_KEY, axisName),
2507         abbreviation, *direction, unit,
2508         !isNull(meridianNode) ? buildMeridian(meridianNode).as_nullable()
2509                               : nullptr);
2510 }
2511 
2512 // ---------------------------------------------------------------------------
2513 
2514 static const PropertyMap emptyPropertyMap{};
2515 
ThrowParsingException(const std::string & msg)2516 PROJ_NO_RETURN static void ThrowParsingException(const std::string &msg) {
2517     throw ParsingException(msg);
2518 }
2519 
2520 static ParsingException
buildParsingExceptionInvalidAxisCount(const std::string & csType)2521 buildParsingExceptionInvalidAxisCount(const std::string &csType) {
2522     return ParsingException(
2523         concat("buildCS: invalid CS axis count for ", csType));
2524 }
2525 
2526 CoordinateSystemNNPtr
buildCS(const WKTNodeNNPtr & node,const WKTNodeNNPtr & parentNode,const UnitOfMeasure & defaultAngularUnit)2527 WKTParser::Private::buildCS(const WKTNodeNNPtr &node, /* maybe null */
2528                             const WKTNodeNNPtr &parentNode,
2529                             const UnitOfMeasure &defaultAngularUnit) {
2530     bool isGeocentric = false;
2531     std::string csType;
2532     const int numberOfAxis =
2533         parentNode->countChildrenOfName(WKTConstants::AXIS);
2534     int axisCount = numberOfAxis;
2535     if (!isNull(node)) {
2536         const auto *nodeP = node->GP();
2537         const auto &children = nodeP->children();
2538         if (children.size() < 2) {
2539             ThrowNotEnoughChildren(nodeP->value());
2540         }
2541         csType = children[0]->GP()->value();
2542         try {
2543             axisCount = std::stoi(children[1]->GP()->value());
2544         } catch (const std::exception &) {
2545             ThrowParsingException(concat("buildCS: invalid CS axis count: ",
2546                                          children[1]->GP()->value()));
2547         }
2548     } else {
2549         const char *csTypeCStr = "";
2550         const auto &parentNodeName = parentNode->GP()->value();
2551         if (ci_equal(parentNodeName, WKTConstants::GEOCCS)) {
2552             csTypeCStr = "Cartesian";
2553             isGeocentric = true;
2554             if (axisCount == 0) {
2555                 auto unit =
2556                     buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2557                 if (unit == UnitOfMeasure::NONE) {
2558                     ThrowParsingExceptionMissingUNIT();
2559                 }
2560                 return CartesianCS::createGeocentric(unit);
2561             }
2562         } else if (ci_equal(parentNodeName, WKTConstants::GEOGCS)) {
2563             csTypeCStr = "Ellipsoidal";
2564             if (axisCount == 0) {
2565                 // Missing axis with GEOGCS ? Presumably Long/Lat order
2566                 // implied
2567                 auto unit = buildUnitInSubNode(parentNode,
2568                                                UnitOfMeasure::Type::ANGULAR);
2569                 if (unit == UnitOfMeasure::NONE) {
2570                     ThrowParsingExceptionMissingUNIT();
2571                 }
2572                 // WKT1 --> long/lat
2573                 return EllipsoidalCS::createLongitudeLatitude(unit);
2574             }
2575         } else if (ci_equal(parentNodeName, WKTConstants::BASEGEODCRS) ||
2576                    ci_equal(parentNodeName, WKTConstants::BASEGEOGCRS)) {
2577             csTypeCStr = "Ellipsoidal";
2578             if (axisCount == 0) {
2579                 auto unit = buildUnitInSubNode(parentNode,
2580                                                UnitOfMeasure::Type::ANGULAR);
2581                 if (unit == UnitOfMeasure::NONE) {
2582                     unit = defaultAngularUnit;
2583                 }
2584                 // WKT2 --> presumably lat/long
2585                 return EllipsoidalCS::createLatitudeLongitude(unit);
2586             }
2587         } else if (ci_equal(parentNodeName, WKTConstants::PROJCS) ||
2588                    ci_equal(parentNodeName, WKTConstants::BASEPROJCRS) ||
2589                    ci_equal(parentNodeName, WKTConstants::BASEENGCRS)) {
2590             csTypeCStr = "Cartesian";
2591             if (axisCount == 0) {
2592                 auto unit =
2593                     buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2594                 if (unit == UnitOfMeasure::NONE) {
2595                     if (ci_equal(parentNodeName, WKTConstants::PROJCS)) {
2596                         ThrowParsingExceptionMissingUNIT();
2597                     } else {
2598                         unit = UnitOfMeasure::METRE;
2599                     }
2600                 }
2601                 return CartesianCS::createEastingNorthing(unit);
2602             }
2603         } else if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
2604                    ci_equal(parentNodeName, WKTConstants::VERTCS) ||
2605                    ci_equal(parentNodeName, WKTConstants::BASEVERTCRS)) {
2606             csTypeCStr = "vertical";
2607 
2608             bool downDirection = false;
2609             if (ci_equal(parentNodeName, WKTConstants::VERTCS)) // ESRI
2610             {
2611                 for (const auto &childNode : parentNode->GP()->children()) {
2612                     const auto &childNodeChildren = childNode->GP()->children();
2613                     if (childNodeChildren.size() == 2 &&
2614                         ci_equal(childNode->GP()->value(),
2615                                  WKTConstants::PARAMETER) &&
2616                         childNodeChildren[0]->GP()->value() ==
2617                             "\"Direction\"") {
2618                         const auto &paramValue =
2619                             childNodeChildren[1]->GP()->value();
2620                         try {
2621                             double val = asDouble(childNodeChildren[1]);
2622                             if (val == 1.0) {
2623                                 // ok
2624                             } else if (val == -1.0) {
2625                                 downDirection = true;
2626                             }
2627                         } catch (const std::exception &) {
2628                             throw ParsingException(
2629                                 concat("unhandled parameter value type : ",
2630                                        paramValue));
2631                         }
2632                     }
2633                 }
2634             }
2635 
2636             if (axisCount == 0) {
2637                 auto unit =
2638                     buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2639                 if (unit == UnitOfMeasure::NONE) {
2640                     if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
2641                         ci_equal(parentNodeName, WKTConstants::VERTCS)) {
2642                         ThrowParsingExceptionMissingUNIT();
2643                     } else {
2644                         unit = UnitOfMeasure::METRE;
2645                     }
2646                 }
2647                 if (downDirection) {
2648                     return VerticalCS::create(
2649                         util::PropertyMap(),
2650                         CoordinateSystemAxis::create(
2651                             util::PropertyMap().set(IdentifiedObject::NAME_KEY,
2652                                                     "depth"),
2653                             "D", AxisDirection::DOWN, unit));
2654                 }
2655                 return VerticalCS::createGravityRelatedHeight(unit);
2656             }
2657         } else if (ci_equal(parentNodeName, WKTConstants::LOCAL_CS)) {
2658             if (axisCount == 0) {
2659                 auto unit =
2660                     buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2661                 if (unit == UnitOfMeasure::NONE) {
2662                     unit = UnitOfMeasure::METRE;
2663                 }
2664                 return CartesianCS::createEastingNorthing(unit);
2665             } else if (axisCount == 1) {
2666                 csTypeCStr = "vertical";
2667             } else if (axisCount == 2) {
2668                 csTypeCStr = "Cartesian";
2669             } else {
2670                 throw ParsingException(
2671                     "buildCS: unexpected AXIS count for LOCAL_CS");
2672             }
2673         } else if (ci_equal(parentNodeName, WKTConstants::BASEPARAMCRS)) {
2674             csTypeCStr = "parametric";
2675             if (axisCount == 0) {
2676                 auto unit =
2677                     buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2678                 if (unit == UnitOfMeasure::NONE) {
2679                     unit = UnitOfMeasure("unknown", 1,
2680                                          UnitOfMeasure::Type::PARAMETRIC);
2681                 }
2682                 return ParametricCS::create(
2683                     emptyPropertyMap,
2684                     CoordinateSystemAxis::create(
2685                         PropertyMap().set(IdentifiedObject::NAME_KEY,
2686                                           "unknown parametric"),
2687                         std::string(), AxisDirection::UNSPECIFIED, unit));
2688             }
2689         } else if (ci_equal(parentNodeName, WKTConstants::BASETIMECRS)) {
2690             csTypeCStr = "temporal";
2691             if (axisCount == 0) {
2692                 auto unit =
2693                     buildUnitInSubNode(parentNode, UnitOfMeasure::Type::TIME);
2694                 if (unit == UnitOfMeasure::NONE) {
2695                     unit =
2696                         UnitOfMeasure("unknown", 1, UnitOfMeasure::Type::TIME);
2697                 }
2698                 return DateTimeTemporalCS::create(
2699                     emptyPropertyMap,
2700                     CoordinateSystemAxis::create(
2701                         PropertyMap().set(IdentifiedObject::NAME_KEY,
2702                                           "unknown temporal"),
2703                         std::string(), AxisDirection::FUTURE, unit));
2704             }
2705         } else {
2706             // Shouldn't happen normally
2707             throw ParsingException(
2708                 concat("buildCS: unexpected parent node: ", parentNodeName));
2709         }
2710         csType = csTypeCStr;
2711     }
2712 
2713     if (axisCount != 1 && axisCount != 2 && axisCount != 3) {
2714         throw buildParsingExceptionInvalidAxisCount(csType);
2715     }
2716     if (numberOfAxis != axisCount) {
2717         throw ParsingException("buildCS: declared number of axis by CS node "
2718                                "and number of AXIS are inconsistent");
2719     }
2720 
2721     const auto unitType =
2722         ci_equal(csType, "ellipsoidal")
2723             ? UnitOfMeasure::Type::ANGULAR
2724             : ci_equal(csType, "ordinal")
2725                   ? UnitOfMeasure::Type::NONE
2726                   : ci_equal(csType, "parametric")
2727                         ? UnitOfMeasure::Type::PARAMETRIC
2728                         : ci_equal(csType, "Cartesian") ||
2729                                   ci_equal(csType, "vertical")
2730                               ? UnitOfMeasure::Type::LINEAR
2731                               : (ci_equal(csType, "temporal") ||
2732                                  ci_equal(csType, "TemporalDateTime") ||
2733                                  ci_equal(csType, "TemporalCount") ||
2734                                  ci_equal(csType, "TemporalMeasure"))
2735                                     ? UnitOfMeasure::Type::TIME
2736                                     : UnitOfMeasure::Type::UNKNOWN;
2737     UnitOfMeasure unit = buildUnitInSubNode(parentNode, unitType);
2738 
2739     std::vector<CoordinateSystemAxisNNPtr> axisList;
2740     for (int i = 0; i < axisCount; i++) {
2741         axisList.emplace_back(
2742             buildAxis(parentNode->GP()->lookForChild(WKTConstants::AXIS, i),
2743                       unit, unitType, isGeocentric, i + 1));
2744     };
2745 
2746     const PropertyMap &csMap = emptyPropertyMap;
2747     if (ci_equal(csType, "ellipsoidal")) {
2748         if (axisCount == 2) {
2749             return EllipsoidalCS::create(csMap, axisList[0], axisList[1]);
2750         } else if (axisCount == 3) {
2751             return EllipsoidalCS::create(csMap, axisList[0], axisList[1],
2752                                          axisList[2]);
2753         }
2754     } else if (ci_equal(csType, "Cartesian")) {
2755         if (axisCount == 2) {
2756             return CartesianCS::create(csMap, axisList[0], axisList[1]);
2757         } else if (axisCount == 3) {
2758             return CartesianCS::create(csMap, axisList[0], axisList[1],
2759                                        axisList[2]);
2760         }
2761     } else if (ci_equal(csType, "vertical")) {
2762         if (axisCount == 1) {
2763             return VerticalCS::create(csMap, axisList[0]);
2764         }
2765     } else if (ci_equal(csType, "spherical")) {
2766         if (axisCount == 3) {
2767             return SphericalCS::create(csMap, axisList[0], axisList[1],
2768                                        axisList[2]);
2769         }
2770     } else if (ci_equal(csType, "ordinal")) { // WKT2-2019
2771         return OrdinalCS::create(csMap, axisList);
2772     } else if (ci_equal(csType, "parametric")) {
2773         if (axisCount == 1) {
2774             return ParametricCS::create(csMap, axisList[0]);
2775         }
2776     } else if (ci_equal(csType, "temporal")) { // WKT2-2015
2777         if (axisCount == 1) {
2778             if (isNull(
2779                     parentNode->GP()->lookForChild(WKTConstants::TIMEUNIT)) &&
2780                 isNull(parentNode->GP()->lookForChild(WKTConstants::UNIT))) {
2781                 return DateTimeTemporalCS::create(csMap, axisList[0]);
2782             } else {
2783                 // Default to TemporalMeasureCS
2784                 // TemporalCount could also be possible
2785                 return TemporalMeasureCS::create(csMap, axisList[0]);
2786             }
2787         }
2788     } else if (ci_equal(csType, "TemporalDateTime")) { // WKT2-2019
2789         if (axisCount == 1) {
2790             return DateTimeTemporalCS::create(csMap, axisList[0]);
2791         }
2792     } else if (ci_equal(csType, "TemporalCount")) { // WKT2-2019
2793         if (axisCount == 1) {
2794             return TemporalCountCS::create(csMap, axisList[0]);
2795         }
2796     } else if (ci_equal(csType, "TemporalMeasure")) { // WKT2-2019
2797         if (axisCount == 1) {
2798             return TemporalMeasureCS::create(csMap, axisList[0]);
2799         }
2800     } else {
2801         throw ParsingException(concat("unhandled CS type: ", csType));
2802     }
2803     throw buildParsingExceptionInvalidAxisCount(csType);
2804 }
2805 
2806 // ---------------------------------------------------------------------------
2807 
2808 std::string
getExtensionProj4(const WKTNode::Private * nodeP)2809 WKTParser::Private::getExtensionProj4(const WKTNode::Private *nodeP) {
2810     auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
2811     const auto &extensionChildren = extensionNode->GP()->children();
2812     if (extensionChildren.size() == 2) {
2813         if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
2814             return stripQuotes(extensionChildren[1]);
2815         }
2816     }
2817     return std::string();
2818 }
2819 
2820 // ---------------------------------------------------------------------------
2821 
addExtensionProj4ToProp(const WKTNode::Private * nodeP,PropertyMap & props)2822 void WKTParser::Private::addExtensionProj4ToProp(const WKTNode::Private *nodeP,
2823                                                  PropertyMap &props) {
2824     const auto extensionProj4(getExtensionProj4(nodeP));
2825     if (!extensionProj4.empty()) {
2826         props.set("EXTENSION_PROJ4", extensionProj4);
2827     }
2828 }
2829 
2830 // ---------------------------------------------------------------------------
2831 
2832 GeodeticCRSNNPtr
buildGeodeticCRS(const WKTNodeNNPtr & node)2833 WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node) {
2834     const auto *nodeP = node->GP();
2835     auto &datumNode = nodeP->lookForChild(
2836         WKTConstants::DATUM, WKTConstants::GEODETICDATUM, WKTConstants::TRF);
2837     auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE);
2838     if (isNull(datumNode) && isNull(ensembleNode)) {
2839         throw ParsingException("Missing DATUM or ENSEMBLE node");
2840     }
2841 
2842     // Do that now so that esriStyle_ can be set before buildPrimeMeridian()
2843     auto props = buildProperties(node);
2844 
2845     auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC);
2846 
2847     auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
2848     const auto &nodeName = nodeP->value();
2849     if (isNull(csNode) && !ci_equal(nodeName, WKTConstants::GEOGCS) &&
2850         !ci_equal(nodeName, WKTConstants::GEOCCS) &&
2851         !ci_equal(nodeName, WKTConstants::BASEGEODCRS) &&
2852         !ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) {
2853         ThrowMissing(WKTConstants::CS_);
2854     }
2855 
2856     auto &primeMeridianNode =
2857         nodeP->lookForChild(WKTConstants::PRIMEM, WKTConstants::PRIMEMERIDIAN);
2858     if (isNull(primeMeridianNode)) {
2859         // PRIMEM is required in WKT1
2860         if (ci_equal(nodeName, WKTConstants::GEOGCS) ||
2861             ci_equal(nodeName, WKTConstants::GEOCCS)) {
2862             emitRecoverableWarning(nodeName + " should have a PRIMEM node");
2863         }
2864     }
2865 
2866     auto angularUnit =
2867         buildUnitInSubNode(node, ci_equal(nodeName, WKTConstants::GEOGCS)
2868                                      ? UnitOfMeasure::Type::ANGULAR
2869                                      : UnitOfMeasure::Type::UNKNOWN);
2870     if (angularUnit.type() != UnitOfMeasure::Type::ANGULAR) {
2871         angularUnit = UnitOfMeasure::NONE;
2872     }
2873 
2874     auto primeMeridian =
2875         !isNull(primeMeridianNode)
2876             ? buildPrimeMeridian(primeMeridianNode, angularUnit)
2877             : PrimeMeridian::GREENWICH;
2878     if (angularUnit == UnitOfMeasure::NONE) {
2879         angularUnit = primeMeridian->longitude().unit();
2880     }
2881 
2882     addExtensionProj4ToProp(nodeP, props);
2883 
2884     // No explicit AXIS node ? (WKT1)
2885     if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) {
2886         props.set("IMPLICIT_CS", true);
2887     }
2888 
2889     auto datum =
2890         !isNull(datumNode)
2891             ? buildGeodeticReferenceFrame(datumNode, primeMeridian, dynamicNode)
2892                   .as_nullable()
2893             : nullptr;
2894     auto datumEnsemble =
2895         !isNull(ensembleNode)
2896             ? buildDatumEnsemble(ensembleNode, primeMeridian, true)
2897                   .as_nullable()
2898             : nullptr;
2899     auto cs = buildCS(csNode, node, angularUnit);
2900 
2901     // If there's no CS[] node, typically for a BASEGEODCRS of a projected CRS,
2902     // in a few rare cases, this might be a Geocentric CRS, and thus a
2903     // Cartesian CS, and not the ellipsoidalCS we assumed above. The only way
2904     // to figure that is to resolve the CRS from its code...
2905     if (isNull(csNode) && dbContext_ &&
2906         ci_equal(nodeName, WKTConstants::BASEGEODCRS)) {
2907         const auto &nodeChildren = nodeP->children();
2908         for (const auto &subNode : nodeChildren) {
2909             const auto &subNodeName(subNode->GP()->value());
2910             if (ci_equal(subNodeName, WKTConstants::ID) ||
2911                 ci_equal(subNodeName, WKTConstants::AUTHORITY)) {
2912                 auto id = buildId(subNode, true, false);
2913                 if (id) {
2914                     try {
2915                         auto authFactory = AuthorityFactory::create(
2916                             NN_NO_CHECK(dbContext_), *id->codeSpace());
2917                         auto dbCRS = authFactory->createGeodeticCRS(id->code());
2918                         cs = dbCRS->coordinateSystem();
2919                     } catch (const util::Exception &) {
2920                     }
2921                 }
2922             }
2923         }
2924     }
2925 
2926     auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs);
2927     if (ellipsoidalCS) {
2928         if (ci_equal(nodeName, WKTConstants::GEOCCS)) {
2929             throw ParsingException("ellipsoidal CS not expected in GEOCCS");
2930         }
2931         try {
2932             auto crs = GeographicCRS::create(props, datum, datumEnsemble,
2933                                              NN_NO_CHECK(ellipsoidalCS));
2934             // In case of missing CS node, or to check it, query the coordinate
2935             // system from the DB if possible (typically for the baseCRS of a
2936             // ProjectedCRS)
2937             if (!crs->identifiers().empty() && dbContext_) {
2938                 GeographicCRSPtr dbCRS;
2939                 try {
2940                     const auto &id = crs->identifiers()[0];
2941                     auto authFactory = AuthorityFactory::create(
2942                         NN_NO_CHECK(dbContext_), *id->codeSpace());
2943                     dbCRS = authFactory->createGeographicCRS(id->code())
2944                                 .as_nullable();
2945                 } catch (const util::Exception &) {
2946                 }
2947                 if (dbCRS &&
2948                     (!isNull(csNode) ||
2949                      node->countChildrenOfName(WKTConstants::AXIS) != 0) &&
2950                     !ellipsoidalCS->_isEquivalentTo(
2951                         dbCRS->coordinateSystem().get(),
2952                         util::IComparable::Criterion::EQUIVALENT)) {
2953                     emitRecoverableWarning(
2954                         "Coordinate system of GeographicCRS in the WKT "
2955                         "definition is different from the one of the "
2956                         "authority. Unsetting the identifier to avoid "
2957                         "confusion");
2958                     props.unset(Identifier::CODESPACE_KEY);
2959                     props.unset(Identifier::AUTHORITY_KEY);
2960                     props.unset(IdentifiedObject::IDENTIFIERS_KEY);
2961                     crs = GeographicCRS::create(props, datum, datumEnsemble,
2962                                                 NN_NO_CHECK(ellipsoidalCS));
2963                 } else if (dbCRS) {
2964                     crs = GeographicCRS::create(props, datum, datumEnsemble,
2965                                                 dbCRS->coordinateSystem());
2966                 }
2967             }
2968             return crs;
2969         } catch (const util::Exception &e) {
2970             throw ParsingException(std::string("buildGeodeticCRS: ") +
2971                                    e.what());
2972         }
2973     } else if (ci_equal(nodeName, WKTConstants::GEOGCRS) ||
2974                ci_equal(nodeName, WKTConstants::GEOGRAPHICCRS) ||
2975                ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) {
2976         // This is a WKT2-2019 GeographicCRS. An ellipsoidal CS is expected
2977         throw ParsingException(concat("ellipsoidal CS expected, but found ",
2978                                       cs->getWKT2Type(true)));
2979     }
2980 
2981     auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
2982     if (cartesianCS) {
2983         if (cartesianCS->axisList().size() != 3) {
2984             throw ParsingException(
2985                 "Cartesian CS for a GeodeticCRS should have 3 axis");
2986         }
2987         try {
2988             return GeodeticCRS::create(props, datum, datumEnsemble,
2989                                        NN_NO_CHECK(cartesianCS));
2990         } catch (const util::Exception &e) {
2991             throw ParsingException(std::string("buildGeodeticCRS: ") +
2992                                    e.what());
2993         }
2994     }
2995 
2996     auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
2997     if (sphericalCS) {
2998         try {
2999             return GeodeticCRS::create(props, datum, datumEnsemble,
3000                                        NN_NO_CHECK(sphericalCS));
3001         } catch (const util::Exception &e) {
3002             throw ParsingException(std::string("buildGeodeticCRS: ") +
3003                                    e.what());
3004         }
3005     }
3006 
3007     throw ParsingException(
3008         concat("unhandled CS type: ", cs->getWKT2Type(true)));
3009 }
3010 
3011 // ---------------------------------------------------------------------------
3012 
buildDerivedGeodeticCRS(const WKTNodeNNPtr & node)3013 CRSNNPtr WKTParser::Private::buildDerivedGeodeticCRS(const WKTNodeNNPtr &node) {
3014     const auto *nodeP = node->GP();
3015     auto &baseGeodCRSNode = nodeP->lookForChild(WKTConstants::BASEGEODCRS,
3016                                                 WKTConstants::BASEGEOGCRS);
3017     // given the constraints enforced on calling code path
3018     assert(!isNull(baseGeodCRSNode));
3019 
3020     auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode);
3021 
3022     auto &derivingConversionNode =
3023         nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
3024     if (isNull(derivingConversionNode)) {
3025         ThrowMissing(WKTConstants::DERIVINGCONVERSION);
3026     }
3027     auto derivingConversion = buildConversion(
3028         derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
3029 
3030     auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
3031     if (isNull(csNode)) {
3032         ThrowMissing(WKTConstants::CS_);
3033     }
3034     auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
3035 
3036     auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs);
3037     if (ellipsoidalCS) {
3038         return DerivedGeographicCRS::create(buildProperties(node), baseGeodCRS,
3039                                             derivingConversion,
3040                                             NN_NO_CHECK(ellipsoidalCS));
3041     } else if (ci_equal(nodeP->value(), WKTConstants::GEOGCRS)) {
3042         // This is a WKT2-2019 GeographicCRS. An ellipsoidal CS is expected
3043         throw ParsingException(concat("ellipsoidal CS expected, but found ",
3044                                       cs->getWKT2Type(true)));
3045     }
3046 
3047     auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
3048     if (cartesianCS) {
3049         if (cartesianCS->axisList().size() != 3) {
3050             throw ParsingException(
3051                 "Cartesian CS for a GeodeticCRS should have 3 axis");
3052         }
3053         return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS,
3054                                           derivingConversion,
3055                                           NN_NO_CHECK(cartesianCS));
3056     }
3057 
3058     auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
3059     if (sphericalCS) {
3060         return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS,
3061                                           derivingConversion,
3062                                           NN_NO_CHECK(sphericalCS));
3063     }
3064 
3065     throw ParsingException(
3066         concat("unhandled CS type: ", cs->getWKT2Type(true)));
3067 }
3068 
3069 // ---------------------------------------------------------------------------
3070 
guessUnitForParameter(const std::string & paramName,const UnitOfMeasure & defaultLinearUnit,const UnitOfMeasure & defaultAngularUnit)3071 UnitOfMeasure WKTParser::Private::guessUnitForParameter(
3072     const std::string &paramName, const UnitOfMeasure &defaultLinearUnit,
3073     const UnitOfMeasure &defaultAngularUnit) {
3074     UnitOfMeasure unit;
3075     // scale must be first because of 'Scale factor on pseudo standard parallel'
3076     if (ci_find(paramName, "scale") != std::string::npos ||
3077         ci_find(paramName, "scaling factor") != std::string::npos) {
3078         unit = UnitOfMeasure::SCALE_UNITY;
3079     } else if (ci_find(paramName, "latitude") != std::string::npos ||
3080                ci_find(paramName, "longitude") != std::string::npos ||
3081                ci_find(paramName, "meridian") != std::string::npos ||
3082                ci_find(paramName, "parallel") != std::string::npos ||
3083                ci_find(paramName, "azimuth") != std::string::npos ||
3084                ci_find(paramName, "angle") != std::string::npos ||
3085                ci_find(paramName, "heading") != std::string::npos ||
3086                ci_find(paramName, "rotation") != std::string::npos) {
3087         unit = defaultAngularUnit;
3088     } else if (ci_find(paramName, "easting") != std::string::npos ||
3089                ci_find(paramName, "northing") != std::string::npos ||
3090                ci_find(paramName, "height") != std::string::npos) {
3091         unit = defaultLinearUnit;
3092     }
3093     return unit;
3094 }
3095 
3096 // ---------------------------------------------------------------------------
3097 
consumeParameters(const WKTNodeNNPtr & node,bool isAbridged,std::vector<OperationParameterNNPtr> & parameters,std::vector<ParameterValueNNPtr> & values,const UnitOfMeasure & defaultLinearUnit,const UnitOfMeasure & defaultAngularUnit)3098 void WKTParser::Private::consumeParameters(
3099     const WKTNodeNNPtr &node, bool isAbridged,
3100     std::vector<OperationParameterNNPtr> &parameters,
3101     std::vector<ParameterValueNNPtr> &values,
3102     const UnitOfMeasure &defaultLinearUnit,
3103     const UnitOfMeasure &defaultAngularUnit) {
3104     for (const auto &childNode : node->GP()->children()) {
3105         const auto &childNodeChildren = childNode->GP()->children();
3106         if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
3107             if (childNodeChildren.size() < 2) {
3108                 ThrowNotEnoughChildren(childNode->GP()->value());
3109             }
3110             parameters.push_back(
3111                 OperationParameter::create(buildProperties(childNode)));
3112             const auto &paramValue = childNodeChildren[1]->GP()->value();
3113             if (!paramValue.empty() && paramValue[0] == '"') {
3114                 values.push_back(
3115                     ParameterValue::create(stripQuotes(childNodeChildren[1])));
3116             } else {
3117                 try {
3118                     double val = asDouble(childNodeChildren[1]);
3119                     auto unit = buildUnitInSubNode(childNode);
3120                     if (unit == UnitOfMeasure::NONE) {
3121                         const auto &paramName =
3122                             childNodeChildren[0]->GP()->value();
3123                         unit = guessUnitForParameter(
3124                             paramName, defaultLinearUnit, defaultAngularUnit);
3125                     }
3126 
3127                     if (isAbridged) {
3128                         const auto &paramName = parameters.back()->nameStr();
3129                         int paramEPSGCode = 0;
3130                         const auto &paramIds = parameters.back()->identifiers();
3131                         if (paramIds.size() == 1 &&
3132                             ci_equal(*(paramIds[0]->codeSpace()),
3133                                      Identifier::EPSG)) {
3134                             paramEPSGCode = ::atoi(paramIds[0]->code().c_str());
3135                         }
3136                         const common::UnitOfMeasure *pUnit = nullptr;
3137                         if (OperationParameterValue::convertFromAbridged(
3138                                 paramName, val, pUnit, paramEPSGCode)) {
3139                             unit = *pUnit;
3140                             parameters.back() = OperationParameter::create(
3141                                 buildProperties(childNode)
3142                                     .set(Identifier::CODESPACE_KEY,
3143                                          Identifier::EPSG)
3144                                     .set(Identifier::CODE_KEY, paramEPSGCode));
3145                         }
3146                     }
3147 
3148                     values.push_back(
3149                         ParameterValue::create(Measure(val, unit)));
3150                 } catch (const std::exception &) {
3151                     throw ParsingException(concat(
3152                         "unhandled parameter value type : ", paramValue));
3153                 }
3154             }
3155         } else if (ci_equal(childNode->GP()->value(),
3156                             WKTConstants::PARAMETERFILE)) {
3157             if (childNodeChildren.size() < 2) {
3158                 ThrowNotEnoughChildren(childNode->GP()->value());
3159             }
3160             parameters.push_back(
3161                 OperationParameter::create(buildProperties(childNode)));
3162             values.push_back(ParameterValue::createFilename(
3163                 stripQuotes(childNodeChildren[1])));
3164         }
3165     }
3166 }
3167 
3168 // ---------------------------------------------------------------------------
3169 
3170 ConversionNNPtr
buildConversion(const WKTNodeNNPtr & node,const UnitOfMeasure & defaultLinearUnit,const UnitOfMeasure & defaultAngularUnit)3171 WKTParser::Private::buildConversion(const WKTNodeNNPtr &node,
3172                                     const UnitOfMeasure &defaultLinearUnit,
3173                                     const UnitOfMeasure &defaultAngularUnit) {
3174     auto &methodNode = node->GP()->lookForChild(WKTConstants::METHOD,
3175                                                 WKTConstants::PROJECTION);
3176     if (isNull(methodNode)) {
3177         ThrowMissing(WKTConstants::METHOD);
3178     }
3179     if (methodNode->GP()->childrenSize() == 0) {
3180         ThrowNotEnoughChildren(WKTConstants::METHOD);
3181     }
3182 
3183     std::vector<OperationParameterNNPtr> parameters;
3184     std::vector<ParameterValueNNPtr> values;
3185     consumeParameters(node, false, parameters, values, defaultLinearUnit,
3186                       defaultAngularUnit);
3187 
3188     auto &convProps = buildProperties(node);
3189     auto &methodProps = buildProperties(methodNode);
3190     std::string convName;
3191     std::string methodName;
3192     if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) &&
3193         methodProps.getStringValue(IdentifiedObject::NAME_KEY, methodName) &&
3194         starts_with(convName, "Inverse of ") &&
3195         starts_with(methodName, "Inverse of ")) {
3196 
3197         auto &invConvProps = buildProperties(node, true);
3198         auto &invMethodProps = buildProperties(methodNode, true);
3199         return NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(
3200             Conversion::create(invConvProps, invMethodProps, parameters, values)
3201                 ->inverse()));
3202     }
3203     return Conversion::create(convProps, methodProps, parameters, values);
3204 }
3205 
3206 // ---------------------------------------------------------------------------
3207 
3208 TransformationNNPtr
buildCoordinateOperation(const WKTNodeNNPtr & node)3209 WKTParser::Private::buildCoordinateOperation(const WKTNodeNNPtr &node) {
3210     const auto *nodeP = node->GP();
3211     auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD);
3212     if (isNull(methodNode)) {
3213         ThrowMissing(WKTConstants::METHOD);
3214     }
3215     if (methodNode->GP()->childrenSize() == 0) {
3216         ThrowNotEnoughChildren(WKTConstants::METHOD);
3217     }
3218 
3219     auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
3220     if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) {
3221         ThrowMissing(WKTConstants::SOURCECRS);
3222     }
3223     auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
3224     if (!sourceCRS) {
3225         throw ParsingException("Invalid content in SOURCECRS node");
3226     }
3227 
3228     auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
3229     if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) {
3230         ThrowMissing(WKTConstants::TARGETCRS);
3231     }
3232     auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]);
3233     if (!targetCRS) {
3234         throw ParsingException("Invalid content in TARGETCRS node");
3235     }
3236 
3237     auto &interpolationCRSNode =
3238         nodeP->lookForChild(WKTConstants::INTERPOLATIONCRS);
3239     CRSPtr interpolationCRS;
3240     if (/*!isNull(interpolationCRSNode) && */ interpolationCRSNode->GP()
3241             ->childrenSize() == 1) {
3242         interpolationCRS = buildCRS(interpolationCRSNode->GP()->children()[0]);
3243     }
3244 
3245     std::vector<OperationParameterNNPtr> parameters;
3246     std::vector<ParameterValueNNPtr> values;
3247     auto defaultLinearUnit = UnitOfMeasure::NONE;
3248     auto defaultAngularUnit = UnitOfMeasure::NONE;
3249     consumeParameters(node, false, parameters, values, defaultLinearUnit,
3250                       defaultAngularUnit);
3251 
3252     std::vector<PositionalAccuracyNNPtr> accuracies;
3253     auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY);
3254     if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) {
3255         accuracies.push_back(PositionalAccuracy::create(
3256             stripQuotes(accuracyNode->GP()->children()[0])));
3257     }
3258 
3259     return Transformation::create(buildProperties(node), NN_NO_CHECK(sourceCRS),
3260                                   NN_NO_CHECK(targetCRS), interpolationCRS,
3261                                   buildProperties(methodNode), parameters,
3262                                   values, accuracies);
3263 }
3264 
3265 // ---------------------------------------------------------------------------
3266 
3267 ConcatenatedOperationNNPtr
buildConcatenatedOperation(const WKTNodeNNPtr & node)3268 WKTParser::Private::buildConcatenatedOperation(const WKTNodeNNPtr &node) {
3269 
3270     const auto *nodeP = node->GP();
3271     auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
3272     if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) {
3273         ThrowMissing(WKTConstants::SOURCECRS);
3274     }
3275     auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
3276     if (!sourceCRS) {
3277         throw ParsingException("Invalid content in SOURCECRS node");
3278     }
3279 
3280     auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
3281     if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) {
3282         ThrowMissing(WKTConstants::TARGETCRS);
3283     }
3284     auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]);
3285     if (!targetCRS) {
3286         throw ParsingException("Invalid content in TARGETCRS node");
3287     }
3288 
3289     std::vector<CoordinateOperationNNPtr> operations;
3290     for (const auto &childNode : nodeP->children()) {
3291         if (ci_equal(childNode->GP()->value(), WKTConstants::STEP)) {
3292             if (childNode->GP()->childrenSize() != 1) {
3293                 throw ParsingException("Invalid content in STEP node");
3294             }
3295             auto op = nn_dynamic_pointer_cast<CoordinateOperation>(
3296                 build(childNode->GP()->children()[0]));
3297             if (!op) {
3298                 throw ParsingException("Invalid content in STEP node");
3299             }
3300             operations.emplace_back(NN_NO_CHECK(op));
3301         }
3302     }
3303 
3304     ConcatenatedOperation::fixStepsDirection(
3305         NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), operations);
3306 
3307     try {
3308         return ConcatenatedOperation::create(
3309             buildProperties(node), operations,
3310             std::vector<PositionalAccuracyNNPtr>());
3311     } catch (const InvalidOperation &e) {
3312         throw ParsingException(
3313             std::string("Cannot build concatenated operation: ") + e.what());
3314     }
3315 }
3316 
3317 // ---------------------------------------------------------------------------
3318 
hasWebMercPROJ4String(const WKTNodeNNPtr & projCRSNode,const WKTNodeNNPtr & projectionNode)3319 bool WKTParser::Private::hasWebMercPROJ4String(
3320     const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode) {
3321     if (projectionNode->GP()->childrenSize() == 0) {
3322         ThrowNotEnoughChildren(WKTConstants::PROJECTION);
3323     }
3324     const std::string wkt1ProjectionName =
3325         stripQuotes(projectionNode->GP()->children()[0]);
3326 
3327     auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION);
3328 
3329     if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
3330                                                "Mercator_1SP") &&
3331         projCRSNode->countChildrenOfName("center_latitude") == 0) {
3332 
3333         // Hack to detect the hacky way of encodign webmerc in GDAL WKT1
3334         // with a EXTENSION["PROJ4", "+proj=merc +a=6378137 +b=6378137
3335         // +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m
3336         // +nadgrids=@null +wktext +no_defs"] node
3337         if (extensionNode && extensionNode->GP()->childrenSize() == 2 &&
3338             ci_equal(stripQuotes(extensionNode->GP()->children()[0]),
3339                      "PROJ4")) {
3340             std::string projString =
3341                 stripQuotes(extensionNode->GP()->children()[1]);
3342             if (projString.find("+proj=merc") != std::string::npos &&
3343                 projString.find("+a=6378137") != std::string::npos &&
3344                 projString.find("+b=6378137") != std::string::npos &&
3345                 projString.find("+lon_0=0") != std::string::npos &&
3346                 projString.find("+x_0=0") != std::string::npos &&
3347                 projString.find("+y_0=0") != std::string::npos &&
3348                 projString.find("+nadgrids=@null") != std::string::npos &&
3349                 (projString.find("+lat_ts=") == std::string::npos ||
3350                  projString.find("+lat_ts=0") != std::string::npos) &&
3351                 (projString.find("+k=") == std::string::npos ||
3352                  projString.find("+k=1") != std::string::npos) &&
3353                 (projString.find("+units=") == std::string::npos ||
3354                  projString.find("+units=m") != std::string::npos)) {
3355                 return true;
3356             }
3357         }
3358     }
3359     return false;
3360 }
3361 
3362 // ---------------------------------------------------------------------------
3363 
3364 static const MethodMapping *
selectSphericalOrEllipsoidal(const MethodMapping * mapping,const GeodeticCRSNNPtr & baseGeodCRS)3365 selectSphericalOrEllipsoidal(const MethodMapping *mapping,
3366                              const GeodeticCRSNNPtr &baseGeodCRS) {
3367     if (mapping->epsg_code ==
3368             EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL ||
3369         mapping->epsg_code == EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA) {
3370         mapping = getMapping(
3371             baseGeodCRS->ellipsoid()->isSphere()
3372                 ? EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL
3373                 : EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA);
3374     } else if (mapping->epsg_code ==
3375                    EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL ||
3376                mapping->epsg_code ==
3377                    EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) {
3378         mapping = getMapping(
3379             baseGeodCRS->ellipsoid()->isSphere()
3380                 ? EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL
3381                 : EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA);
3382     } else if (mapping->epsg_code ==
3383                    EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL ||
3384                mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL) {
3385         mapping =
3386             getMapping(baseGeodCRS->ellipsoid()->isSphere()
3387                            ? EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL
3388                            : EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL);
3389     }
3390     return mapping;
3391 }
3392 
3393 // ---------------------------------------------------------------------------
3394 
buildProjectionFromESRI(const GeodeticCRSNNPtr & baseGeodCRS,const WKTNodeNNPtr & projCRSNode,const WKTNodeNNPtr & projectionNode,const UnitOfMeasure & defaultLinearUnit,const UnitOfMeasure & defaultAngularUnit)3395 ConversionNNPtr WKTParser::Private::buildProjectionFromESRI(
3396     const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
3397     const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
3398     const UnitOfMeasure &defaultAngularUnit) {
3399     const std::string esriProjectionName =
3400         stripQuotes(projectionNode->GP()->children()[0]);
3401 
3402     // Lambert_Conformal_Conic or Krovak may map to different WKT2 methods
3403     // depending
3404     // on the parameters / their values
3405     const auto esriMappings = getMappingsFromESRI(esriProjectionName);
3406     if (esriMappings.empty()) {
3407         return buildProjectionStandard(baseGeodCRS, projCRSNode, projectionNode,
3408                                        defaultLinearUnit, defaultAngularUnit);
3409     }
3410 
3411     struct ci_less_struct {
3412         bool operator()(const std::string &lhs, const std::string &rhs) const
3413             noexcept {
3414             return ci_less(lhs, rhs);
3415         }
3416     };
3417 
3418     // Build a map of present parameters
3419     std::map<std::string, std::string, ci_less_struct> mapParamNameToValue;
3420     for (const auto &childNode : projCRSNode->GP()->children()) {
3421         if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
3422             const auto &childNodeChildren = childNode->GP()->children();
3423             if (childNodeChildren.size() < 2) {
3424                 ThrowNotEnoughChildren(WKTConstants::PARAMETER);
3425             }
3426             const std::string parameterName(stripQuotes(childNodeChildren[0]));
3427             const auto &paramValue = childNodeChildren[1]->GP()->value();
3428             mapParamNameToValue[parameterName] = paramValue;
3429         }
3430     }
3431 
3432     // Compare parameters present with the ones expected in the mapping
3433     const ESRIMethodMapping *esriMapping = nullptr;
3434     int bestMatchCount = -1;
3435     for (const auto &mapping : esriMappings) {
3436         int matchCount = 0;
3437         for (const auto *param = mapping->params; param->esri_name; ++param) {
3438             auto iter = mapParamNameToValue.find(param->esri_name);
3439             if (iter != mapParamNameToValue.end()) {
3440                 if (param->wkt2_name == nullptr) {
3441                     bool ok = true;
3442                     try {
3443                         if (io::asDouble(param->fixed_value) ==
3444                             io::asDouble(iter->second)) {
3445                             matchCount++;
3446                         } else {
3447                             ok = false;
3448                         }
3449                     } catch (const std::exception &) {
3450                         ok = false;
3451                     }
3452                     if (!ok) {
3453                         matchCount = -1;
3454                         break;
3455                     }
3456                 } else {
3457                     matchCount++;
3458                 }
3459             } else if (param->is_fixed_value) {
3460                 mapParamNameToValue[param->esri_name] = param->fixed_value;
3461             }
3462         }
3463         if (matchCount > bestMatchCount) {
3464             esriMapping = mapping;
3465             bestMatchCount = matchCount;
3466         }
3467     }
3468     if (esriMapping == nullptr) {
3469         return buildProjectionStandard(baseGeodCRS, projCRSNode, projectionNode,
3470                                        defaultLinearUnit, defaultAngularUnit);
3471     }
3472 
3473     std::map<std::string, const char *> mapWKT2NameToESRIName;
3474     for (const auto *param = esriMapping->params; param->esri_name; ++param) {
3475         if (param->wkt2_name) {
3476             mapWKT2NameToESRIName[param->wkt2_name] = param->esri_name;
3477         }
3478     }
3479 
3480     const char *projectionMethodWkt2Name = esriMapping->wkt2_name;
3481     if (ci_equal(esriProjectionName, "Krovak")) {
3482         const std::string projCRSName =
3483             stripQuotes(projCRSNode->GP()->children()[0]);
3484         if (projCRSName.find("_East_North") != std::string::npos) {
3485             projectionMethodWkt2Name = EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED;
3486         }
3487     }
3488 
3489     const auto *wkt2_mapping = getMapping(projectionMethodWkt2Name);
3490     if (ci_equal(esriProjectionName, "Stereographic")) {
3491         try {
3492             if (std::fabs(io::asDouble(
3493                     mapParamNameToValue["Latitude_Of_Origin"])) == 90.0) {
3494                 wkt2_mapping =
3495                     getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A);
3496             }
3497         } catch (const std::exception &) {
3498         }
3499     }
3500     assert(wkt2_mapping);
3501 
3502     wkt2_mapping = selectSphericalOrEllipsoidal(wkt2_mapping, baseGeodCRS);
3503 
3504     PropertyMap propertiesMethod;
3505     propertiesMethod.set(IdentifiedObject::NAME_KEY, wkt2_mapping->wkt2_name);
3506     if (wkt2_mapping->epsg_code != 0) {
3507         propertiesMethod.set(Identifier::CODE_KEY, wkt2_mapping->epsg_code);
3508         propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
3509     }
3510 
3511     std::vector<OperationParameterNNPtr> parameters;
3512     std::vector<ParameterValueNNPtr> values;
3513 
3514     if (wkt2_mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL &&
3515         ci_equal(esriProjectionName, "Plate_Carree")) {
3516         // Add a fixed  Latitude of 1st parallel = 0 so as to have all
3517         // parameters expected by Equidistant Cylindrical.
3518         mapWKT2NameToESRIName[EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL] =
3519             "Standard_Parallel_1";
3520         mapParamNameToValue["Standard_Parallel_1"] = "0";
3521     } else if ((wkt2_mapping->epsg_code ==
3522                     EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A ||
3523                 wkt2_mapping->epsg_code ==
3524                     EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) &&
3525                !ci_equal(esriProjectionName,
3526                          "Rectified_Skew_Orthomorphic_Natural_Origin") &&
3527                !ci_equal(esriProjectionName,
3528                          "Rectified_Skew_Orthomorphic_Center")) {
3529         // ESRI WKT lacks the angle to skew grid
3530         // Take it from the azimuth value
3531         mapWKT2NameToESRIName
3532             [EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID] = "Azimuth";
3533     }
3534 
3535     for (int i = 0; wkt2_mapping->params[i] != nullptr; i++) {
3536         const auto *paramMapping = wkt2_mapping->params[i];
3537 
3538         auto iter = mapWKT2NameToESRIName.find(paramMapping->wkt2_name);
3539         if (iter == mapWKT2NameToESRIName.end()) {
3540             continue;
3541         }
3542         const auto &esriParamName = iter->second;
3543         auto iter2 = mapParamNameToValue.find(esriParamName);
3544         auto mapParamNameToValueEnd = mapParamNameToValue.end();
3545         if (iter2 == mapParamNameToValueEnd) {
3546             // In case we don't find a direct match, try the aliases
3547             for (iter2 = mapParamNameToValue.begin();
3548                  iter2 != mapParamNameToValueEnd; ++iter2) {
3549                 if (areEquivalentParameters(iter2->first, esriParamName)) {
3550                     break;
3551                 }
3552             }
3553             if (iter2 == mapParamNameToValueEnd) {
3554                 continue;
3555             }
3556         }
3557 
3558         PropertyMap propertiesParameter;
3559         propertiesParameter.set(IdentifiedObject::NAME_KEY,
3560                                 paramMapping->wkt2_name);
3561         if (paramMapping->epsg_code != 0) {
3562             propertiesParameter.set(Identifier::CODE_KEY,
3563                                     paramMapping->epsg_code);
3564             propertiesParameter.set(Identifier::CODESPACE_KEY,
3565                                     Identifier::EPSG);
3566         }
3567         parameters.push_back(OperationParameter::create(propertiesParameter));
3568 
3569         try {
3570             double val = io::asDouble(iter2->second);
3571             auto unit = guessUnitForParameter(
3572                 paramMapping->wkt2_name, defaultLinearUnit, defaultAngularUnit);
3573             values.push_back(ParameterValue::create(Measure(val, unit)));
3574         } catch (const std::exception &) {
3575             throw ParsingException(
3576                 concat("unhandled parameter value type : ", iter2->second));
3577         }
3578     }
3579 
3580     return Conversion::create(
3581                PropertyMap().set(IdentifiedObject::NAME_KEY,
3582                                  esriProjectionName == "Gauss_Kruger"
3583                                      ? "unnnamed (Gauss Kruger)"
3584                                      : "unnamed"),
3585                propertiesMethod, parameters, values)
3586         ->identify();
3587 }
3588 
3589 // ---------------------------------------------------------------------------
3590 
buildProjection(const GeodeticCRSNNPtr & baseGeodCRS,const WKTNodeNNPtr & projCRSNode,const WKTNodeNNPtr & projectionNode,const UnitOfMeasure & defaultLinearUnit,const UnitOfMeasure & defaultAngularUnit)3591 ConversionNNPtr WKTParser::Private::buildProjection(
3592     const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
3593     const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
3594     const UnitOfMeasure &defaultAngularUnit) {
3595     if (projectionNode->GP()->childrenSize() == 0) {
3596         ThrowNotEnoughChildren(WKTConstants::PROJECTION);
3597     }
3598     if (esriStyle_) {
3599         return buildProjectionFromESRI(baseGeodCRS, projCRSNode, projectionNode,
3600                                        defaultLinearUnit, defaultAngularUnit);
3601     }
3602     return buildProjectionStandard(baseGeodCRS, projCRSNode, projectionNode,
3603                                    defaultLinearUnit, defaultAngularUnit);
3604 }
3605 
3606 // ---------------------------------------------------------------------------
3607 
3608 std::string
projectionGetParameter(const WKTNodeNNPtr & projCRSNode,const char * paramName)3609 WKTParser::Private::projectionGetParameter(const WKTNodeNNPtr &projCRSNode,
3610                                            const char *paramName) {
3611     for (const auto &childNode : projCRSNode->GP()->children()) {
3612         if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
3613             const auto &childNodeChildren = childNode->GP()->children();
3614             if (childNodeChildren.size() == 2 &&
3615                 metadata::Identifier::isEquivalentName(
3616                     stripQuotes(childNodeChildren[0]).c_str(), paramName)) {
3617                 return childNodeChildren[1]->GP()->value();
3618             }
3619         }
3620     }
3621     return std::string();
3622 }
3623 
3624 // ---------------------------------------------------------------------------
3625 
buildProjectionStandard(const GeodeticCRSNNPtr & baseGeodCRS,const WKTNodeNNPtr & projCRSNode,const WKTNodeNNPtr & projectionNode,const UnitOfMeasure & defaultLinearUnit,const UnitOfMeasure & defaultAngularUnit)3626 ConversionNNPtr WKTParser::Private::buildProjectionStandard(
3627     const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
3628     const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
3629     const UnitOfMeasure &defaultAngularUnit) {
3630     std::string wkt1ProjectionName =
3631         stripQuotes(projectionNode->GP()->children()[0]);
3632 
3633     std::vector<OperationParameterNNPtr> parameters;
3634     std::vector<ParameterValueNNPtr> values;
3635     bool tryToIdentifyWKT1Method = true;
3636 
3637     auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION);
3638     const auto &extensionChildren = extensionNode->GP()->children();
3639 
3640     bool gdal_3026_hack = false;
3641     if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
3642                                                "Mercator_1SP") &&
3643         projectionGetParameter(projCRSNode, "center_latitude").empty()) {
3644 
3645         // Hack for https://trac.osgeo.org/gdal/ticket/3026
3646         std::string lat0(
3647             projectionGetParameter(projCRSNode, "latitude_of_origin"));
3648         if (!lat0.empty() && lat0 != "0" && lat0 != "0.0") {
3649             wkt1ProjectionName = "Mercator_2SP";
3650             gdal_3026_hack = true;
3651         } else {
3652             // The latitude of origin, which should always be zero, is
3653             // missing
3654             // in GDAL WKT1, but provisionned in the EPSG Mercator_1SP
3655             // definition,
3656             // so add it manually.
3657             PropertyMap propertiesParameter;
3658             propertiesParameter.set(IdentifiedObject::NAME_KEY,
3659                                     "Latitude of natural origin");
3660             propertiesParameter.set(Identifier::CODE_KEY, 8801);
3661             propertiesParameter.set(Identifier::CODESPACE_KEY,
3662                                     Identifier::EPSG);
3663             parameters.push_back(
3664                 OperationParameter::create(propertiesParameter));
3665             values.push_back(
3666                 ParameterValue::create(Measure(0, UnitOfMeasure::DEGREE)));
3667         }
3668 
3669     } else if (metadata::Identifier::isEquivalentName(
3670                    wkt1ProjectionName.c_str(), "Polar_Stereographic")) {
3671         std::map<std::string, Measure> mapParameters;
3672         for (const auto &childNode : projCRSNode->GP()->children()) {
3673             const auto &childNodeChildren = childNode->GP()->children();
3674             if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) &&
3675                 childNodeChildren.size() == 2) {
3676                 const std::string wkt1ParameterName(
3677                     stripQuotes(childNodeChildren[0]));
3678                 try {
3679                     double val = asDouble(childNodeChildren[1]);
3680                     auto unit = guessUnitForParameter(wkt1ParameterName,
3681                                                       defaultLinearUnit,
3682                                                       defaultAngularUnit);
3683                     mapParameters.insert(std::pair<std::string, Measure>(
3684                         tolower(wkt1ParameterName), Measure(val, unit)));
3685                 } catch (const std::exception &) {
3686                 }
3687             }
3688         }
3689 
3690         Measure latitudeOfOrigin = mapParameters["latitude_of_origin"];
3691         Measure centralMeridian = mapParameters["central_meridian"];
3692         Measure scaleFactorFromMap = mapParameters["scale_factor"];
3693         Measure scaleFactor((scaleFactorFromMap.unit() == UnitOfMeasure::NONE)
3694                                 ? Measure(1.0, UnitOfMeasure::SCALE_UNITY)
3695                                 : scaleFactorFromMap);
3696         Measure falseEasting = mapParameters["false_easting"];
3697         Measure falseNorthing = mapParameters["false_northing"];
3698         if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
3699             scaleFactor.getSIValue() == 1.0) {
3700             return Conversion::createPolarStereographicVariantB(
3701                 PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
3702                 Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()),
3703                 Angle(centralMeridian.value(), centralMeridian.unit()),
3704                 Length(falseEasting.value(), falseEasting.unit()),
3705                 Length(falseNorthing.value(), falseNorthing.unit()));
3706         }
3707 
3708         if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
3709             std::fabs(std::fabs(latitudeOfOrigin.convertToUnit(
3710                           UnitOfMeasure::DEGREE)) -
3711                       90.0) < 1e-10) {
3712             return Conversion::createPolarStereographicVariantA(
3713                 PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
3714                 Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()),
3715                 Angle(centralMeridian.value(), centralMeridian.unit()),
3716                 Scale(scaleFactor.value(), scaleFactor.unit()),
3717                 Length(falseEasting.value(), falseEasting.unit()),
3718                 Length(falseNorthing.value(), falseNorthing.unit()));
3719         }
3720 
3721         tryToIdentifyWKT1Method = false;
3722         // Import GDAL PROJ4 extension nodes
3723     } else if (extensionChildren.size() == 2 &&
3724                ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
3725         std::string projString = stripQuotes(extensionChildren[1]);
3726         if (starts_with(projString, "+proj=")) {
3727             if (projString.find(" +type=crs") == std::string::npos) {
3728                 projString += " +type=crs";
3729             }
3730             try {
3731                 auto projObj =
3732                     PROJStringParser().createFromPROJString(projString);
3733                 auto projObjCrs =
3734                     nn_dynamic_pointer_cast<ProjectedCRS>(projObj);
3735                 if (projObjCrs) {
3736                     return projObjCrs->derivingConversion();
3737                 }
3738             } catch (const io::ParsingException &) {
3739             }
3740         }
3741     }
3742 
3743     std::string projectionName(wkt1ProjectionName);
3744     const MethodMapping *mapping =
3745         tryToIdentifyWKT1Method ? getMappingFromWKT1(projectionName) : nullptr;
3746     if (mapping) {
3747         mapping = selectSphericalOrEllipsoidal(mapping, baseGeodCRS);
3748     }
3749 
3750     // For Krovak, we need to look at axis to decide between the Krovak and
3751     // Krovak East-North Oriented methods
3752     if (ci_equal(projectionName, "Krovak") &&
3753         projCRSNode->countChildrenOfName(WKTConstants::AXIS) == 2 &&
3754         &buildAxis(
3755              projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 0),
3756              defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false,
3757              1)->direction() == &AxisDirection::SOUTH &&
3758         &buildAxis(
3759              projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 1),
3760              defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false,
3761              2)->direction() == &AxisDirection::WEST) {
3762         mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
3763     }
3764 
3765     PropertyMap propertiesMethod;
3766     if (mapping) {
3767         projectionName = mapping->wkt2_name;
3768         if (mapping->epsg_code != 0) {
3769             propertiesMethod.set(Identifier::CODE_KEY, mapping->epsg_code);
3770             propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
3771         }
3772     }
3773     propertiesMethod.set(IdentifiedObject::NAME_KEY, projectionName);
3774 
3775     std::vector<bool> foundParameters;
3776     if (mapping) {
3777         size_t countParams = 0;
3778         while (mapping->params[countParams] != nullptr) {
3779             ++countParams;
3780         }
3781         foundParameters.resize(countParams);
3782     }
3783     bool found2ndStdParallel = false;
3784     for (const auto &childNode : projCRSNode->GP()->children()) {
3785         if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
3786             const auto &childNodeChildren = childNode->GP()->children();
3787             if (childNodeChildren.size() < 2) {
3788                 ThrowNotEnoughChildren(WKTConstants::PARAMETER);
3789             }
3790             const auto &paramValue = childNodeChildren[1]->GP()->value();
3791 
3792             PropertyMap propertiesParameter;
3793             const std::string wkt1ParameterName(
3794                 stripQuotes(childNodeChildren[0]));
3795             std::string parameterName(wkt1ParameterName);
3796             if (gdal_3026_hack) {
3797                 if (ci_equal(parameterName, "latitude_of_origin")) {
3798                     parameterName = "standard_parallel_1";
3799                 } else if (ci_equal(parameterName, "scale_factor") &&
3800                            paramValue == "1") {
3801                     continue;
3802                 }
3803             }
3804             auto *paramMapping =
3805                 mapping ? getMappingFromWKT1(mapping, parameterName) : nullptr;
3806             if (mapping &&
3807                 mapping->epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
3808                 ci_equal(parameterName, "latitude_of_origin")) {
3809                 for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
3810                     if (mapping->params[idx]->epsg_code ==
3811                         EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) {
3812                         foundParameters[idx] = true;
3813                         break;
3814                     }
3815                 }
3816                 parameterName = EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN;
3817                 propertiesParameter.set(
3818                     Identifier::CODE_KEY,
3819                     EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
3820                 propertiesParameter.set(Identifier::CODESPACE_KEY,
3821                                         Identifier::EPSG);
3822             } else if (mapping && paramMapping) {
3823                 for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
3824                     if (mapping->params[idx] == paramMapping) {
3825                         foundParameters[idx] = true;
3826                         break;
3827                     }
3828                 }
3829                 parameterName = paramMapping->wkt2_name;
3830                 if (paramMapping->epsg_code != 0) {
3831                     propertiesParameter.set(Identifier::CODE_KEY,
3832                                             paramMapping->epsg_code);
3833                     propertiesParameter.set(Identifier::CODESPACE_KEY,
3834                                             Identifier::EPSG);
3835                 }
3836                 if (paramMapping->epsg_code ==
3837                     EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) {
3838                     found2ndStdParallel = true;
3839                 }
3840             }
3841             propertiesParameter.set(IdentifiedObject::NAME_KEY, parameterName);
3842             parameters.push_back(
3843                 OperationParameter::create(propertiesParameter));
3844             try {
3845                 double val = io::asDouble(paramValue);
3846                 auto unit = guessUnitForParameter(
3847                     wkt1ParameterName, defaultLinearUnit, defaultAngularUnit);
3848                 values.push_back(ParameterValue::create(Measure(val, unit)));
3849             } catch (const std::exception &) {
3850                 throw ParsingException(
3851                     concat("unhandled parameter value type : ", paramValue));
3852             }
3853         }
3854     }
3855 
3856     // Oracle WKT: make sure that the 2nd std parallel parameter is found to
3857     // select the LCC_2SP mapping
3858     if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
3859                                                "Lambert Conformal Conic") &&
3860         !found2ndStdParallel) {
3861         propertiesMethod.set(IdentifiedObject::NAME_KEY, wkt1ProjectionName);
3862     }
3863 
3864     // Add back important parameters that should normally be present, but
3865     // are sometimes missing. Currently we only deal with Scale factor at
3866     // natural origin. This is to avoid a default value of 0 to slip in later.
3867     // But such WKT should be considered invalid.
3868     if (mapping) {
3869         for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
3870             if (!foundParameters[idx] &&
3871                 mapping->params[idx]->epsg_code ==
3872                     EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) {
3873 
3874                 emitRecoverableWarning(
3875                     "The WKT string lacks a value "
3876                     "for " EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN
3877                     ". Default it to 1.");
3878 
3879                 PropertyMap propertiesParameter;
3880                 propertiesParameter.set(
3881                     Identifier::CODE_KEY,
3882                     EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
3883                 propertiesParameter.set(Identifier::CODESPACE_KEY,
3884                                         Identifier::EPSG);
3885                 propertiesParameter.set(
3886                     IdentifiedObject::NAME_KEY,
3887                     EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
3888                 parameters.push_back(
3889                     OperationParameter::create(propertiesParameter));
3890                 values.push_back(ParameterValue::create(
3891                     Measure(1.0, UnitOfMeasure::SCALE_UNITY)));
3892             }
3893         }
3894     }
3895 
3896     return Conversion::create(
3897                PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
3898                propertiesMethod, parameters, values)
3899         ->identify();
3900 }
3901 
3902 // ---------------------------------------------------------------------------
3903 
createPseudoMercator(const PropertyMap & props,const cs::CartesianCSNNPtr & cs)3904 static ProjectedCRSNNPtr createPseudoMercator(const PropertyMap &props,
3905                                               const cs::CartesianCSNNPtr &cs) {
3906     auto conversion = Conversion::createPopularVisualisationPseudoMercator(
3907         PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(0),
3908         Angle(0), Length(0), Length(0));
3909     return ProjectedCRS::create(props, GeographicCRS::EPSG_4326, conversion,
3910                                 cs);
3911 }
3912 
3913 // ---------------------------------------------------------------------------
3914 
3915 ProjectedCRSNNPtr
buildProjectedCRS(const WKTNodeNNPtr & node)3916 WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) {
3917 
3918     const auto *nodeP = node->GP();
3919     auto &conversionNode = nodeP->lookForChild(WKTConstants::CONVERSION);
3920     auto &projectionNode = nodeP->lookForChild(WKTConstants::PROJECTION);
3921     if (isNull(conversionNode) && isNull(projectionNode)) {
3922         ThrowMissing(WKTConstants::CONVERSION);
3923     }
3924 
3925     auto &baseGeodCRSNode =
3926         nodeP->lookForChild(WKTConstants::BASEGEODCRS,
3927                             WKTConstants::BASEGEOGCRS, WKTConstants::GEOGCS);
3928     if (isNull(baseGeodCRSNode)) {
3929         throw ParsingException(
3930             "Missing BASEGEODCRS / BASEGEOGCRS / GEOGCS node");
3931     }
3932     auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode);
3933 
3934     auto props = buildProperties(node);
3935 
3936     auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
3937     const auto &nodeValue = nodeP->value();
3938     if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::PROJCS) &&
3939         !ci_equal(nodeValue, WKTConstants::BASEPROJCRS)) {
3940         ThrowMissing(WKTConstants::CS_);
3941     }
3942     auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
3943     auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
3944 
3945     const std::string projCRSName = stripQuotes(nodeP->children()[0]);
3946     if (esriStyle_ && dbContext_) {
3947         // It is likely that the ESRI definition of EPSG:32661 (UPS North) &
3948         // EPSG:32761 (UPS South) uses the easting-northing order, instead
3949         // of the EPSG northing-easting order
3950         // so don't substitue names to avoid confusion.
3951         if (projCRSName == "UPS_North") {
3952             props.set(IdentifiedObject::NAME_KEY, "WGS 84 / UPS North (E,N)");
3953         } else if (projCRSName == "UPS_South") {
3954             props.set(IdentifiedObject::NAME_KEY, "WGS 84 / UPS South (E,N)");
3955         } else {
3956             std::string outTableName;
3957             std::string authNameFromAlias;
3958             std::string codeFromAlias;
3959             auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
3960                                                         std::string());
3961             auto officialName = authFactory->getOfficialNameFromAlias(
3962                 projCRSName, "projected_crs", "ESRI", false, outTableName,
3963                 authNameFromAlias, codeFromAlias);
3964             if (!officialName.empty()) {
3965                 // Special case for https://github.com/OSGeo/PROJ/issues/2086
3966                 // The name of the CRS to identify is
3967                 // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501
3968                 // whereas it should be
3969                 // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501_Feet
3970                 constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37;
3971                 if (projCRSName.find("_FIPS_") != std::string::npos &&
3972                     projCRSName.find("_Feet") == std::string::npos &&
3973                     std::fabs(
3974                         cartesianCS->axisList()[0]->unit().conversionToSI() -
3975                         US_FOOT_CONV_FACTOR) < 1e-10 * US_FOOT_CONV_FACTOR) {
3976                     auto officialNameFromFeet =
3977                         authFactory->getOfficialNameFromAlias(
3978                             projCRSName + "_Feet", "projected_crs", "ESRI",
3979                             false, outTableName, authNameFromAlias,
3980                             codeFromAlias);
3981                     if (!officialNameFromFeet.empty()) {
3982                         officialName = officialNameFromFeet;
3983                     }
3984                 }
3985 
3986                 props.set(IdentifiedObject::NAME_KEY, officialName);
3987             }
3988         }
3989     }
3990 
3991     if (isNull(conversionNode) && hasWebMercPROJ4String(node, projectionNode) &&
3992         cartesianCS) {
3993         toWGS84Parameters_.clear();
3994         return createPseudoMercator(props, NN_NO_CHECK(cartesianCS));
3995     }
3996 
3997     // WGS_84_Pseudo_Mercator: Particular case for corrupted ESRI WKT generated
3998     // by older GDAL versions
3999     // https://trac.osgeo.org/gdal/changeset/30732
4000     // WGS_1984_Web_Mercator: deprecated ESRI:102113
4001     if (cartesianCS && (metadata::Identifier::isEquivalentName(
4002                             projCRSName.c_str(), "WGS_84_Pseudo_Mercator") ||
4003                         metadata::Identifier::isEquivalentName(
4004                             projCRSName.c_str(), "WGS_1984_Web_Mercator"))) {
4005         toWGS84Parameters_.clear();
4006         return createPseudoMercator(props, NN_NO_CHECK(cartesianCS));
4007     }
4008 
4009     // For WKT2, if there is no explicit parameter unit, use metre for linear
4010     // units and degree for angular units
4011     auto linearUnit =
4012         !isNull(conversionNode)
4013             ? UnitOfMeasure::METRE
4014             : buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR);
4015     auto angularUnit =
4016         !isNull(conversionNode)
4017             ? UnitOfMeasure::DEGREE
4018             : baseGeodCRS->coordinateSystem()->axisList()[0]->unit();
4019 
4020     auto conversion =
4021         !isNull(conversionNode)
4022             ? buildConversion(conversionNode, linearUnit, angularUnit)
4023             : buildProjection(baseGeodCRS, node, projectionNode, linearUnit,
4024                               angularUnit);
4025 
4026     // No explicit AXIS node ? (WKT1)
4027     if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) {
4028         props.set("IMPLICIT_CS", true);
4029     }
4030 
4031     if (isNull(csNode) && node->countChildrenOfName(WKTConstants::AXIS) == 0) {
4032 
4033         const auto methodCode = conversion->method()->getEPSGCode();
4034         // Krovak south oriented ?
4035         if (methodCode == EPSG_CODE_METHOD_KROVAK) {
4036             cartesianCS =
4037                 CartesianCS::create(
4038                     PropertyMap(),
4039                     CoordinateSystemAxis::create(
4040                         util::PropertyMap().set(IdentifiedObject::NAME_KEY,
4041                                                 AxisName::Southing),
4042                         emptyString, AxisDirection::SOUTH, linearUnit),
4043                     CoordinateSystemAxis::create(
4044                         util::PropertyMap().set(IdentifiedObject::NAME_KEY,
4045                                                 AxisName::Westing),
4046                         emptyString, AxisDirection::WEST, linearUnit))
4047                     .as_nullable();
4048         } else if (methodCode ==
4049                        EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A ||
4050                    methodCode ==
4051                        EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) {
4052             // It is likely that the ESRI definition of EPSG:32661 (UPS North) &
4053             // EPSG:32761 (UPS South) uses the easting-northing order, instead
4054             // of the EPSG northing-easting order.
4055             // Same for WKT1_GDAL
4056             const double lat0 = conversion->parameterValueNumeric(
4057                 EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
4058                 common::UnitOfMeasure::DEGREE);
4059             if (std::fabs(lat0 - 90) < 1e-10) {
4060                 cartesianCS =
4061                     CartesianCS::createNorthPoleEastingSouthNorthingSouth(
4062                         linearUnit)
4063                         .as_nullable();
4064             } else if (std::fabs(lat0 - -90) < 1e-10) {
4065                 cartesianCS =
4066                     CartesianCS::createSouthPoleEastingNorthNorthingNorth(
4067                         linearUnit)
4068                         .as_nullable();
4069             }
4070         } else if (methodCode ==
4071                    EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
4072             const double lat_ts = conversion->parameterValueNumeric(
4073                 EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL,
4074                 common::UnitOfMeasure::DEGREE);
4075             if (lat_ts > 0) {
4076                 cartesianCS =
4077                     CartesianCS::createNorthPoleEastingSouthNorthingSouth(
4078                         linearUnit)
4079                         .as_nullable();
4080             } else if (lat_ts < 0) {
4081                 cartesianCS =
4082                     CartesianCS::createSouthPoleEastingNorthNorthingNorth(
4083                         linearUnit)
4084                         .as_nullable();
4085             }
4086         } else if (methodCode ==
4087                    EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
4088             cartesianCS =
4089                 CartesianCS::createWestingSouthing(linearUnit).as_nullable();
4090         }
4091     }
4092     if (!cartesianCS) {
4093         ThrowNotExpectedCSType("Cartesian");
4094     }
4095 
4096     if (cartesianCS->axisList().size() == 3 &&
4097         baseGeodCRS->coordinateSystem()->axisList().size() == 2) {
4098         baseGeodCRS = NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
4099             baseGeodCRS->promoteTo3D(std::string(), dbContext_)));
4100     }
4101 
4102     addExtensionProj4ToProp(nodeP, props);
4103 
4104     return ProjectedCRS::create(props, baseGeodCRS, conversion,
4105                                 NN_NO_CHECK(cartesianCS));
4106 }
4107 
4108 // ---------------------------------------------------------------------------
4109 
parseDynamic(const WKTNodeNNPtr & dynamicNode,double & frameReferenceEpoch,util::optional<std::string> & modelName)4110 void WKTParser::Private::parseDynamic(const WKTNodeNNPtr &dynamicNode,
4111                                       double &frameReferenceEpoch,
4112                                       util::optional<std::string> &modelName) {
4113     auto &frameEpochNode = dynamicNode->lookForChild(WKTConstants::FRAMEEPOCH);
4114     const auto &frameEpochChildren = frameEpochNode->GP()->children();
4115     if (frameEpochChildren.empty()) {
4116         ThrowMissing(WKTConstants::FRAMEEPOCH);
4117     }
4118     try {
4119         frameReferenceEpoch = asDouble(frameEpochChildren[0]);
4120     } catch (const std::exception &) {
4121         throw ParsingException("Invalid FRAMEEPOCH node");
4122     }
4123     auto &modelNode = dynamicNode->GP()->lookForChild(
4124         WKTConstants::MODEL, WKTConstants::VELOCITYGRID);
4125     const auto &modelChildren = modelNode->GP()->children();
4126     if (modelChildren.size() == 1) {
4127         modelName = stripQuotes(modelChildren[0]);
4128     }
4129 }
4130 
4131 // ---------------------------------------------------------------------------
4132 
buildVerticalReferenceFrame(const WKTNodeNNPtr & node,const WKTNodeNNPtr & dynamicNode)4133 VerticalReferenceFrameNNPtr WKTParser::Private::buildVerticalReferenceFrame(
4134     const WKTNodeNNPtr &node, const WKTNodeNNPtr &dynamicNode) {
4135 
4136     if (!isNull(dynamicNode)) {
4137         double frameReferenceEpoch = 0.0;
4138         util::optional<std::string> modelName;
4139         parseDynamic(dynamicNode, frameReferenceEpoch, modelName);
4140         return DynamicVerticalReferenceFrame::create(
4141             buildProperties(node), getAnchor(node),
4142             optional<RealizationMethod>(),
4143             common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR),
4144             modelName);
4145     }
4146 
4147     // WKT1 VERT_DATUM has a datum type after the datum name
4148     const auto *nodeP = node->GP();
4149     const std::string &name(nodeP->value());
4150     auto &props = buildProperties(node);
4151 
4152     if (esriStyle_ && dbContext_) {
4153         std::string outTableName;
4154         std::string authNameFromAlias;
4155         std::string codeFromAlias;
4156         auto authFactory =
4157             AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
4158         const std::string datumName = stripQuotes(nodeP->children()[0]);
4159         auto officialName = authFactory->getOfficialNameFromAlias(
4160             datumName, "vertical_datum", "ESRI", false, outTableName,
4161             authNameFromAlias, codeFromAlias);
4162         if (!officialName.empty()) {
4163             props.set(IdentifiedObject::NAME_KEY, officialName);
4164         }
4165     }
4166 
4167     if (ci_equal(name, WKTConstants::VERT_DATUM)) {
4168         const auto &children = nodeP->children();
4169         if (children.size() >= 2) {
4170             props.set("VERT_DATUM_TYPE", children[1]->GP()->value());
4171         }
4172     }
4173 
4174     return VerticalReferenceFrame::create(props, getAnchor(node));
4175 }
4176 
4177 // ---------------------------------------------------------------------------
4178 
4179 TemporalDatumNNPtr
buildTemporalDatum(const WKTNodeNNPtr & node)4180 WKTParser::Private::buildTemporalDatum(const WKTNodeNNPtr &node) {
4181     const auto *nodeP = node->GP();
4182     auto &calendarNode = nodeP->lookForChild(WKTConstants::CALENDAR);
4183     std::string calendar = TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN;
4184     const auto &calendarChildren = calendarNode->GP()->children();
4185     if (calendarChildren.size() == 1) {
4186         calendar = stripQuotes(calendarChildren[0]);
4187     }
4188 
4189     auto &timeOriginNode = nodeP->lookForChild(WKTConstants::TIMEORIGIN);
4190     std::string originStr;
4191     const auto &timeOriginNodeChildren = timeOriginNode->GP()->children();
4192     if (timeOriginNodeChildren.size() == 1) {
4193         originStr = stripQuotes(timeOriginNodeChildren[0]);
4194     }
4195     auto origin = DateTime::create(originStr);
4196     return TemporalDatum::create(buildProperties(node), origin, calendar);
4197 }
4198 
4199 // ---------------------------------------------------------------------------
4200 
4201 EngineeringDatumNNPtr
buildEngineeringDatum(const WKTNodeNNPtr & node)4202 WKTParser::Private::buildEngineeringDatum(const WKTNodeNNPtr &node) {
4203     return EngineeringDatum::create(buildProperties(node), getAnchor(node));
4204 }
4205 
4206 // ---------------------------------------------------------------------------
4207 
4208 ParametricDatumNNPtr
buildParametricDatum(const WKTNodeNNPtr & node)4209 WKTParser::Private::buildParametricDatum(const WKTNodeNNPtr &node) {
4210     return ParametricDatum::create(buildProperties(node), getAnchor(node));
4211 }
4212 
4213 // ---------------------------------------------------------------------------
4214 
4215 static CRSNNPtr
createBoundCRSSourceTransformationCRS(const crs::CRSPtr & sourceCRS,const crs::CRSPtr & targetCRS)4216 createBoundCRSSourceTransformationCRS(const crs::CRSPtr &sourceCRS,
4217                                       const crs::CRSPtr &targetCRS) {
4218     CRSPtr sourceTransformationCRS;
4219     if (dynamic_cast<GeographicCRS *>(targetCRS.get())) {
4220         GeographicCRSPtr sourceGeographicCRS =
4221             sourceCRS->extractGeographicCRS();
4222         sourceTransformationCRS = sourceGeographicCRS;
4223         if (sourceGeographicCRS) {
4224             if (sourceGeographicCRS->datum() != nullptr &&
4225                 sourceGeographicCRS->primeMeridian()
4226                         ->longitude()
4227                         .getSIValue() != 0.0) {
4228                 sourceTransformationCRS =
4229                     GeographicCRS::create(
4230                         util::PropertyMap().set(
4231                             common::IdentifiedObject::NAME_KEY,
4232                             sourceGeographicCRS->nameStr() +
4233                                 " (with Greenwich prime meridian)"),
4234                         datum::GeodeticReferenceFrame::create(
4235                             util::PropertyMap().set(
4236                                 common::IdentifiedObject::NAME_KEY,
4237                                 sourceGeographicCRS->datum()->nameStr() +
4238                                     " (with Greenwich prime meridian)"),
4239                             sourceGeographicCRS->datum()->ellipsoid(),
4240                             util::optional<std::string>(),
4241                             datum::PrimeMeridian::GREENWICH),
4242                         cs::EllipsoidalCS::createLatitudeLongitude(
4243                             common::UnitOfMeasure::DEGREE))
4244                         .as_nullable();
4245             }
4246         } else {
4247             auto vertSourceCRS =
4248                 std::dynamic_pointer_cast<VerticalCRS>(sourceCRS);
4249             if (!vertSourceCRS) {
4250                 throw ParsingException(
4251                     "Cannot find GeographicCRS or VerticalCRS in sourceCRS");
4252             }
4253             const auto &axis = vertSourceCRS->coordinateSystem()->axisList()[0];
4254             if (axis->unit() == common::UnitOfMeasure::METRE &&
4255                 &(axis->direction()) == &AxisDirection::UP) {
4256                 sourceTransformationCRS = sourceCRS;
4257             } else {
4258                 std::string sourceTransformationCRSName(
4259                     vertSourceCRS->nameStr());
4260                 if (ends_with(sourceTransformationCRSName, " (ftUS)")) {
4261                     sourceTransformationCRSName.resize(
4262                         sourceTransformationCRSName.size() - strlen(" (ftUS)"));
4263                 }
4264                 if (ends_with(sourceTransformationCRSName, " depth")) {
4265                     sourceTransformationCRSName.resize(
4266                         sourceTransformationCRSName.size() - strlen(" depth"));
4267                 }
4268                 if (!ends_with(sourceTransformationCRSName, " height")) {
4269                     sourceTransformationCRSName += " height";
4270                 }
4271                 sourceTransformationCRS =
4272                     VerticalCRS::create(
4273                         PropertyMap().set(IdentifiedObject::NAME_KEY,
4274                                           sourceTransformationCRSName),
4275                         vertSourceCRS->datum(), vertSourceCRS->datumEnsemble(),
4276                         VerticalCS::createGravityRelatedHeight(
4277                             common::UnitOfMeasure::METRE))
4278                         .as_nullable();
4279             }
4280         }
4281     } else {
4282         sourceTransformationCRS = sourceCRS;
4283     }
4284     return NN_NO_CHECK(sourceTransformationCRS);
4285 }
4286 
4287 // ---------------------------------------------------------------------------
4288 
buildVerticalCRS(const WKTNodeNNPtr & node)4289 CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) {
4290     const auto *nodeP = node->GP();
4291     const auto &nodeValue = nodeP->value();
4292     auto &vdatumNode =
4293         nodeP->lookForChild(WKTConstants::VDATUM, WKTConstants::VERT_DATUM,
4294                             WKTConstants::VERTICALDATUM, WKTConstants::VRF);
4295     auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE);
4296     // like in ESRI  VERTCS["WGS_1984",DATUM["D_WGS_1984",
4297     //               SPHEROID["WGS_1984",6378137.0,298.257223563]],
4298     //               PARAMETER["Vertical_Shift",0.0],
4299     //               PARAMETER["Direction",1.0],UNIT["Meter",1.0]
4300     auto &geogDatumNode = ci_equal(nodeValue, WKTConstants::VERTCS)
4301                               ? nodeP->lookForChild(WKTConstants::DATUM)
4302                               : null_node;
4303     if (isNull(vdatumNode) && isNull(geogDatumNode) && isNull(ensembleNode)) {
4304         throw ParsingException("Missing VDATUM or ENSEMBLE node");
4305     }
4306 
4307     for (const auto &childNode : nodeP->children()) {
4308         const auto &childNodeChildren = childNode->GP()->children();
4309         if (childNodeChildren.size() == 2 &&
4310             ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) &&
4311             childNodeChildren[0]->GP()->value() == "\"Vertical_Shift\"") {
4312             esriStyle_ = true;
4313             break;
4314         }
4315     }
4316 
4317     auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC);
4318     auto vdatum =
4319         !isNull(geogDatumNode)
4320             ? VerticalReferenceFrame::create(
4321                   PropertyMap()
4322                       .set(IdentifiedObject::NAME_KEY,
4323                            buildGeodeticReferenceFrame(geogDatumNode,
4324                                                        PrimeMeridian::GREENWICH,
4325                                                        null_node)
4326                                ->nameStr())
4327                       .set("VERT_DATUM_TYPE", "2002"))
4328                   .as_nullable()
4329             : !isNull(vdatumNode)
4330                   ? buildVerticalReferenceFrame(vdatumNode, dynamicNode)
4331                         .as_nullable()
4332                   : nullptr;
4333     auto datumEnsemble =
4334         !isNull(ensembleNode)
4335             ? buildDatumEnsemble(ensembleNode, nullptr, false).as_nullable()
4336             : nullptr;
4337 
4338     auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
4339     if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::VERT_CS) &&
4340         !ci_equal(nodeValue, WKTConstants::VERTCS) &&
4341         !ci_equal(nodeValue, WKTConstants::BASEVERTCRS)) {
4342         ThrowMissing(WKTConstants::CS_);
4343     }
4344     auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(
4345         buildCS(csNode, node, UnitOfMeasure::NONE));
4346     if (!verticalCS) {
4347         ThrowNotExpectedCSType("vertical");
4348     }
4349 
4350     if (vdatum && vdatum->getWKT1DatumType() == "2002" &&
4351         &(verticalCS->axisList()[0]->direction()) == &(AxisDirection::UP)) {
4352         verticalCS =
4353             VerticalCS::create(
4354                 util::PropertyMap(),
4355                 CoordinateSystemAxis::create(
4356                     util::PropertyMap().set(IdentifiedObject::NAME_KEY,
4357                                             "ellipsoidal height"),
4358                     "h", AxisDirection::UP, verticalCS->axisList()[0]->unit()))
4359                 .as_nullable();
4360     }
4361 
4362     auto &props = buildProperties(node);
4363 
4364     if (esriStyle_ && dbContext_) {
4365         std::string outTableName;
4366         std::string authNameFromAlias;
4367         std::string codeFromAlias;
4368         auto authFactory =
4369             AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
4370         const std::string vertCRSName = stripQuotes(nodeP->children()[0]);
4371         auto officialName = authFactory->getOfficialNameFromAlias(
4372             vertCRSName, "vertical_crs", "ESRI", false, outTableName,
4373             authNameFromAlias, codeFromAlias);
4374         if (!officialName.empty()) {
4375             props.set(IdentifiedObject::NAME_KEY, officialName);
4376         }
4377     }
4378 
4379     // Deal with Lidar WKT1 VertCRS that embeds geoid model in CRS name,
4380     // following conventions from
4381     // https://pubs.usgs.gov/tm/11b4/pdf/tm11-B4.pdf
4382     // page 9
4383     if (ci_equal(nodeValue, WKTConstants::VERT_CS) ||
4384         ci_equal(nodeValue, WKTConstants::VERTCS)) {
4385         std::string name;
4386         if (props.getStringValue(IdentifiedObject::NAME_KEY, name)) {
4387             std::string geoidName;
4388             for (const char *prefix :
4389                  {"NAVD88 - ", "NAVD88 via ", "NAVD88 height - ",
4390                   "NAVD88 height (ftUS) - "}) {
4391                 if (starts_with(name, prefix)) {
4392                     geoidName = name.substr(strlen(prefix));
4393                     auto pos = geoidName.find_first_of(" (");
4394                     if (pos != std::string::npos) {
4395                         geoidName.resize(pos);
4396                     }
4397                     break;
4398                 }
4399             }
4400             if (!geoidName.empty()) {
4401                 const auto &axis = verticalCS->axisList()[0];
4402                 const auto &dir = axis->direction();
4403                 if (dir == cs::AxisDirection::UP) {
4404                     if (axis->unit() == common::UnitOfMeasure::METRE) {
4405                         props.set(IdentifiedObject::NAME_KEY, "NAVD88 height");
4406                         props.set(Identifier::CODE_KEY, 5703);
4407                         props.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
4408                     } else if (axis->unit().name() == "US survey foot") {
4409                         props.set(IdentifiedObject::NAME_KEY,
4410                                   "NAVD88 height (ftUS)");
4411                         props.set(Identifier::CODE_KEY, 6360);
4412                         props.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
4413                     }
4414                 }
4415                 PropertyMap propsModel;
4416                 propsModel.set(IdentifiedObject::NAME_KEY, toupper(geoidName));
4417                 PropertyMap propsDatum;
4418                 propsDatum.set(IdentifiedObject::NAME_KEY,
4419                                "North American Vertical Datum 1988");
4420                 propsDatum.set(Identifier::CODE_KEY, 5103);
4421                 propsDatum.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
4422                 vdatum =
4423                     VerticalReferenceFrame::create(propsDatum).as_nullable();
4424                 const auto dummyCRS =
4425                     VerticalCRS::create(PropertyMap(), vdatum, datumEnsemble,
4426                                         NN_NO_CHECK(verticalCS));
4427                 const auto model(Transformation::create(
4428                     propsModel, dummyCRS, dummyCRS, nullptr,
4429                     OperationMethod::create(
4430                         PropertyMap(), std::vector<OperationParameterNNPtr>()),
4431                     {}, {}));
4432                 props.set("GEOID_MODEL", model);
4433             }
4434         }
4435     }
4436 
4437     auto &geoidModelNode = nodeP->lookForChild(WKTConstants::GEOIDMODEL);
4438     if (!isNull(geoidModelNode)) {
4439         auto &propsModel = buildProperties(geoidModelNode);
4440         const auto dummyCRS = VerticalCRS::create(
4441             PropertyMap(), vdatum, datumEnsemble, NN_NO_CHECK(verticalCS));
4442         const auto model(Transformation::create(
4443             propsModel, dummyCRS, dummyCRS, nullptr,
4444             OperationMethod::create(PropertyMap(),
4445                                     std::vector<OperationParameterNNPtr>()),
4446             {}, {}));
4447         props.set("GEOID_MODEL", model);
4448     }
4449 
4450     auto crs = nn_static_pointer_cast<CRS>(VerticalCRS::create(
4451         props, vdatum, datumEnsemble, NN_NO_CHECK(verticalCS)));
4452 
4453     if (!isNull(vdatumNode)) {
4454         auto &extensionNode = vdatumNode->lookForChild(WKTConstants::EXTENSION);
4455         const auto &extensionChildren = extensionNode->GP()->children();
4456         if (extensionChildren.size() == 2) {
4457             if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) {
4458                 const auto gridName(stripQuotes(extensionChildren[1]));
4459                 // This is the expansion of EPSG:5703 by old GDAL versions.
4460                 // See
4461                 // https://trac.osgeo.org/metacrs/changeset?reponame=&new=2281%40geotiff%2Ftrunk%2Flibgeotiff%2Fcsv%2Fvertcs.override.csv&old=1893%40geotiff%2Ftrunk%2Flibgeotiff%2Fcsv%2Fvertcs.override.csv
4462                 // It is unlikely that the user really explicitly wants this.
4463                 if (gridName != "g2003conus.gtx,g2003alaska.gtx,"
4464                                 "g2003h01.gtx,g2003p01.gtx" &&
4465                     gridName != "g2012a_conus.gtx,g2012a_alaska.gtx,"
4466                                 "g2012a_guam.gtx,g2012a_hawaii.gtx,"
4467                                 "g2012a_puertorico.gtx,g2012a_samoa.gtx") {
4468                     auto sourceTransformationCRS =
4469                         createBoundCRSSourceTransformationCRS(
4470                             crs.as_nullable(),
4471                             GeographicCRS::EPSG_4979.as_nullable());
4472                     auto transformation = Transformation::
4473                         createGravityRelatedHeightToGeographic3D(
4474                             PropertyMap().set(
4475                                 IdentifiedObject::NAME_KEY,
4476                                 sourceTransformationCRS->nameStr() +
4477                                     " to WGS84 ellipsoidal height"),
4478                             sourceTransformationCRS, GeographicCRS::EPSG_4979,
4479                             nullptr, gridName,
4480                             std::vector<PositionalAccuracyNNPtr>());
4481                     return nn_static_pointer_cast<CRS>(BoundCRS::create(
4482                         crs, GeographicCRS::EPSG_4979, transformation));
4483                 }
4484             }
4485         }
4486     }
4487 
4488     return crs;
4489 }
4490 
4491 // ---------------------------------------------------------------------------
4492 
4493 DerivedVerticalCRSNNPtr
buildDerivedVerticalCRS(const WKTNodeNNPtr & node)4494 WKTParser::Private::buildDerivedVerticalCRS(const WKTNodeNNPtr &node) {
4495     const auto *nodeP = node->GP();
4496     auto &baseVertCRSNode = nodeP->lookForChild(WKTConstants::BASEVERTCRS);
4497     // given the constraints enforced on calling code path
4498     assert(!isNull(baseVertCRSNode));
4499 
4500     auto baseVertCRS_tmp = buildVerticalCRS(baseVertCRSNode);
4501     auto baseVertCRS = NN_NO_CHECK(baseVertCRS_tmp->extractVerticalCRS());
4502 
4503     auto &derivingConversionNode =
4504         nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
4505     if (isNull(derivingConversionNode)) {
4506         ThrowMissing(WKTConstants::DERIVINGCONVERSION);
4507     }
4508     auto derivingConversion = buildConversion(
4509         derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
4510 
4511     auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
4512     if (isNull(csNode)) {
4513         ThrowMissing(WKTConstants::CS_);
4514     }
4515     auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
4516 
4517     auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(cs);
4518     if (!verticalCS) {
4519         throw ParsingException(
4520             concat("vertical CS expected, but found ", cs->getWKT2Type(true)));
4521     }
4522 
4523     return DerivedVerticalCRS::create(buildProperties(node), baseVertCRS,
4524                                       derivingConversion,
4525                                       NN_NO_CHECK(verticalCS));
4526 }
4527 
4528 // ---------------------------------------------------------------------------
4529 
buildCompoundCRS(const WKTNodeNNPtr & node)4530 CRSNNPtr WKTParser::Private::buildCompoundCRS(const WKTNodeNNPtr &node) {
4531     std::vector<CRSNNPtr> components;
4532     for (const auto &child : node->GP()->children()) {
4533         auto crs = buildCRS(child);
4534         if (crs) {
4535             components.push_back(NN_NO_CHECK(crs));
4536         }
4537     }
4538 
4539     if (ci_equal(node->GP()->value(), WKTConstants::COMPD_CS)) {
4540         return CompoundCRS::createLax(buildProperties(node), components,
4541                                       dbContext_);
4542     } else {
4543         return CompoundCRS::create(buildProperties(node), components);
4544     }
4545 }
4546 
4547 // ---------------------------------------------------------------------------
4548 
buildBoundCRS(const WKTNodeNNPtr & node)4549 BoundCRSNNPtr WKTParser::Private::buildBoundCRS(const WKTNodeNNPtr &node) {
4550     const auto *nodeP = node->GP();
4551     auto &abridgedNode =
4552         nodeP->lookForChild(WKTConstants::ABRIDGEDTRANSFORMATION);
4553     if (isNull(abridgedNode)) {
4554         ThrowNotEnoughChildren(WKTConstants::ABRIDGEDTRANSFORMATION);
4555     }
4556 
4557     auto &methodNode = abridgedNode->GP()->lookForChild(WKTConstants::METHOD);
4558     if (isNull(methodNode)) {
4559         ThrowMissing(WKTConstants::METHOD);
4560     }
4561     if (methodNode->GP()->childrenSize() == 0) {
4562         ThrowNotEnoughChildren(WKTConstants::METHOD);
4563     }
4564 
4565     auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
4566     const auto &sourceCRSNodeChildren = sourceCRSNode->GP()->children();
4567     if (sourceCRSNodeChildren.size() != 1) {
4568         ThrowNotEnoughChildren(WKTConstants::SOURCECRS);
4569     }
4570     auto sourceCRS = buildCRS(sourceCRSNodeChildren[0]);
4571     if (!sourceCRS) {
4572         throw ParsingException("Invalid content in SOURCECRS node");
4573     }
4574 
4575     auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
4576     const auto &targetCRSNodeChildren = targetCRSNode->GP()->children();
4577     if (targetCRSNodeChildren.size() != 1) {
4578         ThrowNotEnoughChildren(WKTConstants::TARGETCRS);
4579     }
4580     auto targetCRS = buildCRS(targetCRSNodeChildren[0]);
4581     if (!targetCRS) {
4582         throw ParsingException("Invalid content in TARGETCRS node");
4583     }
4584 
4585     std::vector<OperationParameterNNPtr> parameters;
4586     std::vector<ParameterValueNNPtr> values;
4587     auto defaultLinearUnit = UnitOfMeasure::NONE;
4588     auto defaultAngularUnit = UnitOfMeasure::NONE;
4589     consumeParameters(abridgedNode, true, parameters, values, defaultLinearUnit,
4590                       defaultAngularUnit);
4591 
4592     const auto sourceTransformationCRS(
4593         createBoundCRSSourceTransformationCRS(sourceCRS, targetCRS));
4594     auto transformation = Transformation::create(
4595         buildProperties(abridgedNode), sourceTransformationCRS,
4596         NN_NO_CHECK(targetCRS), nullptr, buildProperties(methodNode),
4597         parameters, values, std::vector<PositionalAccuracyNNPtr>());
4598 
4599     return BoundCRS::create(NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS),
4600                             transformation);
4601 }
4602 
4603 // ---------------------------------------------------------------------------
4604 
4605 TemporalCSNNPtr
buildTemporalCS(const WKTNodeNNPtr & parentNode)4606 WKTParser::Private::buildTemporalCS(const WKTNodeNNPtr &parentNode) {
4607 
4608     auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS_);
4609     if (isNull(csNode) &&
4610         !ci_equal(parentNode->GP()->value(), WKTConstants::BASETIMECRS)) {
4611         ThrowMissing(WKTConstants::CS_);
4612     }
4613     auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE);
4614     auto temporalCS = nn_dynamic_pointer_cast<TemporalCS>(cs);
4615     if (!temporalCS) {
4616         ThrowNotExpectedCSType("temporal");
4617     }
4618     return NN_NO_CHECK(temporalCS);
4619 }
4620 
4621 // ---------------------------------------------------------------------------
4622 
4623 TemporalCRSNNPtr
buildTemporalCRS(const WKTNodeNNPtr & node)4624 WKTParser::Private::buildTemporalCRS(const WKTNodeNNPtr &node) {
4625     auto &datumNode =
4626         node->GP()->lookForChild(WKTConstants::TDATUM, WKTConstants::TIMEDATUM);
4627     if (isNull(datumNode)) {
4628         throw ParsingException("Missing TDATUM / TIMEDATUM node");
4629     }
4630 
4631     return TemporalCRS::create(buildProperties(node),
4632                                buildTemporalDatum(datumNode),
4633                                buildTemporalCS(node));
4634 }
4635 
4636 // ---------------------------------------------------------------------------
4637 
4638 DerivedTemporalCRSNNPtr
buildDerivedTemporalCRS(const WKTNodeNNPtr & node)4639 WKTParser::Private::buildDerivedTemporalCRS(const WKTNodeNNPtr &node) {
4640     const auto *nodeP = node->GP();
4641     auto &baseCRSNode = nodeP->lookForChild(WKTConstants::BASETIMECRS);
4642     // given the constraints enforced on calling code path
4643     assert(!isNull(baseCRSNode));
4644 
4645     auto &derivingConversionNode =
4646         nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
4647     if (isNull(derivingConversionNode)) {
4648         ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
4649     }
4650 
4651     return DerivedTemporalCRS::create(
4652         buildProperties(node), buildTemporalCRS(baseCRSNode),
4653         buildConversion(derivingConversionNode, UnitOfMeasure::NONE,
4654                         UnitOfMeasure::NONE),
4655         buildTemporalCS(node));
4656 }
4657 
4658 // ---------------------------------------------------------------------------
4659 
4660 EngineeringCRSNNPtr
buildEngineeringCRS(const WKTNodeNNPtr & node)4661 WKTParser::Private::buildEngineeringCRS(const WKTNodeNNPtr &node) {
4662     const auto *nodeP = node->GP();
4663     auto &datumNode = nodeP->lookForChild(WKTConstants::EDATUM,
4664                                           WKTConstants::ENGINEERINGDATUM);
4665     if (isNull(datumNode)) {
4666         throw ParsingException("Missing EDATUM / ENGINEERINGDATUM node");
4667     }
4668 
4669     auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
4670     if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::BASEENGCRS)) {
4671         ThrowMissing(WKTConstants::CS_);
4672     }
4673 
4674     auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
4675     return EngineeringCRS::create(buildProperties(node),
4676                                   buildEngineeringDatum(datumNode), cs);
4677 }
4678 
4679 // ---------------------------------------------------------------------------
4680 
4681 EngineeringCRSNNPtr
buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr & node)4682 WKTParser::Private::buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node) {
4683     auto &datumNode = node->GP()->lookForChild(WKTConstants::LOCAL_DATUM);
4684     auto cs = buildCS(null_node, node, UnitOfMeasure::NONE);
4685     auto datum = EngineeringDatum::create(
4686         !isNull(datumNode)
4687             ? buildProperties(datumNode)
4688             :
4689             // In theory OGC 01-009 mandates LOCAL_DATUM, but GDAL has a
4690             // tradition of emitting just LOCAL_CS["foo"]
4691             emptyPropertyMap);
4692     return EngineeringCRS::create(buildProperties(node), datum, cs);
4693 }
4694 
4695 // ---------------------------------------------------------------------------
4696 
4697 DerivedEngineeringCRSNNPtr
buildDerivedEngineeringCRS(const WKTNodeNNPtr & node)4698 WKTParser::Private::buildDerivedEngineeringCRS(const WKTNodeNNPtr &node) {
4699     const auto *nodeP = node->GP();
4700     auto &baseEngCRSNode = nodeP->lookForChild(WKTConstants::BASEENGCRS);
4701     // given the constraints enforced on calling code path
4702     assert(!isNull(baseEngCRSNode));
4703 
4704     auto baseEngCRS = buildEngineeringCRS(baseEngCRSNode);
4705 
4706     auto &derivingConversionNode =
4707         nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
4708     if (isNull(derivingConversionNode)) {
4709         ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
4710     }
4711     auto derivingConversion = buildConversion(
4712         derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
4713 
4714     auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
4715     if (isNull(csNode)) {
4716         ThrowMissing(WKTConstants::CS_);
4717     }
4718     auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
4719 
4720     return DerivedEngineeringCRS::create(buildProperties(node), baseEngCRS,
4721                                          derivingConversion, cs);
4722 }
4723 
4724 // ---------------------------------------------------------------------------
4725 
4726 ParametricCSNNPtr
buildParametricCS(const WKTNodeNNPtr & parentNode)4727 WKTParser::Private::buildParametricCS(const WKTNodeNNPtr &parentNode) {
4728 
4729     auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS_);
4730     if (isNull(csNode) &&
4731         !ci_equal(parentNode->GP()->value(), WKTConstants::BASEPARAMCRS)) {
4732         ThrowMissing(WKTConstants::CS_);
4733     }
4734     auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE);
4735     auto parametricCS = nn_dynamic_pointer_cast<ParametricCS>(cs);
4736     if (!parametricCS) {
4737         ThrowNotExpectedCSType("parametric");
4738     }
4739     return NN_NO_CHECK(parametricCS);
4740 }
4741 
4742 // ---------------------------------------------------------------------------
4743 
4744 ParametricCRSNNPtr
buildParametricCRS(const WKTNodeNNPtr & node)4745 WKTParser::Private::buildParametricCRS(const WKTNodeNNPtr &node) {
4746     auto &datumNode = node->GP()->lookForChild(WKTConstants::PDATUM,
4747                                                WKTConstants::PARAMETRICDATUM);
4748     if (isNull(datumNode)) {
4749         throw ParsingException("Missing PDATUM / PARAMETRICDATUM node");
4750     }
4751 
4752     return ParametricCRS::create(buildProperties(node),
4753                                  buildParametricDatum(datumNode),
4754                                  buildParametricCS(node));
4755 }
4756 
4757 // ---------------------------------------------------------------------------
4758 
4759 DerivedParametricCRSNNPtr
buildDerivedParametricCRS(const WKTNodeNNPtr & node)4760 WKTParser::Private::buildDerivedParametricCRS(const WKTNodeNNPtr &node) {
4761     const auto *nodeP = node->GP();
4762     auto &baseParamCRSNode = nodeP->lookForChild(WKTConstants::BASEPARAMCRS);
4763     // given the constraints enforced on calling code path
4764     assert(!isNull(baseParamCRSNode));
4765 
4766     auto &derivingConversionNode =
4767         nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
4768     if (isNull(derivingConversionNode)) {
4769         ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
4770     }
4771 
4772     return DerivedParametricCRS::create(
4773         buildProperties(node), buildParametricCRS(baseParamCRSNode),
4774         buildConversion(derivingConversionNode, UnitOfMeasure::NONE,
4775                         UnitOfMeasure::NONE),
4776         buildParametricCS(node));
4777 }
4778 
4779 // ---------------------------------------------------------------------------
4780 
4781 DerivedProjectedCRSNNPtr
buildDerivedProjectedCRS(const WKTNodeNNPtr & node)4782 WKTParser::Private::buildDerivedProjectedCRS(const WKTNodeNNPtr &node) {
4783     const auto *nodeP = node->GP();
4784     auto &baseProjCRSNode = nodeP->lookForChild(WKTConstants::BASEPROJCRS);
4785     if (isNull(baseProjCRSNode)) {
4786         ThrowNotEnoughChildren(WKTConstants::BASEPROJCRS);
4787     }
4788     auto baseProjCRS = buildProjectedCRS(baseProjCRSNode);
4789 
4790     auto &conversionNode =
4791         nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
4792     if (isNull(conversionNode)) {
4793         ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
4794     }
4795 
4796     auto linearUnit = buildUnitInSubNode(node);
4797     auto angularUnit =
4798         baseProjCRS->baseCRS()->coordinateSystem()->axisList()[0]->unit();
4799 
4800     auto conversion = buildConversion(conversionNode, linearUnit, angularUnit);
4801 
4802     auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
4803     if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::PROJCS)) {
4804         ThrowMissing(WKTConstants::CS_);
4805     }
4806     auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
4807     return DerivedProjectedCRS::create(buildProperties(node), baseProjCRS,
4808                                        conversion, cs);
4809 }
4810 
4811 // ---------------------------------------------------------------------------
4812 
isGeodeticCRS(const std::string & name)4813 static bool isGeodeticCRS(const std::string &name) {
4814     return ci_equal(name, WKTConstants::GEODCRS) ||       // WKT2
4815            ci_equal(name, WKTConstants::GEODETICCRS) ||   // WKT2
4816            ci_equal(name, WKTConstants::GEOGCRS) ||       // WKT2 2019
4817            ci_equal(name, WKTConstants::GEOGRAPHICCRS) || // WKT2 2019
4818            ci_equal(name, WKTConstants::GEOGCS) ||        // WKT1
4819            ci_equal(name, WKTConstants::GEOCCS);          // WKT1
4820 }
4821 
4822 // ---------------------------------------------------------------------------
4823 
buildCRS(const WKTNodeNNPtr & node)4824 CRSPtr WKTParser::Private::buildCRS(const WKTNodeNNPtr &node) {
4825     const auto *nodeP = node->GP();
4826     const std::string &name(nodeP->value());
4827 
4828     const auto applyHorizontalBoundCRSParams = [&](const CRSNNPtr &crs) {
4829         if (!toWGS84Parameters_.empty()) {
4830             auto ret = BoundCRS::createFromTOWGS84(crs, toWGS84Parameters_);
4831             toWGS84Parameters_.clear();
4832             return util::nn_static_pointer_cast<CRS>(ret);
4833         } else if (!datumPROJ4Grids_.empty()) {
4834             auto ret = BoundCRS::createFromNadgrids(crs, datumPROJ4Grids_);
4835             datumPROJ4Grids_.clear();
4836             return util::nn_static_pointer_cast<CRS>(ret);
4837         }
4838         return crs;
4839     };
4840 
4841     if (isGeodeticCRS(name)) {
4842         if (!isNull(nodeP->lookForChild(WKTConstants::BASEGEOGCRS,
4843                                         WKTConstants::BASEGEODCRS))) {
4844             return util::nn_static_pointer_cast<CRS>(
4845                 applyHorizontalBoundCRSParams(buildDerivedGeodeticCRS(node)));
4846         } else {
4847             return util::nn_static_pointer_cast<CRS>(
4848                 applyHorizontalBoundCRSParams(buildGeodeticCRS(node)));
4849         }
4850     }
4851 
4852     if (ci_equal(name, WKTConstants::PROJCS) ||
4853         ci_equal(name, WKTConstants::PROJCRS) ||
4854         ci_equal(name, WKTConstants::PROJECTEDCRS)) {
4855         // Get the EXTENSION "PROJ4" node before attempting to call
4856         // buildProjectedCRS() since formulations of WKT1_GDAL from GDAL 2.x
4857         // with the netCDF driver and the lack the required UNIT[] node
4858         std::string projString = getExtensionProj4(nodeP);
4859         if (!projString.empty() &&
4860             (starts_with(projString, "+proj=ob_tran +o_proj=longlat") ||
4861              starts_with(projString, "+proj=ob_tran +o_proj=lonlat") ||
4862              starts_with(projString, "+proj=ob_tran +o_proj=latlong") ||
4863              starts_with(projString, "+proj=ob_tran +o_proj=latlon"))) {
4864             // Those are not a projected CRS, but a DerivedGeographic one...
4865             if (projString.find(" +type=crs") == std::string::npos) {
4866                 projString += " +type=crs";
4867             }
4868             try {
4869                 auto projObj =
4870                     PROJStringParser().createFromPROJString(projString);
4871                 auto crs = nn_dynamic_pointer_cast<CRS>(projObj);
4872                 if (crs) {
4873                     return util::nn_static_pointer_cast<CRS>(
4874                         applyHorizontalBoundCRSParams(NN_NO_CHECK(crs)));
4875                 }
4876             } catch (const io::ParsingException &) {
4877             }
4878         }
4879         return util::nn_static_pointer_cast<CRS>(
4880             applyHorizontalBoundCRSParams(buildProjectedCRS(node)));
4881     }
4882 
4883     if (ci_equal(name, WKTConstants::VERT_CS) ||
4884         ci_equal(name, WKTConstants::VERTCS) ||
4885         ci_equal(name, WKTConstants::VERTCRS) ||
4886         ci_equal(name, WKTConstants::VERTICALCRS)) {
4887         if (!isNull(nodeP->lookForChild(WKTConstants::BASEVERTCRS))) {
4888             return util::nn_static_pointer_cast<CRS>(
4889                 buildDerivedVerticalCRS(node));
4890         } else {
4891             return util::nn_static_pointer_cast<CRS>(buildVerticalCRS(node));
4892         }
4893     }
4894 
4895     if (ci_equal(name, WKTConstants::COMPD_CS) ||
4896         ci_equal(name, WKTConstants::COMPOUNDCRS)) {
4897         return util::nn_static_pointer_cast<CRS>(buildCompoundCRS(node));
4898     }
4899 
4900     if (ci_equal(name, WKTConstants::BOUNDCRS)) {
4901         return util::nn_static_pointer_cast<CRS>(buildBoundCRS(node));
4902     }
4903 
4904     if (ci_equal(name, WKTConstants::TIMECRS)) {
4905         if (!isNull(nodeP->lookForChild(WKTConstants::BASETIMECRS))) {
4906             return util::nn_static_pointer_cast<CRS>(
4907                 buildDerivedTemporalCRS(node));
4908         } else {
4909             return util::nn_static_pointer_cast<CRS>(buildTemporalCRS(node));
4910         }
4911     }
4912 
4913     if (ci_equal(name, WKTConstants::DERIVEDPROJCRS)) {
4914         return util::nn_static_pointer_cast<CRS>(
4915             buildDerivedProjectedCRS(node));
4916     }
4917 
4918     if (ci_equal(name, WKTConstants::ENGCRS) ||
4919         ci_equal(name, WKTConstants::ENGINEERINGCRS)) {
4920         if (!isNull(nodeP->lookForChild(WKTConstants::BASEENGCRS))) {
4921             return util::nn_static_pointer_cast<CRS>(
4922                 buildDerivedEngineeringCRS(node));
4923         } else {
4924             return util::nn_static_pointer_cast<CRS>(buildEngineeringCRS(node));
4925         }
4926     }
4927 
4928     if (ci_equal(name, WKTConstants::LOCAL_CS)) {
4929         return util::nn_static_pointer_cast<CRS>(
4930             buildEngineeringCRSFromLocalCS(node));
4931     }
4932 
4933     if (ci_equal(name, WKTConstants::PARAMETRICCRS)) {
4934         if (!isNull(nodeP->lookForChild(WKTConstants::BASEPARAMCRS))) {
4935             return util::nn_static_pointer_cast<CRS>(
4936                 buildDerivedParametricCRS(node));
4937         } else {
4938             return util::nn_static_pointer_cast<CRS>(buildParametricCRS(node));
4939         }
4940     }
4941 
4942     return nullptr;
4943 }
4944 
4945 // ---------------------------------------------------------------------------
4946 
build(const WKTNodeNNPtr & node)4947 BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) {
4948     const auto *nodeP = node->GP();
4949     const std::string &name(nodeP->value());
4950 
4951     auto crs = buildCRS(node);
4952     if (crs) {
4953         return util::nn_static_pointer_cast<BaseObject>(NN_NO_CHECK(crs));
4954     }
4955 
4956     // Datum handled by caller code WKTParser::createFromWKT()
4957 
4958     if (ci_equal(name, WKTConstants::ENSEMBLE)) {
4959         return util::nn_static_pointer_cast<BaseObject>(buildDatumEnsemble(
4960             node, PrimeMeridian::GREENWICH,
4961             !isNull(nodeP->lookForChild(WKTConstants::ELLIPSOID))));
4962     }
4963 
4964     if (ci_equal(name, WKTConstants::VDATUM) ||
4965         ci_equal(name, WKTConstants::VERT_DATUM) ||
4966         ci_equal(name, WKTConstants::VERTICALDATUM) ||
4967         ci_equal(name, WKTConstants::VRF)) {
4968         return util::nn_static_pointer_cast<BaseObject>(
4969             buildVerticalReferenceFrame(node, null_node));
4970     }
4971 
4972     if (ci_equal(name, WKTConstants::TDATUM) ||
4973         ci_equal(name, WKTConstants::TIMEDATUM)) {
4974         return util::nn_static_pointer_cast<BaseObject>(
4975             buildTemporalDatum(node));
4976     }
4977 
4978     if (ci_equal(name, WKTConstants::EDATUM) ||
4979         ci_equal(name, WKTConstants::ENGINEERINGDATUM)) {
4980         return util::nn_static_pointer_cast<BaseObject>(
4981             buildEngineeringDatum(node));
4982     }
4983 
4984     if (ci_equal(name, WKTConstants::PDATUM) ||
4985         ci_equal(name, WKTConstants::PARAMETRICDATUM)) {
4986         return util::nn_static_pointer_cast<BaseObject>(
4987             buildParametricDatum(node));
4988     }
4989 
4990     if (ci_equal(name, WKTConstants::ELLIPSOID) ||
4991         ci_equal(name, WKTConstants::SPHEROID)) {
4992         return util::nn_static_pointer_cast<BaseObject>(buildEllipsoid(node));
4993     }
4994 
4995     if (ci_equal(name, WKTConstants::COORDINATEOPERATION)) {
4996         auto transf = buildCoordinateOperation(node);
4997 
4998         const char *prefixes[] = {
4999             "PROJ-based operation method: ",
5000             "PROJ-based operation method (approximate): "};
5001         for (const char *prefix : prefixes) {
5002             if (starts_with(transf->method()->nameStr(), prefix)) {
5003                 auto projString =
5004                     transf->method()->nameStr().substr(strlen(prefix));
5005                 return util::nn_static_pointer_cast<BaseObject>(
5006                     PROJBasedOperation::create(
5007                         PropertyMap(), projString, transf->sourceCRS(),
5008                         transf->targetCRS(),
5009                         transf->coordinateOperationAccuracies()));
5010             }
5011         }
5012 
5013         return util::nn_static_pointer_cast<BaseObject>(transf);
5014     }
5015 
5016     if (ci_equal(name, WKTConstants::CONVERSION)) {
5017         auto conv =
5018             buildConversion(node, UnitOfMeasure::METRE, UnitOfMeasure::DEGREE);
5019 
5020         if (starts_with(conv->method()->nameStr(),
5021                         "PROJ-based operation method: ")) {
5022             auto projString = conv->method()->nameStr().substr(
5023                 strlen("PROJ-based operation method: "));
5024             return util::nn_static_pointer_cast<BaseObject>(
5025                 PROJBasedOperation::create(PropertyMap(), projString, nullptr,
5026                                            nullptr, {}));
5027         }
5028 
5029         return util::nn_static_pointer_cast<BaseObject>(conv);
5030     }
5031 
5032     if (ci_equal(name, WKTConstants::CONCATENATEDOPERATION)) {
5033         return util::nn_static_pointer_cast<BaseObject>(
5034             buildConcatenatedOperation(node));
5035     }
5036 
5037     if (ci_equal(name, WKTConstants::ID) ||
5038         ci_equal(name, WKTConstants::AUTHORITY)) {
5039         return util::nn_static_pointer_cast<BaseObject>(
5040             NN_NO_CHECK(buildId(node, false, false)));
5041     }
5042 
5043     throw ParsingException(concat("unhandled keyword: ", name));
5044 }
5045 
5046 // ---------------------------------------------------------------------------
5047 
5048 class JSONParser {
5049     DatabaseContextPtr dbContext_{};
5050 
5051     static std::string getString(const json &j, const char *key);
5052     static json getObject(const json &j, const char *key);
5053     static json getArray(const json &j, const char *key);
5054     static double getNumber(const json &j, const char *key);
5055     static UnitOfMeasure getUnit(const json &j, const char *key);
5056     static std::string getName(const json &j);
5057     static std::string getType(const json &j);
5058     static Length getLength(const json &j, const char *key);
5059     static Measure getMeasure(const json &j);
5060 
5061     IdentifierNNPtr buildId(const json &j, bool removeInverseOf);
5062     static ObjectDomainPtr buildObjectDomain(const json &j);
5063     PropertyMap buildProperties(const json &j, bool removeInverseOf = false);
5064 
5065     GeographicCRSNNPtr buildGeographicCRS(const json &j);
5066     GeodeticCRSNNPtr buildGeodeticCRS(const json &j);
5067     ProjectedCRSNNPtr buildProjectedCRS(const json &j);
5068     ConversionNNPtr buildConversion(const json &j);
5069     DatumEnsembleNNPtr buildDatumEnsemble(const json &j);
5070     GeodeticReferenceFrameNNPtr buildGeodeticReferenceFrame(const json &j);
5071     VerticalReferenceFrameNNPtr buildVerticalReferenceFrame(const json &j);
5072     DynamicGeodeticReferenceFrameNNPtr
5073     buildDynamicGeodeticReferenceFrame(const json &j);
5074     DynamicVerticalReferenceFrameNNPtr
5075     buildDynamicVerticalReferenceFrame(const json &j);
5076     EllipsoidNNPtr buildEllipsoid(const json &j);
5077     PrimeMeridianNNPtr buildPrimeMeridian(const json &j);
5078     CoordinateSystemNNPtr buildCS(const json &j);
5079     CoordinateSystemAxisNNPtr buildAxis(const json &j);
5080     VerticalCRSNNPtr buildVerticalCRS(const json &j);
5081     CRSNNPtr buildCRS(const json &j);
5082     CompoundCRSNNPtr buildCompoundCRS(const json &j);
5083     BoundCRSNNPtr buildBoundCRS(const json &j);
5084     TransformationNNPtr buildTransformation(const json &j);
5085     ConcatenatedOperationNNPtr buildConcatenatedOperation(const json &j);
5086 
5087     void buildGeodeticDatumOrDatumEnsemble(const json &j,
5088                                            GeodeticReferenceFramePtr &datum,
5089                                            DatumEnsemblePtr &datumEnsemble);
5090 
getAnchor(const json & j)5091     static util::optional<std::string> getAnchor(const json &j) {
5092         util::optional<std::string> anchor;
5093         if (j.contains("anchor")) {
5094             anchor = getString(j, "anchor");
5095         }
5096         return anchor;
5097     }
5098 
buildEngineeringDatum(const json & j)5099     EngineeringDatumNNPtr buildEngineeringDatum(const json &j) {
5100         return EngineeringDatum::create(buildProperties(j), getAnchor(j));
5101     }
5102 
buildParametricDatum(const json & j)5103     ParametricDatumNNPtr buildParametricDatum(const json &j) {
5104         return ParametricDatum::create(buildProperties(j), getAnchor(j));
5105     }
5106 
buildTemporalDatum(const json & j)5107     TemporalDatumNNPtr buildTemporalDatum(const json &j) {
5108         auto calendar = getString(j, "calendar");
5109         auto origin = DateTime::create(j.contains("time_origin")
5110                                            ? getString(j, "time_origin")
5111                                            : std::string());
5112         return TemporalDatum::create(buildProperties(j), origin, calendar);
5113     }
5114 
5115     template <class TargetCRS, class DatumBuilderType,
5116               class CSClass = CoordinateSystem>
buildCRS(const json & j,DatumBuilderType f)5117     util::nn<std::shared_ptr<TargetCRS>> buildCRS(const json &j,
5118                                                   DatumBuilderType f) {
5119         auto datum = (this->*f)(getObject(j, "datum"));
5120         auto cs = buildCS(getObject(j, "coordinate_system"));
5121         auto csCast = util::nn_dynamic_pointer_cast<CSClass>(cs);
5122         if (!csCast) {
5123             throw ParsingException("coordinate_system not of expected type");
5124         }
5125         return TargetCRS::create(buildProperties(j), datum,
5126                                  NN_NO_CHECK(csCast));
5127     }
5128 
5129     template <class TargetCRS, class BaseCRS, class CSClass = CoordinateSystem>
buildDerivedCRS(const json & j)5130     util::nn<std::shared_ptr<TargetCRS>> buildDerivedCRS(const json &j) {
5131         auto baseCRSObj = create(getObject(j, "base_crs"));
5132         auto baseCRS = util::nn_dynamic_pointer_cast<BaseCRS>(baseCRSObj);
5133         if (!baseCRS) {
5134             throw ParsingException("base_crs not of expected type");
5135         }
5136         auto cs = buildCS(getObject(j, "coordinate_system"));
5137         auto csCast = util::nn_dynamic_pointer_cast<CSClass>(cs);
5138         if (!csCast) {
5139             throw ParsingException("coordinate_system not of expected type");
5140         }
5141         auto conv = buildConversion(getObject(j, "conversion"));
5142         return TargetCRS::create(buildProperties(j), NN_NO_CHECK(baseCRS), conv,
5143                                  NN_NO_CHECK(csCast));
5144     }
5145 
5146   public:
5147     JSONParser() = default;
5148 
attachDatabaseContext(const DatabaseContextPtr & dbContext)5149     JSONParser &attachDatabaseContext(const DatabaseContextPtr &dbContext) {
5150         dbContext_ = dbContext;
5151         return *this;
5152     }
5153 
5154     BaseObjectNNPtr create(const json &j);
5155 };
5156 
5157 // ---------------------------------------------------------------------------
5158 
getString(const json & j,const char * key)5159 std::string JSONParser::getString(const json &j, const char *key) {
5160     if (!j.contains(key)) {
5161         throw ParsingException(std::string("Missing \"") + key + "\" key");
5162     }
5163     auto v = j[key];
5164     if (!v.is_string()) {
5165         throw ParsingException(std::string("The value of \"") + key +
5166                                "\" should be a string");
5167     }
5168     return v.get<std::string>();
5169 }
5170 
5171 // ---------------------------------------------------------------------------
5172 
getObject(const json & j,const char * key)5173 json JSONParser::getObject(const json &j, const char *key) {
5174     if (!j.contains(key)) {
5175         throw ParsingException(std::string("Missing \"") + key + "\" key");
5176     }
5177     auto v = j[key];
5178     if (!v.is_object()) {
5179         throw ParsingException(std::string("The value of \"") + key +
5180                                "\" should be a object");
5181     }
5182     return v.get<json>();
5183 }
5184 
5185 // ---------------------------------------------------------------------------
5186 
getArray(const json & j,const char * key)5187 json JSONParser::getArray(const json &j, const char *key) {
5188     if (!j.contains(key)) {
5189         throw ParsingException(std::string("Missing \"") + key + "\" key");
5190     }
5191     auto v = j[key];
5192     if (!v.is_array()) {
5193         throw ParsingException(std::string("The value of \"") + key +
5194                                "\" should be a array");
5195     }
5196     return v.get<json>();
5197 }
5198 
5199 // ---------------------------------------------------------------------------
5200 
getNumber(const json & j,const char * key)5201 double JSONParser::getNumber(const json &j, const char *key) {
5202     if (!j.contains(key)) {
5203         throw ParsingException(std::string("Missing \"") + key + "\" key");
5204     }
5205     auto v = j[key];
5206     if (!v.is_number()) {
5207         throw ParsingException(std::string("The value of \"") + key +
5208                                "\" should be a number");
5209     }
5210     return v.get<double>();
5211 }
5212 
5213 // ---------------------------------------------------------------------------
5214 
getUnit(const json & j,const char * key)5215 UnitOfMeasure JSONParser::getUnit(const json &j, const char *key) {
5216     if (!j.contains(key)) {
5217         throw ParsingException(std::string("Missing \"") + key + "\" key");
5218     }
5219     auto v = j[key];
5220     if (v.is_string()) {
5221         auto vStr = v.get<std::string>();
5222         for (const auto &unit : {UnitOfMeasure::METRE, UnitOfMeasure::DEGREE,
5223                                  UnitOfMeasure::SCALE_UNITY}) {
5224             if (vStr == unit.name())
5225                 return unit;
5226         }
5227         throw ParsingException("Unknown unit name: " + vStr);
5228     }
5229     if (!v.is_object()) {
5230         throw ParsingException(std::string("The value of \"") + key +
5231                                "\" should be a string or an object");
5232     }
5233     auto typeStr = getType(v);
5234     UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN;
5235     if (typeStr == "LinearUnit") {
5236         type = UnitOfMeasure::Type::LINEAR;
5237     } else if (typeStr == "AngularUnit") {
5238         type = UnitOfMeasure::Type::ANGULAR;
5239     } else if (typeStr == "ScaleUnit") {
5240         type = UnitOfMeasure::Type::SCALE;
5241     } else if (typeStr == "TimeUnit") {
5242         type = UnitOfMeasure::Type::TIME;
5243     } else if (typeStr == "ParametricUnit") {
5244         type = UnitOfMeasure::Type::PARAMETRIC;
5245     } else if (typeStr == "Unit") {
5246         type = UnitOfMeasure::Type::UNKNOWN;
5247     } else {
5248         throw ParsingException("Unsupported value of \"type\"");
5249     }
5250     auto nameStr = getName(v);
5251     auto convFactor = getNumber(v, "conversion_factor");
5252     std::string authorityStr;
5253     std::string codeStr;
5254     if (v.contains("authority") && v.contains("code")) {
5255         authorityStr = getString(v, "authority");
5256         auto code = v["code"];
5257         if (code.is_string()) {
5258             codeStr = code.get<std::string>();
5259         } else if (code.is_number_integer()) {
5260             codeStr = internal::toString(code.get<int>());
5261         } else {
5262             throw ParsingException("Unexpected type for value of \"code\"");
5263         }
5264     }
5265     return UnitOfMeasure(nameStr, convFactor, type, authorityStr, codeStr);
5266 }
5267 
5268 // ---------------------------------------------------------------------------
5269 
getName(const json & j)5270 std::string JSONParser::getName(const json &j) { return getString(j, "name"); }
5271 
5272 // ---------------------------------------------------------------------------
5273 
getType(const json & j)5274 std::string JSONParser::getType(const json &j) { return getString(j, "type"); }
5275 
5276 // ---------------------------------------------------------------------------
5277 
getLength(const json & j,const char * key)5278 Length JSONParser::getLength(const json &j, const char *key) {
5279     if (!j.contains(key)) {
5280         throw ParsingException(std::string("Missing \"") + key + "\" key");
5281     }
5282     auto v = j[key];
5283     if (v.is_number()) {
5284         return Length(v.get<double>(), UnitOfMeasure::METRE);
5285     }
5286     if (v.is_object()) {
5287         return Length(getMeasure(v));
5288     }
5289     throw ParsingException(std::string("The value of \"") + key +
5290                            "\" should be a number or an object");
5291 }
5292 
5293 // ---------------------------------------------------------------------------
5294 
getMeasure(const json & j)5295 Measure JSONParser::getMeasure(const json &j) {
5296     return Measure(getNumber(j, "value"), getUnit(j, "unit"));
5297 }
5298 
5299 // ---------------------------------------------------------------------------
5300 
buildObjectDomain(const json & j)5301 ObjectDomainPtr JSONParser::buildObjectDomain(const json &j) {
5302     optional<std::string> scope;
5303     if (j.contains("scope")) {
5304         scope = getString(j, "scope");
5305     }
5306     std::string area;
5307     if (j.contains("area")) {
5308         area = getString(j, "area");
5309     }
5310     std::vector<GeographicExtentNNPtr> geogExtent;
5311     if (j.contains("bbox")) {
5312         auto bbox = getObject(j, "bbox");
5313         double south = getNumber(bbox, "south_latitude");
5314         double west = getNumber(bbox, "west_longitude");
5315         double north = getNumber(bbox, "north_latitude");
5316         double east = getNumber(bbox, "east_longitude");
5317         geogExtent.emplace_back(
5318             GeographicBoundingBox::create(west, south, east, north));
5319     }
5320     if (scope.has_value() || !area.empty() || !geogExtent.empty()) {
5321         util::optional<std::string> description;
5322         if (!area.empty())
5323             description = area;
5324         ExtentPtr extent;
5325         if (description.has_value() || !geogExtent.empty()) {
5326             extent =
5327                 Extent::create(description, geogExtent, {}, {}).as_nullable();
5328         }
5329         return ObjectDomain::create(scope, extent).as_nullable();
5330     }
5331     return nullptr;
5332 }
5333 
5334 // ---------------------------------------------------------------------------
5335 
buildId(const json & j,bool removeInverseOf)5336 IdentifierNNPtr JSONParser::buildId(const json &j, bool removeInverseOf) {
5337 
5338     PropertyMap propertiesId;
5339     auto codeSpace(getString(j, "authority"));
5340     if (removeInverseOf && starts_with(codeSpace, "INVERSE(") &&
5341         codeSpace.back() == ')') {
5342         codeSpace = codeSpace.substr(strlen("INVERSE("));
5343         codeSpace.resize(codeSpace.size() - 1);
5344     }
5345     propertiesId.set(metadata::Identifier::CODESPACE_KEY, codeSpace);
5346     propertiesId.set(metadata::Identifier::AUTHORITY_KEY, codeSpace);
5347     if (!j.contains("code")) {
5348         throw ParsingException("Missing \"code\" key");
5349     }
5350     std::string code;
5351     auto codeJ = j["code"];
5352     if (codeJ.is_string()) {
5353         code = codeJ.get<std::string>();
5354     } else if (codeJ.is_number_integer()) {
5355         code = internal::toString(codeJ.get<int>());
5356     } else {
5357         throw ParsingException("Unexpected type for value of \"code\"");
5358     }
5359     return Identifier::create(code, propertiesId);
5360 }
5361 
5362 // ---------------------------------------------------------------------------
5363 
buildProperties(const json & j,bool removeInverseOf)5364 PropertyMap JSONParser::buildProperties(const json &j, bool removeInverseOf) {
5365     PropertyMap map;
5366     std::string name(getName(j));
5367     if (removeInverseOf && starts_with(name, "Inverse of ")) {
5368         name = name.substr(strlen("Inverse of "));
5369     }
5370     map.set(IdentifiedObject::NAME_KEY, name);
5371 
5372     if (j.contains("ids")) {
5373         auto idsJ = getArray(j, "ids");
5374         auto identifiers = ArrayOfBaseObject::create();
5375         for (const auto &idJ : idsJ) {
5376             if (!idJ.is_object()) {
5377                 throw ParsingException(
5378                     "Unexpected type for value of \"ids\" child");
5379             }
5380             identifiers->add(buildId(idJ, removeInverseOf));
5381         }
5382         map.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
5383     } else if (j.contains("id")) {
5384         auto idJ = getObject(j, "id");
5385         auto identifiers = ArrayOfBaseObject::create();
5386         identifiers->add(buildId(idJ, removeInverseOf));
5387         map.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
5388     }
5389 
5390     if (j.contains("remarks")) {
5391         map.set(IdentifiedObject::REMARKS_KEY, getString(j, "remarks"));
5392     }
5393 
5394     if (j.contains("usages")) {
5395         ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create();
5396         auto usages = j["usages"];
5397         if (!usages.is_array()) {
5398             throw ParsingException("Unexpected type for value of \"usages\"");
5399         }
5400         for (const auto &usage : usages) {
5401             if (!usage.is_object()) {
5402                 throw ParsingException(
5403                     "Unexpected type for value of \"usages\" child");
5404             }
5405             auto objectDomain = buildObjectDomain(usage);
5406             if (!objectDomain) {
5407                 throw ParsingException("missing children in \"usages\" child");
5408             }
5409             array->add(NN_NO_CHECK(objectDomain));
5410         }
5411         if (!array->empty()) {
5412             map.set(ObjectUsage::OBJECT_DOMAIN_KEY, array);
5413         }
5414     } else {
5415         auto objectDomain = buildObjectDomain(j);
5416         if (objectDomain) {
5417             map.set(ObjectUsage::OBJECT_DOMAIN_KEY, NN_NO_CHECK(objectDomain));
5418         }
5419     }
5420 
5421     return map;
5422 }
5423 
5424 // ---------------------------------------------------------------------------
5425 
create(const json & j)5426 BaseObjectNNPtr JSONParser::create(const json &j)
5427 
5428 {
5429     if (!j.is_object()) {
5430         throw ParsingException("JSON object expected");
5431     }
5432     auto type = getString(j, "type");
5433     if (type == "GeographicCRS") {
5434         return buildGeographicCRS(j);
5435     }
5436     if (type == "GeodeticCRS") {
5437         return buildGeodeticCRS(j);
5438     }
5439     if (type == "ProjectedCRS") {
5440         return buildProjectedCRS(j);
5441     }
5442     if (type == "VerticalCRS") {
5443         return buildVerticalCRS(j);
5444     }
5445     if (type == "CompoundCRS") {
5446         return buildCompoundCRS(j);
5447     }
5448     if (type == "BoundCRS") {
5449         return buildBoundCRS(j);
5450     }
5451     if (type == "EngineeringCRS") {
5452         return buildCRS<EngineeringCRS>(j, &JSONParser::buildEngineeringDatum);
5453     }
5454     if (type == "ParametricCRS") {
5455         return buildCRS<ParametricCRS,
5456                         decltype(&JSONParser::buildParametricDatum),
5457                         ParametricCS>(j, &JSONParser::buildParametricDatum);
5458     }
5459     if (type == "TemporalCRS") {
5460         return buildCRS<TemporalCRS, decltype(&JSONParser::buildTemporalDatum),
5461                         TemporalCS>(j, &JSONParser::buildTemporalDatum);
5462     }
5463     if (type == "DerivedGeodeticCRS") {
5464         auto baseCRSObj = create(getObject(j, "base_crs"));
5465         auto baseCRS = util::nn_dynamic_pointer_cast<GeodeticCRS>(baseCRSObj);
5466         if (!baseCRS) {
5467             throw ParsingException("base_crs not of expected type");
5468         }
5469         auto cs = buildCS(getObject(j, "coordinate_system"));
5470         auto conv = buildConversion(getObject(j, "conversion"));
5471         auto csCartesian = util::nn_dynamic_pointer_cast<CartesianCS>(cs);
5472         if (csCartesian)
5473             return DerivedGeodeticCRS::create(buildProperties(j),
5474                                               NN_NO_CHECK(baseCRS), conv,
5475                                               NN_NO_CHECK(csCartesian));
5476         auto csSpherical = util::nn_dynamic_pointer_cast<SphericalCS>(cs);
5477         if (csSpherical)
5478             return DerivedGeodeticCRS::create(buildProperties(j),
5479                                               NN_NO_CHECK(baseCRS), conv,
5480                                               NN_NO_CHECK(csSpherical));
5481         throw ParsingException("coordinate_system not of expected type");
5482     }
5483     if (type == "DerivedGeographicCRS") {
5484         return buildDerivedCRS<DerivedGeographicCRS, GeodeticCRS,
5485                                EllipsoidalCS>(j);
5486     }
5487     if (type == "DerivedProjectedCRS") {
5488         return buildDerivedCRS<DerivedProjectedCRS, ProjectedCRS>(j);
5489     }
5490     if (type == "DerivedVerticalCRS") {
5491         return buildDerivedCRS<DerivedVerticalCRS, VerticalCRS, VerticalCS>(j);
5492     }
5493     if (type == "DerivedEngineeringCRS") {
5494         return buildDerivedCRS<DerivedEngineeringCRS, EngineeringCRS>(j);
5495     }
5496     if (type == "DerivedParametricCRS") {
5497         return buildDerivedCRS<DerivedParametricCRS, ParametricCRS,
5498                                ParametricCS>(j);
5499     }
5500     if (type == "DerivedTemporalCRS") {
5501         return buildDerivedCRS<DerivedTemporalCRS, TemporalCRS, TemporalCS>(j);
5502     }
5503     if (type == "DatumEnsemble") {
5504         return buildDatumEnsemble(j);
5505     }
5506     if (type == "GeodeticReferenceFrame") {
5507         return buildGeodeticReferenceFrame(j);
5508     }
5509     if (type == "VerticalReferenceFrame") {
5510         return buildVerticalReferenceFrame(j);
5511     }
5512     if (type == "DynamicGeodeticReferenceFrame") {
5513         return buildDynamicGeodeticReferenceFrame(j);
5514     }
5515     if (type == "DynamicVerticalReferenceFrame") {
5516         return buildDynamicVerticalReferenceFrame(j);
5517     }
5518     if (type == "EngineeringDatum") {
5519         return buildEngineeringDatum(j);
5520     }
5521     if (type == "ParametricDatum") {
5522         return buildParametricDatum(j);
5523     }
5524     if (type == "TemporalDatum") {
5525         return buildTemporalDatum(j);
5526     }
5527     if (type == "Ellipsoid") {
5528         return buildEllipsoid(j);
5529     }
5530     if (type == "PrimeMeridian") {
5531         return buildPrimeMeridian(j);
5532     }
5533     if (type == "CoordinateSystem") {
5534         return buildCS(j);
5535     }
5536     if (type == "Conversion") {
5537         return buildConversion(j);
5538     }
5539     if (type == "Transformation") {
5540         return buildTransformation(j);
5541     }
5542     if (type == "ConcatenatedOperation") {
5543         return buildConcatenatedOperation(j);
5544     }
5545     throw ParsingException("Unsupported value of \"type\"");
5546 }
5547 
5548 // ---------------------------------------------------------------------------
5549 
buildGeodeticDatumOrDatumEnsemble(const json & j,GeodeticReferenceFramePtr & datum,DatumEnsemblePtr & datumEnsemble)5550 void JSONParser::buildGeodeticDatumOrDatumEnsemble(
5551     const json &j, GeodeticReferenceFramePtr &datum,
5552     DatumEnsemblePtr &datumEnsemble) {
5553     if (j.contains("datum")) {
5554         auto datumJ = getObject(j, "datum");
5555         datum = util::nn_dynamic_pointer_cast<GeodeticReferenceFrame>(
5556             create(datumJ));
5557         if (!datum) {
5558             throw ParsingException("datum of wrong type");
5559         }
5560 
5561     } else {
5562         datumEnsemble =
5563             buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable();
5564     }
5565 }
5566 
5567 // ---------------------------------------------------------------------------
5568 
buildGeographicCRS(const json & j)5569 GeographicCRSNNPtr JSONParser::buildGeographicCRS(const json &j) {
5570     GeodeticReferenceFramePtr datum;
5571     DatumEnsemblePtr datumEnsemble;
5572     buildGeodeticDatumOrDatumEnsemble(j, datum, datumEnsemble);
5573     auto csJ = getObject(j, "coordinate_system");
5574     auto ellipsoidalCS =
5575         util::nn_dynamic_pointer_cast<EllipsoidalCS>(buildCS(csJ));
5576     if (!ellipsoidalCS) {
5577         throw ParsingException("expected an ellipsoidal CS");
5578     }
5579     return GeographicCRS::create(buildProperties(j), datum, datumEnsemble,
5580                                  NN_NO_CHECK(ellipsoidalCS));
5581 }
5582 
5583 // ---------------------------------------------------------------------------
5584 
buildGeodeticCRS(const json & j)5585 GeodeticCRSNNPtr JSONParser::buildGeodeticCRS(const json &j) {
5586     GeodeticReferenceFramePtr datum;
5587     DatumEnsemblePtr datumEnsemble;
5588     buildGeodeticDatumOrDatumEnsemble(j, datum, datumEnsemble);
5589     auto csJ = getObject(j, "coordinate_system");
5590     auto cs = buildCS(csJ);
5591     auto props = buildProperties(j);
5592     auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
5593     if (cartesianCS) {
5594         if (cartesianCS->axisList().size() != 3) {
5595             throw ParsingException(
5596                 "Cartesian CS for a GeodeticCRS should have 3 axis");
5597         }
5598         try {
5599             return GeodeticCRS::create(props, datum, datumEnsemble,
5600                                        NN_NO_CHECK(cartesianCS));
5601         } catch (const util::Exception &e) {
5602             throw ParsingException(std::string("buildGeodeticCRS: ") +
5603                                    e.what());
5604         }
5605     }
5606 
5607     auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
5608     if (sphericalCS) {
5609         try {
5610             return GeodeticCRS::create(props, datum, datumEnsemble,
5611                                        NN_NO_CHECK(sphericalCS));
5612         } catch (const util::Exception &e) {
5613             throw ParsingException(std::string("buildGeodeticCRS: ") +
5614                                    e.what());
5615         }
5616     }
5617     throw ParsingException("expected a Cartesian or spherical CS");
5618 }
5619 
5620 // ---------------------------------------------------------------------------
5621 
buildProjectedCRS(const json & j)5622 ProjectedCRSNNPtr JSONParser::buildProjectedCRS(const json &j) {
5623     auto jBaseCRS = getObject(j, "base_crs");
5624     auto jBaseCS = getObject(jBaseCRS, "coordinate_system");
5625     auto baseCS = buildCS(jBaseCS);
5626     auto baseCRS = dynamic_cast<EllipsoidalCS *>(baseCS.get()) != nullptr
5627                        ? util::nn_static_pointer_cast<GeodeticCRS>(
5628                              buildGeographicCRS(jBaseCRS))
5629                        : buildGeodeticCRS(jBaseCRS);
5630     auto csJ = getObject(j, "coordinate_system");
5631     auto cartesianCS = util::nn_dynamic_pointer_cast<CartesianCS>(buildCS(csJ));
5632     if (!cartesianCS) {
5633         throw ParsingException("expected a Cartesian CS");
5634     }
5635     auto conv = buildConversion(getObject(j, "conversion"));
5636     return ProjectedCRS::create(buildProperties(j), baseCRS, conv,
5637                                 NN_NO_CHECK(cartesianCS));
5638 }
5639 
5640 // ---------------------------------------------------------------------------
5641 
buildVerticalCRS(const json & j)5642 VerticalCRSNNPtr JSONParser::buildVerticalCRS(const json &j) {
5643     VerticalReferenceFramePtr datum;
5644     DatumEnsemblePtr datumEnsemble;
5645     if (j.contains("datum")) {
5646         auto datumJ = getObject(j, "datum");
5647         datum = util::nn_dynamic_pointer_cast<VerticalReferenceFrame>(
5648             create(datumJ));
5649         if (!datum) {
5650             throw ParsingException("datum of wrong type");
5651         }
5652     } else {
5653         datumEnsemble =
5654             buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable();
5655     }
5656     auto csJ = getObject(j, "coordinate_system");
5657     auto verticalCS = util::nn_dynamic_pointer_cast<VerticalCS>(buildCS(csJ));
5658     if (!verticalCS) {
5659         throw ParsingException("expected a vertical CS");
5660     }
5661 
5662     auto props = buildProperties(j);
5663     if (j.contains("geoid_model")) {
5664         auto geoidModelJ = getObject(j, "geoid_model");
5665         auto propsModel = buildProperties(geoidModelJ);
5666         const auto dummyCRS = VerticalCRS::create(
5667             PropertyMap(), datum, datumEnsemble, NN_NO_CHECK(verticalCS));
5668         CRSPtr interpolationCRS;
5669         if (geoidModelJ.contains("interpolation_crs")) {
5670             auto interpolationCRSJ =
5671                 getObject(geoidModelJ, "interpolation_crs");
5672             interpolationCRS = buildCRS(interpolationCRSJ).as_nullable();
5673         }
5674         const auto model(Transformation::create(
5675             propsModel, dummyCRS,
5676             GeographicCRS::EPSG_4979, // arbitrarily chosen. Ignored,
5677             interpolationCRS,
5678             OperationMethod::create(PropertyMap(),
5679                                     std::vector<OperationParameterNNPtr>()),
5680             {}, {}));
5681         props.set("GEOID_MODEL", model);
5682     }
5683 
5684     return VerticalCRS::create(props, datum, datumEnsemble,
5685                                NN_NO_CHECK(verticalCS));
5686 }
5687 
5688 // ---------------------------------------------------------------------------
5689 
buildCRS(const json & j)5690 CRSNNPtr JSONParser::buildCRS(const json &j) {
5691     auto crs = util::nn_dynamic_pointer_cast<CRS>(create(j));
5692     if (crs) {
5693         return NN_NO_CHECK(crs);
5694     }
5695     throw ParsingException("Object is not a CRS");
5696 }
5697 
5698 // ---------------------------------------------------------------------------
5699 
buildCompoundCRS(const json & j)5700 CompoundCRSNNPtr JSONParser::buildCompoundCRS(const json &j) {
5701     auto componentsJ = getArray(j, "components");
5702     std::vector<CRSNNPtr> components;
5703     for (const auto &componentJ : componentsJ) {
5704         if (!componentJ.is_object()) {
5705             throw ParsingException(
5706                 "Unexpected type for a \"components\" child");
5707         }
5708         components.push_back(buildCRS(componentJ));
5709     }
5710     return CompoundCRS::create(buildProperties(j), components);
5711 }
5712 
5713 // ---------------------------------------------------------------------------
5714 
buildConversion(const json & j)5715 ConversionNNPtr JSONParser::buildConversion(const json &j) {
5716     auto methodJ = getObject(j, "method");
5717     auto convProps = buildProperties(j);
5718     auto methodProps = buildProperties(methodJ);
5719     if (!j.contains("parameters")) {
5720         return Conversion::create(convProps, methodProps, {}, {});
5721     }
5722 
5723     auto parametersJ = getArray(j, "parameters");
5724     std::vector<OperationParameterNNPtr> parameters;
5725     std::vector<ParameterValueNNPtr> values;
5726     for (const auto &param : parametersJ) {
5727         if (!param.is_object()) {
5728             throw ParsingException(
5729                 "Unexpected type for a \"parameters\" child");
5730         }
5731         parameters.emplace_back(
5732             OperationParameter::create(buildProperties(param)));
5733         values.emplace_back(ParameterValue::create(getMeasure(param)));
5734     }
5735 
5736     std::string convName;
5737     std::string methodName;
5738     if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) &&
5739         methodProps.getStringValue(IdentifiedObject::NAME_KEY, methodName) &&
5740         starts_with(convName, "Inverse of ") &&
5741         starts_with(methodName, "Inverse of ")) {
5742 
5743         auto invConvProps = buildProperties(j, true);
5744         auto invMethodProps = buildProperties(methodJ, true);
5745         return NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(
5746             Conversion::create(invConvProps, invMethodProps, parameters, values)
5747                 ->inverse()));
5748     }
5749     return Conversion::create(convProps, methodProps, parameters, values);
5750 }
5751 
5752 // ---------------------------------------------------------------------------
5753 
buildBoundCRS(const json & j)5754 BoundCRSNNPtr JSONParser::buildBoundCRS(const json &j) {
5755 
5756     auto sourceCRS = buildCRS(getObject(j, "source_crs"));
5757     auto targetCRS = buildCRS(getObject(j, "target_crs"));
5758     auto transformationJ = getObject(j, "transformation");
5759     auto methodJ = getObject(transformationJ, "method");
5760     auto parametersJ = getArray(transformationJ, "parameters");
5761     std::vector<OperationParameterNNPtr> parameters;
5762     std::vector<ParameterValueNNPtr> values;
5763     for (const auto &param : parametersJ) {
5764         if (!param.is_object()) {
5765             throw ParsingException(
5766                 "Unexpected type for a \"parameters\" child");
5767         }
5768         parameters.emplace_back(
5769             OperationParameter::create(buildProperties(param)));
5770         if (param.contains("value")) {
5771             auto v = param["value"];
5772             if (v.is_string()) {
5773                 values.emplace_back(
5774                     ParameterValue::createFilename(v.get<std::string>()));
5775                 continue;
5776             }
5777         }
5778         values.emplace_back(ParameterValue::create(getMeasure(param)));
5779     }
5780 
5781     const auto sourceTransformationCRS(
5782         createBoundCRSSourceTransformationCRS(sourceCRS, targetCRS));
5783     auto transformation = Transformation::create(
5784         buildProperties(transformationJ), sourceTransformationCRS, targetCRS,
5785         nullptr, buildProperties(methodJ), parameters, values,
5786         std::vector<PositionalAccuracyNNPtr>());
5787 
5788     return BoundCRS::create(sourceCRS, targetCRS, transformation);
5789 }
5790 
5791 // ---------------------------------------------------------------------------
5792 
buildTransformation(const json & j)5793 TransformationNNPtr JSONParser::buildTransformation(const json &j) {
5794 
5795     auto sourceCRS = buildCRS(getObject(j, "source_crs"));
5796     auto targetCRS = buildCRS(getObject(j, "target_crs"));
5797     auto methodJ = getObject(j, "method");
5798     auto parametersJ = getArray(j, "parameters");
5799     std::vector<OperationParameterNNPtr> parameters;
5800     std::vector<ParameterValueNNPtr> values;
5801     for (const auto &param : parametersJ) {
5802         if (!param.is_object()) {
5803             throw ParsingException(
5804                 "Unexpected type for a \"parameters\" child");
5805         }
5806         parameters.emplace_back(
5807             OperationParameter::create(buildProperties(param)));
5808         if (param.contains("value")) {
5809             auto v = param["value"];
5810             if (v.is_string()) {
5811                 values.emplace_back(
5812                     ParameterValue::createFilename(v.get<std::string>()));
5813                 continue;
5814             }
5815         }
5816         values.emplace_back(ParameterValue::create(getMeasure(param)));
5817     }
5818     CRSPtr interpolationCRS;
5819     if (j.contains("interpolation_crs")) {
5820         interpolationCRS =
5821             buildCRS(getObject(j, "interpolation_crs")).as_nullable();
5822     }
5823     std::vector<PositionalAccuracyNNPtr> accuracies;
5824     if (j.contains("accuracy")) {
5825         accuracies.push_back(
5826             PositionalAccuracy::create(getString(j, "accuracy")));
5827     }
5828 
5829     return Transformation::create(buildProperties(j), sourceCRS, targetCRS,
5830                                   interpolationCRS, buildProperties(methodJ),
5831                                   parameters, values, accuracies);
5832 }
5833 
5834 // ---------------------------------------------------------------------------
5835 
5836 ConcatenatedOperationNNPtr
buildConcatenatedOperation(const json & j)5837 JSONParser::buildConcatenatedOperation(const json &j) {
5838 
5839     auto sourceCRS = buildCRS(getObject(j, "source_crs"));
5840     auto targetCRS = buildCRS(getObject(j, "target_crs"));
5841     auto stepsJ = getArray(j, "steps");
5842     std::vector<CoordinateOperationNNPtr> operations;
5843     for (const auto &stepJ : stepsJ) {
5844         if (!stepJ.is_object()) {
5845             throw ParsingException("Unexpected type for a \"steps\" child");
5846         }
5847         auto op = nn_dynamic_pointer_cast<CoordinateOperation>(create(stepJ));
5848         if (!op) {
5849             throw ParsingException("Invalid content in a \"steps\" child");
5850         }
5851         operations.emplace_back(NN_NO_CHECK(op));
5852     }
5853 
5854     ConcatenatedOperation::fixStepsDirection(sourceCRS, targetCRS, operations);
5855 
5856     try {
5857         return ConcatenatedOperation::create(
5858             buildProperties(j), operations,
5859             std::vector<PositionalAccuracyNNPtr>());
5860     } catch (const InvalidOperation &e) {
5861         throw ParsingException(
5862             std::string("Cannot build concatenated operation: ") + e.what());
5863     }
5864 }
5865 
5866 // ---------------------------------------------------------------------------
5867 
buildAxis(const json & j)5868 CoordinateSystemAxisNNPtr JSONParser::buildAxis(const json &j) {
5869     auto dirString = getString(j, "direction");
5870     auto abbreviation = getString(j, "abbreviation");
5871     auto unit = j.contains("unit") ? getUnit(j, "unit")
5872                                    : UnitOfMeasure(std::string(), 1.0,
5873                                                    UnitOfMeasure::Type::NONE);
5874     auto direction = AxisDirection::valueOf(dirString);
5875     if (!direction) {
5876         throw ParsingException(concat("unhandled axis direction: ", dirString));
5877     }
5878     return CoordinateSystemAxis::create(buildProperties(j), abbreviation,
5879                                         *direction, unit,
5880                                         nullptr /* meridian */);
5881 }
5882 
5883 // ---------------------------------------------------------------------------
5884 
buildCS(const json & j)5885 CoordinateSystemNNPtr JSONParser::buildCS(const json &j) {
5886     auto subtype = getString(j, "subtype");
5887     if (!j.contains("axis")) {
5888         throw ParsingException("Missing \"axis\" key");
5889     }
5890     auto jAxisList = j["axis"];
5891     if (!jAxisList.is_array()) {
5892         throw ParsingException("Unexpected type for value of \"axis\"");
5893     }
5894     std::vector<CoordinateSystemAxisNNPtr> axisList;
5895     for (const auto &axis : jAxisList) {
5896         if (!axis.is_object()) {
5897             throw ParsingException(
5898                 "Unexpected type for value of a \"axis\" member");
5899         }
5900         axisList.emplace_back(buildAxis(axis));
5901     }
5902     const PropertyMap &csMap = emptyPropertyMap;
5903     if (subtype == "ellipsoidal") {
5904         if (axisList.size() == 2) {
5905             return EllipsoidalCS::create(csMap, axisList[0], axisList[1]);
5906         }
5907         if (axisList.size() == 3) {
5908             return EllipsoidalCS::create(csMap, axisList[0], axisList[1],
5909                                          axisList[2]);
5910         }
5911         throw ParsingException("Expected 2 or 3 axis");
5912     }
5913     if (subtype == "Cartesian") {
5914         if (axisList.size() == 2) {
5915             return CartesianCS::create(csMap, axisList[0], axisList[1]);
5916         }
5917         if (axisList.size() == 3) {
5918             return CartesianCS::create(csMap, axisList[0], axisList[1],
5919                                        axisList[2]);
5920         }
5921         throw ParsingException("Expected 2 or 3 axis");
5922     }
5923     if (subtype == "vertical") {
5924         if (axisList.size() == 1) {
5925             return VerticalCS::create(csMap, axisList[0]);
5926         }
5927         throw ParsingException("Expected 1 axis");
5928     }
5929     if (subtype == "spherical") {
5930         if (axisList.size() == 3) {
5931             return SphericalCS::create(csMap, axisList[0], axisList[1],
5932                                        axisList[2]);
5933         }
5934         throw ParsingException("Expected 3 axis");
5935     }
5936     if (subtype == "ordinal") {
5937         return OrdinalCS::create(csMap, axisList);
5938     }
5939     if (subtype == "parametric") {
5940         if (axisList.size() == 1) {
5941             return ParametricCS::create(csMap, axisList[0]);
5942         }
5943         throw ParsingException("Expected 1 axis");
5944     }
5945     if (subtype == "TemporalDateTime") {
5946         if (axisList.size() == 1) {
5947             return DateTimeTemporalCS::create(csMap, axisList[0]);
5948         }
5949         throw ParsingException("Expected 1 axis");
5950     }
5951     if (subtype == "TemporalCount") {
5952         if (axisList.size() == 1) {
5953             return TemporalCountCS::create(csMap, axisList[0]);
5954         }
5955         throw ParsingException("Expected 1 axis");
5956     }
5957     if (subtype == "TemporalMeasure") {
5958         if (axisList.size() == 1) {
5959             return TemporalMeasureCS::create(csMap, axisList[0]);
5960         }
5961         throw ParsingException("Expected 1 axis");
5962     }
5963     throw ParsingException("Unhandled value for subtype");
5964 }
5965 
5966 // ---------------------------------------------------------------------------
5967 
buildDatumEnsemble(const json & j)5968 DatumEnsembleNNPtr JSONParser::buildDatumEnsemble(const json &j) {
5969     auto membersJ = getArray(j, "members");
5970     std::vector<DatumNNPtr> datums;
5971     const bool hasEllipsoid(j.contains("ellipsoid"));
5972     for (const auto &memberJ : membersJ) {
5973         if (!memberJ.is_object()) {
5974             throw ParsingException(
5975                 "Unexpected type for value of a \"members\" member");
5976         }
5977         auto datumName(getName(memberJ));
5978         if (dbContext_ && memberJ.contains("id")) {
5979             auto id = getObject(memberJ, "id");
5980             auto authority = getString(id, "authority");
5981             auto authFactory =
5982                 AuthorityFactory::create(NN_NO_CHECK(dbContext_), authority);
5983             auto code = id["code"];
5984             std::string codeStr;
5985             if (code.is_string()) {
5986                 codeStr = code.get<std::string>();
5987             } else if (code.is_number_integer()) {
5988                 codeStr = internal::toString(code.get<int>());
5989             } else {
5990                 throw ParsingException("Unexpected type for value of \"code\"");
5991             }
5992             try {
5993                 datums.push_back(authFactory->createDatum(codeStr));
5994             } catch (const std::exception &) {
5995                 throw ParsingException("No Datum of code " + codeStr);
5996             }
5997             continue;
5998         } else if (dbContext_) {
5999             auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
6000                                                         std::string());
6001             auto list = authFactory->createObjectsFromName(
6002                 datumName, {AuthorityFactory::ObjectType::DATUM},
6003                 false /* approximate=false*/);
6004             if (!list.empty()) {
6005                 auto datum = util::nn_dynamic_pointer_cast<Datum>(list.front());
6006                 if (!datum)
6007                     throw ParsingException(
6008                         "DatumEnsemble member is not a datum");
6009                 datums.push_back(NN_NO_CHECK(datum));
6010                 continue;
6011             }
6012         }
6013 
6014         // Fallback if no db match
6015         if (hasEllipsoid) {
6016             datums.emplace_back(GeodeticReferenceFrame::create(
6017                 buildProperties(memberJ),
6018                 buildEllipsoid(getObject(j, "ellipsoid")),
6019                 optional<std::string>(), PrimeMeridian::GREENWICH));
6020         } else {
6021             datums.emplace_back(
6022                 VerticalReferenceFrame::create(buildProperties(memberJ)));
6023         }
6024     }
6025     return DatumEnsemble::create(
6026         buildProperties(j), datums,
6027         PositionalAccuracy::create(getString(j, "accuracy")));
6028 }
6029 
6030 // ---------------------------------------------------------------------------
6031 
6032 GeodeticReferenceFrameNNPtr
buildGeodeticReferenceFrame(const json & j)6033 JSONParser::buildGeodeticReferenceFrame(const json &j) {
6034     auto ellipsoidJ = getObject(j, "ellipsoid");
6035     auto pm = j.contains("prime_meridian")
6036                   ? buildPrimeMeridian(getObject(j, "prime_meridian"))
6037                   : PrimeMeridian::GREENWICH;
6038     return GeodeticReferenceFrame::create(
6039         buildProperties(j), buildEllipsoid(ellipsoidJ), getAnchor(j), pm);
6040 }
6041 
6042 // ---------------------------------------------------------------------------
6043 
6044 DynamicGeodeticReferenceFrameNNPtr
buildDynamicGeodeticReferenceFrame(const json & j)6045 JSONParser::buildDynamicGeodeticReferenceFrame(const json &j) {
6046     auto ellipsoidJ = getObject(j, "ellipsoid");
6047     auto pm = j.contains("prime_meridian")
6048                   ? buildPrimeMeridian(getObject(j, "prime_meridian"))
6049                   : PrimeMeridian::GREENWICH;
6050     Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"),
6051                                 UnitOfMeasure::YEAR);
6052     optional<std::string> deformationModel;
6053     if (j.contains("deformation_model")) {
6054         deformationModel = getString(j, "deformation_model");
6055     }
6056     return DynamicGeodeticReferenceFrame::create(
6057         buildProperties(j), buildEllipsoid(ellipsoidJ), getAnchor(j), pm,
6058         frameReferenceEpoch, deformationModel);
6059 }
6060 
6061 // ---------------------------------------------------------------------------
6062 
6063 VerticalReferenceFrameNNPtr
buildVerticalReferenceFrame(const json & j)6064 JSONParser::buildVerticalReferenceFrame(const json &j) {
6065     return VerticalReferenceFrame::create(buildProperties(j), getAnchor(j));
6066 }
6067 
6068 // ---------------------------------------------------------------------------
6069 
6070 DynamicVerticalReferenceFrameNNPtr
buildDynamicVerticalReferenceFrame(const json & j)6071 JSONParser::buildDynamicVerticalReferenceFrame(const json &j) {
6072     Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"),
6073                                 UnitOfMeasure::YEAR);
6074     optional<std::string> deformationModel;
6075     if (j.contains("deformation_model")) {
6076         deformationModel = getString(j, "deformation_model");
6077     }
6078     return DynamicVerticalReferenceFrame::create(
6079         buildProperties(j), getAnchor(j), util::optional<RealizationMethod>(),
6080         frameReferenceEpoch, deformationModel);
6081 }
6082 
6083 // ---------------------------------------------------------------------------
6084 
buildPrimeMeridian(const json & j)6085 PrimeMeridianNNPtr JSONParser::buildPrimeMeridian(const json &j) {
6086     if (!j.contains("longitude")) {
6087         throw ParsingException("Missing \"longitude\" key");
6088     }
6089     auto longitude = j["longitude"];
6090     if (longitude.is_number()) {
6091         return PrimeMeridian::create(
6092             buildProperties(j),
6093             Angle(longitude.get<double>(), UnitOfMeasure::DEGREE));
6094     } else if (longitude.is_object()) {
6095         return PrimeMeridian::create(buildProperties(j),
6096                                      Angle(getMeasure(longitude)));
6097     }
6098     throw ParsingException("Unexpected type for value of \"longitude\"");
6099 }
6100 
6101 // ---------------------------------------------------------------------------
6102 
buildEllipsoid(const json & j)6103 EllipsoidNNPtr JSONParser::buildEllipsoid(const json &j) {
6104     if (j.contains("semi_major_axis")) {
6105         auto semiMajorAxis = getLength(j, "semi_major_axis");
6106         const auto celestialBody(
6107             Ellipsoid::guessBodyName(dbContext_, semiMajorAxis.getSIValue()));
6108         if (j.contains("semi_minor_axis")) {
6109             return Ellipsoid::createTwoAxis(buildProperties(j), semiMajorAxis,
6110                                             getLength(j, "semi_minor_axis"),
6111                                             celestialBody);
6112         } else if (j.contains("inverse_flattening")) {
6113             return Ellipsoid::createFlattenedSphere(
6114                 buildProperties(j), semiMajorAxis,
6115                 Scale(getNumber(j, "inverse_flattening")), celestialBody);
6116         } else {
6117             throw ParsingException(
6118                 "Missing semi_minor_axis or inverse_flattening");
6119         }
6120     } else if (j.contains("radius")) {
6121         auto radius = getLength(j, "radius");
6122         const auto celestialBody(
6123             Ellipsoid::guessBodyName(dbContext_, radius.getSIValue()));
6124         return Ellipsoid::createSphere(buildProperties(j), radius,
6125                                        celestialBody);
6126     }
6127     throw ParsingException("Missing semi_major_axis or radius");
6128 }
6129 
6130 // ---------------------------------------------------------------------------
6131 
createFromUserInput(const std::string & text,const DatabaseContextPtr & dbContext,bool usePROJ4InitRules,PJ_CONTEXT * ctx)6132 static BaseObjectNNPtr createFromUserInput(const std::string &text,
6133                                            const DatabaseContextPtr &dbContext,
6134                                            bool usePROJ4InitRules,
6135                                            PJ_CONTEXT *ctx) {
6136     if (!text.empty() && text[0] == '{') {
6137         json j;
6138         try {
6139             j = json::parse(text);
6140         } catch (const std::exception &e) {
6141             throw ParsingException(e.what());
6142         }
6143         return JSONParser().attachDatabaseContext(dbContext).create(j);
6144     }
6145 
6146     if (!ci_starts_with(text, "step proj=") &&
6147         !ci_starts_with(text, "step +proj=")) {
6148         for (const auto &wktConstant : WKTConstants::constants()) {
6149             if (ci_starts_with(text, wktConstant)) {
6150                 for (auto wkt = text.c_str() + wktConstant.size(); *wkt != '\0';
6151                      ++wkt) {
6152                     if (isspace(static_cast<unsigned char>(*wkt)))
6153                         continue;
6154                     if (*wkt == '[') {
6155                         return WKTParser()
6156                             .attachDatabaseContext(dbContext)
6157                             .setStrict(false)
6158                             .createFromWKT(text);
6159                     }
6160                     break;
6161                 }
6162             }
6163         }
6164     }
6165 
6166     const char *textWithoutPlusPrefix = text.c_str();
6167     if (textWithoutPlusPrefix[0] == '+')
6168         textWithoutPlusPrefix++;
6169 
6170     if (strncmp(textWithoutPlusPrefix, "proj=", strlen("proj=")) == 0 ||
6171         text.find(" +proj=") != std::string::npos ||
6172         text.find(" proj=") != std::string::npos ||
6173         strncmp(textWithoutPlusPrefix, "init=", strlen("init=")) == 0 ||
6174         text.find(" +init=") != std::string::npos ||
6175         text.find(" init=") != std::string::npos ||
6176         strncmp(textWithoutPlusPrefix, "title=", strlen("title=")) == 0) {
6177         return PROJStringParser()
6178             .attachDatabaseContext(dbContext)
6179             .attachContext(ctx)
6180             .setUsePROJ4InitRules(ctx != nullptr
6181                                       ? (proj_context_get_use_proj4_init_rules(
6182                                              ctx, false) == TRUE)
6183                                       : usePROJ4InitRules)
6184             .createFromPROJString(text);
6185     }
6186 
6187     auto tokens = split(text, ':');
6188     if (tokens.size() == 2) {
6189         if (!dbContext) {
6190             throw ParsingException("no database context specified");
6191         }
6192         DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext));
6193         const auto &authName = tokens[0];
6194         const auto &code = tokens[1];
6195         auto factory = AuthorityFactory::create(dbContextNNPtr, authName);
6196         try {
6197             return factory->createCoordinateReferenceSystem(code);
6198         } catch (...) {
6199 
6200             // Convenience for well-known misused code
6201             // See https://github.com/OSGeo/PROJ/issues/1730
6202             if (ci_equal(authName, "EPSG") && code == "102100") {
6203                 factory = AuthorityFactory::create(dbContextNNPtr, "ESRI");
6204                 return factory->createCoordinateReferenceSystem(code);
6205             }
6206 
6207             const auto authorities = dbContextNNPtr->getAuthorities();
6208             for (const auto &authCandidate : authorities) {
6209                 if (ci_equal(authCandidate, authName)) {
6210                     factory =
6211                         AuthorityFactory::create(dbContextNNPtr, authCandidate);
6212                     try {
6213                         return factory->createCoordinateReferenceSystem(code);
6214                     } catch (...) {
6215                         // EPSG:4326+3855
6216                         auto tokensCode = split(code, '+');
6217                         if (tokensCode.size() == 2) {
6218                             auto crs1(factory->createCoordinateReferenceSystem(
6219                                 tokensCode[0], false));
6220                             auto crs2(factory->createCoordinateReferenceSystem(
6221                                 tokensCode[1], false));
6222                             return CompoundCRS::createLax(
6223                                 util::PropertyMap().set(
6224                                     IdentifiedObject::NAME_KEY,
6225                                     crs1->nameStr() + " + " + crs2->nameStr()),
6226                                 {crs1, crs2}, dbContext);
6227                         }
6228                         throw;
6229                     }
6230                 }
6231             }
6232             throw;
6233         }
6234     }
6235 
6236     if (starts_with(text, "urn:ogc:def:crs,")) {
6237         if (!dbContext) {
6238             throw ParsingException("no database context specified");
6239         }
6240         auto tokensComma = split(text, ',');
6241         if (tokensComma.size() == 4 && starts_with(tokensComma[1], "crs:") &&
6242             starts_with(tokensComma[2], "cs:") &&
6243             starts_with(tokensComma[3], "coordinateOperation:")) {
6244             // OGC 07-092r2: para 7.5.4
6245             // URN combined references for projected or derived CRSs
6246             const auto &crsPart = tokensComma[1];
6247             const auto tokensCRS = split(crsPart, ':');
6248             if (tokensCRS.size() != 4) {
6249                 throw ParsingException(
6250                     concat("invalid crs component: ", crsPart));
6251             }
6252             auto factoryCRS =
6253                 AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensCRS[1]);
6254             auto baseCRS =
6255                 factoryCRS->createCoordinateReferenceSystem(tokensCRS[3], true);
6256 
6257             const auto &csPart = tokensComma[2];
6258             auto tokensCS = split(csPart, ':');
6259             if (tokensCS.size() != 4) {
6260                 throw ParsingException(
6261                     concat("invalid cs component: ", csPart));
6262             }
6263             auto factoryCS =
6264                 AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensCS[1]);
6265             auto cs = factoryCS->createCoordinateSystem(tokensCS[3]);
6266 
6267             const auto &opPart = tokensComma[3];
6268             auto tokensOp = split(opPart, ':');
6269             if (tokensOp.size() != 4) {
6270                 throw ParsingException(
6271                     concat("invalid coordinateOperation component: ", opPart));
6272             }
6273             auto factoryOp =
6274                 AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensOp[1]);
6275             auto op = factoryOp->createCoordinateOperation(tokensOp[3], true);
6276 
6277             if (dynamic_cast<GeographicCRS *>(baseCRS.get()) &&
6278                 dynamic_cast<Conversion *>(op.get()) &&
6279                 dynamic_cast<CartesianCS *>(cs.get())) {
6280                 auto geogCRS = NN_NO_CHECK(
6281                     util::nn_dynamic_pointer_cast<GeographicCRS>(baseCRS));
6282                 auto name = op->nameStr() + " / " + baseCRS->nameStr();
6283                 if (geogCRS->coordinateSystem()->axisList().size() == 3 &&
6284                     baseCRS->nameStr().find("3D") == std::string::npos) {
6285                     name += " (3D)";
6286                 }
6287                 return ProjectedCRS::create(
6288                     util::PropertyMap().set(IdentifiedObject::NAME_KEY, name),
6289                     geogCRS,
6290                     NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(op)),
6291                     NN_NO_CHECK(
6292                         util::nn_dynamic_pointer_cast<CartesianCS>(cs)));
6293             } else if (dynamic_cast<GeodeticCRS *>(baseCRS.get()) &&
6294                        !dynamic_cast<GeographicCRS *>(baseCRS.get()) &&
6295                        dynamic_cast<Conversion *>(op.get()) &&
6296                        dynamic_cast<CartesianCS *>(cs.get())) {
6297                 return DerivedGeodeticCRS::create(
6298                     util::PropertyMap().set(IdentifiedObject::NAME_KEY,
6299                                             op->nameStr() + " / " +
6300                                                 baseCRS->nameStr()),
6301                     NN_NO_CHECK(
6302                         util::nn_dynamic_pointer_cast<GeodeticCRS>(baseCRS)),
6303                     NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(op)),
6304                     NN_NO_CHECK(
6305                         util::nn_dynamic_pointer_cast<CartesianCS>(cs)));
6306             } else if (dynamic_cast<GeographicCRS *>(baseCRS.get()) &&
6307                        dynamic_cast<Conversion *>(op.get()) &&
6308                        dynamic_cast<EllipsoidalCS *>(cs.get())) {
6309                 return DerivedGeographicCRS::create(
6310                     util::PropertyMap().set(IdentifiedObject::NAME_KEY,
6311                                             op->nameStr() + " / " +
6312                                                 baseCRS->nameStr()),
6313                     NN_NO_CHECK(
6314                         util::nn_dynamic_pointer_cast<GeodeticCRS>(baseCRS)),
6315                     NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(op)),
6316                     NN_NO_CHECK(
6317                         util::nn_dynamic_pointer_cast<EllipsoidalCS>(cs)));
6318             } else if (dynamic_cast<ProjectedCRS *>(baseCRS.get()) &&
6319                        dynamic_cast<Conversion *>(op.get())) {
6320                 return DerivedProjectedCRS::create(
6321                     util::PropertyMap().set(IdentifiedObject::NAME_KEY,
6322                                             op->nameStr() + " / " +
6323                                                 baseCRS->nameStr()),
6324                     NN_NO_CHECK(
6325                         util::nn_dynamic_pointer_cast<ProjectedCRS>(baseCRS)),
6326                     NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(op)),
6327                     cs);
6328             } else if (dynamic_cast<VerticalCRS *>(baseCRS.get()) &&
6329                        dynamic_cast<Conversion *>(op.get()) &&
6330                        dynamic_cast<VerticalCS *>(cs.get())) {
6331                 return DerivedVerticalCRS::create(
6332                     util::PropertyMap().set(IdentifiedObject::NAME_KEY,
6333                                             op->nameStr() + " / " +
6334                                                 baseCRS->nameStr()),
6335                     NN_NO_CHECK(
6336                         util::nn_dynamic_pointer_cast<VerticalCRS>(baseCRS)),
6337                     NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(op)),
6338                     NN_NO_CHECK(util::nn_dynamic_pointer_cast<VerticalCS>(cs)));
6339             } else {
6340                 throw ParsingException("unsupported combination of baseCRS, CS "
6341                                        "and coordinateOperation for a "
6342                                        "DerivedCRS");
6343             }
6344         }
6345 
6346         // OGC 07-092r2: para 7.5.2
6347         // URN combined references for compound coordinate reference systems
6348         std::vector<CRSNNPtr> components;
6349         std::string name;
6350         for (size_t i = 1; i < tokensComma.size(); i++) {
6351             tokens = split(tokensComma[i], ':');
6352             if (tokens.size() != 4) {
6353                 throw ParsingException(
6354                     concat("invalid crs component: ", tokensComma[i]));
6355             }
6356             const auto &type = tokens[0];
6357             auto factory =
6358                 AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[1]);
6359             const auto &code = tokens[3];
6360             if (type == "crs") {
6361                 auto crs(factory->createCoordinateReferenceSystem(code, false));
6362                 components.emplace_back(crs);
6363                 if (!name.empty()) {
6364                     name += " + ";
6365                 }
6366                 name += crs->nameStr();
6367             } else {
6368                 throw ParsingException(
6369                     concat("unexpected object type: ", type));
6370             }
6371         }
6372         return CompoundCRS::create(
6373             util::PropertyMap().set(IdentifiedObject::NAME_KEY, name),
6374             components);
6375     }
6376 
6377     // OGC 07-092r2: para 7.5.3
6378     // 7.5.3 URN combined references for concatenated operations
6379     if (starts_with(text, "urn:ogc:def:coordinateOperation,")) {
6380         if (!dbContext) {
6381             throw ParsingException("no database context specified");
6382         }
6383         auto tokensComma = split(text, ',');
6384         std::vector<CoordinateOperationNNPtr> components;
6385         for (size_t i = 1; i < tokensComma.size(); i++) {
6386             tokens = split(tokensComma[i], ':');
6387             if (tokens.size() != 4) {
6388                 throw ParsingException(concat(
6389                     "invalid coordinateOperation component: ", tokensComma[i]));
6390             }
6391             const auto &type = tokens[0];
6392             auto factory =
6393                 AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[1]);
6394             const auto &code = tokens[3];
6395             if (type == "coordinateOperation") {
6396                 auto op(factory->createCoordinateOperation(code, false));
6397                 components.emplace_back(op);
6398             } else {
6399                 throw ParsingException(
6400                     concat("unexpected object type: ", type));
6401             }
6402         }
6403         return ConcatenatedOperation::createComputeMetadata(components, true);
6404     }
6405 
6406     // urn:ogc:def:crs:EPSG::4326
6407     if (tokens.size() == 7) {
6408         if (!dbContext) {
6409             throw ParsingException("no database context specified");
6410         }
6411         const auto &type = tokens[3];
6412         auto factory =
6413             AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[4]);
6414         const auto &code = tokens[6];
6415         if (type == "crs") {
6416             return factory->createCoordinateReferenceSystem(code);
6417         }
6418         if (type == "coordinateOperation") {
6419             return factory->createCoordinateOperation(code, true);
6420         }
6421         if (type == "datum") {
6422             return factory->createDatum(code);
6423         }
6424         if (type == "ellipsoid") {
6425             return factory->createEllipsoid(code);
6426         }
6427         if (type == "meridian") {
6428             return factory->createPrimeMeridian(code);
6429         }
6430         throw ParsingException(concat("unhandled object type: ", type));
6431     }
6432 
6433     if (dbContext) {
6434         auto factory =
6435             AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string());
6436 
6437         const auto searchObject = [&factory](
6438             const std::string &objectName, bool approximateMatch,
6439             const std::vector<AuthorityFactory::ObjectType> &objectTypes,
6440             bool &goOn) {
6441             constexpr size_t limitResultCount = 10;
6442             auto res = factory->createObjectsFromName(
6443                 objectName, objectTypes, approximateMatch, limitResultCount);
6444             if (res.size() == 1) {
6445                 return res.front();
6446             }
6447             if (res.size() > 1) {
6448                 if (objectTypes.size() == 1 &&
6449                     objectTypes[0] == AuthorityFactory::ObjectType::CRS) {
6450                     for (size_t ndim = 2; ndim <= 3; ndim++) {
6451                         for (const auto &obj : res) {
6452                             auto crs =
6453                                 dynamic_cast<crs::GeographicCRS *>(obj.get());
6454                             if (crs &&
6455                                 crs->coordinateSystem()->axisList().size() ==
6456                                     ndim) {
6457                                 return obj;
6458                             }
6459                         }
6460                     }
6461                 }
6462 
6463                 std::string msg("several objects matching this name: ");
6464                 bool first = true;
6465                 for (const auto &obj : res) {
6466                     if (msg.size() > 200) {
6467                         msg += ", ...";
6468                         break;
6469                     }
6470                     if (!first) {
6471                         msg += ", ";
6472                     }
6473                     first = false;
6474                     msg += obj->nameStr();
6475                 }
6476                 throw ParsingException(msg);
6477             }
6478             goOn = true;
6479             throw ParsingException("dummy");
6480         };
6481 
6482         const auto searchCRS = [&searchObject](const std::string &objectName) {
6483             bool goOn = false;
6484             const auto objectTypes = std::vector<AuthorityFactory::ObjectType>{
6485                 AuthorityFactory::ObjectType::CRS};
6486             try {
6487                 constexpr bool approximateMatch = false;
6488                 return searchObject(objectName, approximateMatch, objectTypes,
6489                                     goOn);
6490             } catch (const std::exception &) {
6491                 if (!goOn)
6492                     throw;
6493             }
6494             constexpr bool approximateMatch = true;
6495             return searchObject(objectName, approximateMatch, objectTypes,
6496                                 goOn);
6497         };
6498 
6499         // strings like "WGS 84 + EGM96 height"
6500         CompoundCRSPtr compoundCRS;
6501         try {
6502             const auto tokensCompound = split(text, " + ");
6503             if (tokensCompound.size() == 2) {
6504                 auto obj1 = searchCRS(tokensCompound[0]);
6505                 auto obj2 = searchCRS(tokensCompound[1]);
6506                 auto crs1 = util::nn_dynamic_pointer_cast<CRS>(obj1);
6507                 auto crs2 = util::nn_dynamic_pointer_cast<CRS>(obj2);
6508                 if (crs1 && crs2) {
6509                     compoundCRS =
6510                         CompoundCRS::create(
6511                             util::PropertyMap().set(IdentifiedObject::NAME_KEY,
6512                                                     crs1->nameStr() + " + " +
6513                                                         crs2->nameStr()),
6514                             {NN_NO_CHECK(crs1), NN_NO_CHECK(crs2)})
6515                             .as_nullable();
6516                 }
6517             }
6518         } catch (const std::exception &) {
6519         }
6520 
6521         // First pass: exact match on CRS objects
6522         // Second pass: exact match on other objects
6523         // Third pass: approximate match on CRS objects
6524         // Fourth pass: approximate match on other objects
6525         for (int pass = 0; pass <= 3; ++pass) {
6526             const bool approximateMatch = (pass >= 2);
6527             bool goOn = false;
6528             try {
6529                 return searchObject(
6530                     text, approximateMatch,
6531                     (pass == 0 || pass == 2)
6532                         ? std::vector<
6533                               AuthorityFactory::ObjectType>{AuthorityFactory::
6534                                                                 ObjectType::CRS}
6535                         : std::vector<
6536                               AuthorityFactory::
6537                                   ObjectType>{AuthorityFactory::ObjectType::
6538                                                   ELLIPSOID,
6539                                               AuthorityFactory::ObjectType::
6540                                                   DATUM,
6541                                               AuthorityFactory::ObjectType::
6542                                                   COORDINATE_OPERATION},
6543                     goOn);
6544             } catch (const std::exception &) {
6545                 if (!goOn)
6546                     throw;
6547             }
6548             if (compoundCRS) {
6549                 return NN_NO_CHECK(compoundCRS);
6550             }
6551         }
6552     }
6553 
6554     throw ParsingException("unrecognized format / unknown name");
6555 }
6556 //! @endcond
6557 
6558 // ---------------------------------------------------------------------------
6559 
6560 /** \brief Instantiate a sub-class of BaseObject from a user specified text.
6561  *
6562  * The text can be a:
6563  * <ul>
6564  * <li>WKT string</li>
6565  * <li>PROJ string</li>
6566  * <li>database code, prefixed by its authoriy. e.g. "EPSG:4326"</li>
6567  * <li>OGC URN. e.g. "urn:ogc:def:crs:EPSG::4326",
6568  *     "urn:ogc:def:coordinateOperation:EPSG::1671",
6569  *     "urn:ogc:def:ellipsoid:EPSG::7001"
6570  *     or "urn:ogc:def:datum:EPSG::6326"</li>
6571  * <li> OGC URN combining references for compound coordinate reference systems
6572  *      e.g. "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717"
6573  *      We also accept a custom abbreviated syntax EPSG:2393+5717
6574  * </li>
6575  * <li> OGC URN combining references for references for projected or derived
6576  * CRSs
6577  *      e.g. for Projected 3D CRS "UTM zone 31N / WGS 84 (3D)"
6578  *      "urn:ogc:def:crs,crs:EPSG::4979,cs:PROJ::ENh,coordinateOperation:EPSG::16031"
6579  * </li>
6580  * <li> OGC URN combining references for concatenated operations
6581  *      e.g.
6582  * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</li>
6583  * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
6584  *     uniqueness is not guaranteed, the function may apply heuristics to
6585  *     determine the appropriate best match.</li>
6586  * <li>a compound CRS made from two object names separated with " + ".
6587  *     e.g. "WGS 84 + EGM96 height"</li>
6588  * <li>PROJJSON string</li>
6589  * </ul>
6590  *
6591  * @param text One of the above mentioned text format
6592  * @param dbContext Database context, or nullptr (in which case database
6593  * lookups will not work)
6594  * @param usePROJ4InitRules When set to true,
6595  * init=epsg:XXXX syntax will be allowed and will be interpreted according to
6596  * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
6597  * order and will expect/output coordinates in radians. ProjectedCRS will have
6598  * easting, northing axis order (except the ones with Transverse Mercator South
6599  * Orientated projection). In that mode, the epsg:XXXX syntax will be also
6600  * interprated the same way.
6601  * @throw ParsingException
6602  */
createFromUserInput(const std::string & text,const DatabaseContextPtr & dbContext,bool usePROJ4InitRules)6603 BaseObjectNNPtr createFromUserInput(const std::string &text,
6604                                     const DatabaseContextPtr &dbContext,
6605                                     bool usePROJ4InitRules) {
6606     return createFromUserInput(text, dbContext, usePROJ4InitRules, nullptr);
6607 }
6608 
6609 // ---------------------------------------------------------------------------
6610 
6611 /** \brief Instantiate a sub-class of BaseObject from a user specified text.
6612  *
6613  * The text can be a:
6614  * <ul>
6615  * <li>WKT string</li>
6616  * <li>PROJ string</li>
6617  * <li>database code, prefixed by its authoriy. e.g. "EPSG:4326"</li>
6618  * <li>OGC URN. e.g. "urn:ogc:def:crs:EPSG::4326",
6619  *     "urn:ogc:def:coordinateOperation:EPSG::1671",
6620  *     "urn:ogc:def:ellipsoid:EPSG::7001"
6621  *     or "urn:ogc:def:datum:EPSG::6326"</li>
6622  * <li> OGC URN combining references for compound coordinate reference systems
6623  *      e.g. "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717"
6624  *      We also accept a custom abbreviated syntax EPSG:2393+5717
6625  * </li>
6626  * <li> OGC URN combining references for references for projected or derived
6627  * CRSs
6628  *      e.g. for Projected 3D CRS "UTM zone 31N / WGS 84 (3D)"
6629  *      "urn:ogc:def:crs,crs:EPSG::4979,cs:PROJ::ENh,coordinateOperation:EPSG::16031"
6630  * </li>
6631  * <li> OGC URN combining references for concatenated operations
6632  *      e.g.
6633  * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</li>
6634  * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
6635  *     uniqueness is not guaranteed, the function may apply heuristics to
6636  *     determine the appropriate best match.</li>
6637  * <li>a compound CRS made from two object names separated with " + ".
6638  *     e.g. "WGS 84 + EGM96 height"</li>
6639  * <li>PROJJSON string</li>
6640  * </ul>
6641  *
6642  * @param text One of the above mentioned text format
6643  * @param ctx PROJ context
6644  * @throw ParsingException
6645  */
createFromUserInput(const std::string & text,PJ_CONTEXT * ctx)6646 BaseObjectNNPtr createFromUserInput(const std::string &text, PJ_CONTEXT *ctx) {
6647     DatabaseContextPtr dbContext;
6648     try {
6649         if (ctx != nullptr && ctx->cpp_context) {
6650             dbContext = ctx->cpp_context->getDatabaseContext().as_nullable();
6651         }
6652     } catch (const std::exception &) {
6653     }
6654     return createFromUserInput(text, dbContext, false, ctx);
6655 }
6656 
6657 // ---------------------------------------------------------------------------
6658 
6659 /** \brief Instantiate a sub-class of BaseObject from a WKT string.
6660  *
6661  * By default, validation is strict (to the extent of the checks that are
6662  * actually implemented. Currently only WKT1 strict grammar is checked), and
6663  * any issue detected will cause an exception to be thrown, unless
6664  * setStrict(false) is called priorly.
6665  *
6666  * In non-strict mode, non-fatal issues will be recovered and simply listed
6667  * in warningList(). This does not prevent more severe errors to cause an
6668  * exception to be thrown.
6669  *
6670  * @throw ParsingException
6671  */
createFromWKT(const std::string & wkt)6672 BaseObjectNNPtr WKTParser::createFromWKT(const std::string &wkt) {
6673     const auto build = [this, &wkt]() -> BaseObjectNNPtr {
6674         size_t indexEnd;
6675         WKTNodeNNPtr root = WKTNode::createFrom(wkt, 0, 0, indexEnd);
6676         const std::string &name(root->GP()->value());
6677         if (ci_equal(name, WKTConstants::DATUM) ||
6678             ci_equal(name, WKTConstants::GEODETICDATUM) ||
6679             ci_equal(name, WKTConstants::TRF)) {
6680 
6681             auto primeMeridian = PrimeMeridian::GREENWICH;
6682             if (indexEnd < wkt.size()) {
6683                 indexEnd = skipSpace(wkt, indexEnd);
6684                 if (indexEnd < wkt.size() && wkt[indexEnd] == ',') {
6685                     ++indexEnd;
6686                     indexEnd = skipSpace(wkt, indexEnd);
6687                     if (indexEnd < wkt.size() &&
6688                         ci_starts_with(wkt.c_str() + indexEnd,
6689                                        WKTConstants::PRIMEM.c_str())) {
6690                         primeMeridian = d->buildPrimeMeridian(
6691                             WKTNode::createFrom(wkt, indexEnd, 0, indexEnd),
6692                             UnitOfMeasure::DEGREE);
6693                     }
6694                 }
6695             }
6696             return d->buildGeodeticReferenceFrame(root, primeMeridian,
6697                                                   null_node);
6698         } else if (ci_equal(name, WKTConstants::GEOGCS) ||
6699                    ci_equal(name, WKTConstants::PROJCS)) {
6700             // Parse implicit compoundCRS from ESRI that is
6701             // "PROJCS[...],VERTCS[...]" or "GEOGCS[...],VERTCS[...]"
6702             if (indexEnd < wkt.size()) {
6703                 indexEnd = skipSpace(wkt, indexEnd);
6704                 if (indexEnd < wkt.size() && wkt[indexEnd] == ',') {
6705                     ++indexEnd;
6706                     indexEnd = skipSpace(wkt, indexEnd);
6707                     if (indexEnd < wkt.size() &&
6708                         ci_starts_with(wkt.c_str() + indexEnd,
6709                                        WKTConstants::VERTCS.c_str())) {
6710                         auto horizCRS = d->buildCRS(root);
6711                         if (horizCRS) {
6712                             auto vertCRS =
6713                                 d->buildVerticalCRS(WKTNode::createFrom(
6714                                     wkt, indexEnd, 0, indexEnd));
6715                             return CompoundCRS::createLax(
6716                                 util::PropertyMap().set(
6717                                     IdentifiedObject::NAME_KEY,
6718                                     horizCRS->nameStr() + " + " +
6719                                         vertCRS->nameStr()),
6720                                 {NN_NO_CHECK(horizCRS), vertCRS},
6721                                 d->dbContext_);
6722                         }
6723                     }
6724                 }
6725             }
6726         }
6727         return d->build(root);
6728     };
6729 
6730     auto obj = build();
6731 
6732     const auto dialect = guessDialect(wkt);
6733     if (dialect == WKTGuessedDialect::WKT1_GDAL ||
6734         dialect == WKTGuessedDialect::WKT1_ESRI) {
6735         auto errorMsg = pj_wkt1_parse(wkt);
6736         if (!errorMsg.empty()) {
6737             d->emitRecoverableWarning(errorMsg);
6738         }
6739     } else if (dialect == WKTGuessedDialect::WKT2_2015 ||
6740                dialect == WKTGuessedDialect::WKT2_2019) {
6741         auto errorMsg = pj_wkt2_parse(wkt);
6742         if (!errorMsg.empty()) {
6743             d->emitRecoverableWarning(errorMsg);
6744         }
6745     }
6746 
6747     return obj;
6748 }
6749 
6750 // ---------------------------------------------------------------------------
6751 
6752 /** \brief Attach a database context, to allow queries in it if needed.
6753  */
6754 WKTParser &
attachDatabaseContext(const DatabaseContextPtr & dbContext)6755 WKTParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) {
6756     d->dbContext_ = dbContext;
6757     return *this;
6758 }
6759 
6760 // ---------------------------------------------------------------------------
6761 
6762 /** \brief Guess the "dialect" of the WKT string.
6763  */
6764 WKTParser::WKTGuessedDialect
guessDialect(const std::string & wkt)6765 WKTParser::guessDialect(const std::string &wkt) noexcept {
6766     if (ci_starts_with(wkt, WKTConstants::VERTCS)) {
6767         return WKTGuessedDialect::WKT1_ESRI;
6768     }
6769     const std::string *const wkt1_keywords[] = {
6770         &WKTConstants::GEOCCS, &WKTConstants::GEOGCS,  &WKTConstants::COMPD_CS,
6771         &WKTConstants::PROJCS, &WKTConstants::VERT_CS, &WKTConstants::LOCAL_CS};
6772     for (const auto &pointerKeyword : wkt1_keywords) {
6773         if (ci_starts_with(wkt, *pointerKeyword)) {
6774 
6775             if (ci_find(wkt, "GEOGCS[\"GCS_") != std::string::npos) {
6776                 return WKTGuessedDialect::WKT1_ESRI;
6777             }
6778 
6779             return WKTGuessedDialect::WKT1_GDAL;
6780         }
6781     }
6782 
6783     const std::string *const wkt2_2019_only_keywords[] = {
6784         &WKTConstants::GEOGCRS,
6785         // contained in previous one
6786         // &WKTConstants::BASEGEOGCRS,
6787         &WKTConstants::CONCATENATEDOPERATION, &WKTConstants::USAGE,
6788         &WKTConstants::DYNAMIC, &WKTConstants::FRAMEEPOCH, &WKTConstants::MODEL,
6789         &WKTConstants::VELOCITYGRID, &WKTConstants::ENSEMBLE,
6790         &WKTConstants::DERIVEDPROJCRS, &WKTConstants::BASEPROJCRS,
6791         &WKTConstants::GEOGRAPHICCRS, &WKTConstants::TRF, &WKTConstants::VRF};
6792 
6793     for (const auto &pointerKeyword : wkt2_2019_only_keywords) {
6794         auto pos = ci_find(wkt, *pointerKeyword);
6795         if (pos != std::string::npos &&
6796             wkt[pos + pointerKeyword->size()] == '[') {
6797             return WKTGuessedDialect::WKT2_2019;
6798         }
6799     }
6800     static const char *const wkt2_2019_only_substrings[] = {
6801         "CS[TemporalDateTime,", "CS[TemporalCount,", "CS[TemporalMeasure,",
6802     };
6803     for (const auto &substrings : wkt2_2019_only_substrings) {
6804         if (ci_find(wkt, substrings) != std::string::npos) {
6805             return WKTGuessedDialect::WKT2_2019;
6806         }
6807     }
6808 
6809     for (const auto &wktConstant : WKTConstants::constants()) {
6810         if (ci_starts_with(wkt, wktConstant)) {
6811             for (auto wktPtr = wkt.c_str() + wktConstant.size();
6812                  *wktPtr != '\0'; ++wktPtr) {
6813                 if (isspace(static_cast<unsigned char>(*wktPtr)))
6814                     continue;
6815                 if (*wktPtr == '[') {
6816                     return WKTGuessedDialect::WKT2_2015;
6817                 }
6818                 break;
6819             }
6820         }
6821     }
6822 
6823     return WKTGuessedDialect::NOT_WKT;
6824 }
6825 
6826 // ---------------------------------------------------------------------------
6827 
6828 //! @cond Doxygen_Suppress
FormattingException(const char * message)6829 FormattingException::FormattingException(const char *message)
6830     : Exception(message) {}
6831 
6832 // ---------------------------------------------------------------------------
6833 
FormattingException(const std::string & message)6834 FormattingException::FormattingException(const std::string &message)
6835     : Exception(message) {}
6836 
6837 // ---------------------------------------------------------------------------
6838 
6839 FormattingException::FormattingException(const FormattingException &) = default;
6840 
6841 // ---------------------------------------------------------------------------
6842 
6843 FormattingException::~FormattingException() = default;
6844 
6845 // ---------------------------------------------------------------------------
6846 
Throw(const char * msg)6847 void FormattingException::Throw(const char *msg) {
6848     throw FormattingException(msg);
6849 }
6850 
6851 // ---------------------------------------------------------------------------
6852 
Throw(const std::string & msg)6853 void FormattingException::Throw(const std::string &msg) {
6854     throw FormattingException(msg);
6855 }
6856 
6857 // ---------------------------------------------------------------------------
6858 
ParsingException(const char * message)6859 ParsingException::ParsingException(const char *message) : Exception(message) {}
6860 
6861 // ---------------------------------------------------------------------------
6862 
ParsingException(const std::string & message)6863 ParsingException::ParsingException(const std::string &message)
6864     : Exception(message) {}
6865 
6866 // ---------------------------------------------------------------------------
6867 
6868 ParsingException::ParsingException(const ParsingException &) = default;
6869 
6870 // ---------------------------------------------------------------------------
6871 
6872 ParsingException::~ParsingException() = default;
6873 
6874 // ---------------------------------------------------------------------------
6875 
6876 IPROJStringExportable::~IPROJStringExportable() = default;
6877 
6878 // ---------------------------------------------------------------------------
6879 
exportToPROJString(PROJStringFormatter * formatter) const6880 std::string IPROJStringExportable::exportToPROJString(
6881     PROJStringFormatter *formatter) const {
6882     const bool bIsCRS = dynamic_cast<const crs::CRS *>(this) != nullptr;
6883     if (bIsCRS) {
6884         formatter->setCRSExport(true);
6885     }
6886     _exportToPROJString(formatter);
6887     if (formatter->getAddNoDefs() && bIsCRS) {
6888         if (!formatter->hasParam("no_defs")) {
6889             formatter->addParam("no_defs");
6890         }
6891     }
6892     if (bIsCRS) {
6893         if (!formatter->hasParam("type")) {
6894             formatter->addParam("type", "crs");
6895         }
6896         formatter->setCRSExport(false);
6897     }
6898     return formatter->toString();
6899 }
6900 //! @endcond
6901 
6902 // ---------------------------------------------------------------------------
6903 
6904 //! @cond Doxygen_Suppress
6905 
6906 struct Step {
6907     std::string name{};
6908     bool isInit = false;
6909     bool inverted{false};
6910 
6911     struct KeyValue {
6912         std::string key{};
6913         std::string value{};
6914         bool usedByParser = false; // only for PROJStringParser used
6915 
KeyValueio::Step::KeyValue6916         explicit KeyValue(const std::string &keyIn) : key(keyIn) {}
6917 
6918         KeyValue(const char *keyIn, const std::string &valueIn);
6919 
KeyValueio::Step::KeyValue6920         KeyValue(const std::string &keyIn, const std::string &valueIn)
6921             : key(keyIn), value(valueIn) {}
6922 
6923         // cppcheck-suppress functionStatic
keyEqualsio::Step::KeyValue6924         bool keyEquals(const char *otherKey) const noexcept {
6925             return key == otherKey;
6926         }
6927 
6928         // cppcheck-suppress functionStatic
equalsio::Step::KeyValue6929         bool equals(const char *otherKey, const char *otherVal) const noexcept {
6930             return key == otherKey && value == otherVal;
6931         }
6932 
operator ==io::Step::KeyValue6933         bool operator==(const KeyValue &other) const noexcept {
6934             return key == other.key && value == other.value;
6935         }
6936 
operator !=io::Step::KeyValue6937         bool operator!=(const KeyValue &other) const noexcept {
6938             return key != other.key || value != other.value;
6939         }
6940     };
6941 
6942     std::vector<KeyValue> paramValues{};
6943 
hasKeyio::Step6944     bool hasKey(const char *keyName) const {
6945         for (const auto &kv : paramValues) {
6946             if (kv.key == keyName) {
6947                 return true;
6948             }
6949         }
6950         return false;
6951     }
6952 };
6953 
KeyValue(const char * keyIn,const std::string & valueIn)6954 Step::KeyValue::KeyValue(const char *keyIn, const std::string &valueIn)
6955     : key(keyIn), value(valueIn) {}
6956 
6957 struct PROJStringFormatter::Private {
6958     PROJStringFormatter::Convention convention_ =
6959         PROJStringFormatter::Convention::PROJ_5;
6960     std::vector<double> toWGS84Parameters_{};
6961     std::string vDatumExtension_{};
6962     std::string hDatumExtension_{};
6963 
6964     std::list<Step> steps_{};
6965     std::vector<Step::KeyValue> globalParamValues_{};
6966 
6967     struct InversionStackElt {
6968         std::list<Step>::iterator startIter{};
6969         bool iterValid = false;
6970         bool currentInversionState = false;
6971     };
6972     std::vector<InversionStackElt> inversionStack_{InversionStackElt()};
6973     bool omitProjLongLatIfPossible_ = false;
6974     std::vector<bool> omitZUnitConversion_{false};
6975     std::vector<bool> omitHorizontalConversionInVertTransformation_{false};
6976     DatabaseContextPtr dbContext_{};
6977     bool useApproxTMerc_ = false;
6978     bool addNoDefs_ = true;
6979     bool coordOperationOptimizations_ = false;
6980     bool crsExport_ = false;
6981     bool legacyCRSToCRSContext_ = false;
6982     bool multiLine_ = false;
6983     int indentWidth_ = 2;
6984     int indentLevel_ = 0;
6985     int maxLineLength_ = 80;
6986 
6987     std::string result_{};
6988 
6989     // cppcheck-suppress functionStatic
6990     void appendToResult(const char *str);
6991 
6992     // cppcheck-suppress functionStatic
6993     void addStep();
6994 };
6995 
6996 //! @endcond
6997 
6998 // ---------------------------------------------------------------------------
6999 
7000 //! @cond Doxygen_Suppress
PROJStringFormatter(Convention conventionIn,const DatabaseContextPtr & dbContext)7001 PROJStringFormatter::PROJStringFormatter(Convention conventionIn,
7002                                          const DatabaseContextPtr &dbContext)
7003     : d(internal::make_unique<Private>()) {
7004     d->convention_ = conventionIn;
7005     d->dbContext_ = dbContext;
7006 }
7007 //! @endcond
7008 
7009 // ---------------------------------------------------------------------------
7010 
7011 //! @cond Doxygen_Suppress
7012 PROJStringFormatter::~PROJStringFormatter() = default;
7013 //! @endcond
7014 
7015 // ---------------------------------------------------------------------------
7016 
7017 /** \brief Constructs a new formatter.
7018  *
7019  * A formatter can be used only once (its internal state is mutated)
7020  *
7021  * Its default behavior can be adjusted with the different setters.
7022  *
7023  * @param conventionIn PROJ string flavor. Defaults to Convention::PROJ_5
7024  * @param dbContext Database context (can help to find alternative grid names).
7025  * May be nullptr
7026  * @return new formatter.
7027  */
7028 PROJStringFormatterNNPtr
create(Convention conventionIn,DatabaseContextPtr dbContext)7029 PROJStringFormatter::create(Convention conventionIn,
7030                             DatabaseContextPtr dbContext) {
7031     return NN_NO_CHECK(PROJStringFormatter::make_unique<PROJStringFormatter>(
7032         conventionIn, dbContext));
7033 }
7034 
7035 // ---------------------------------------------------------------------------
7036 
7037 /** \brief Set whether approximate Transverse Mercator or UTM should be used */
setUseApproxTMerc(bool flag)7038 void PROJStringFormatter::setUseApproxTMerc(bool flag) {
7039     d->useApproxTMerc_ = flag;
7040 }
7041 
7042 // ---------------------------------------------------------------------------
7043 
7044 /** \brief Whether to use multi line output or not. */
7045 PROJStringFormatter &
setMultiLine(bool multiLine)7046 PROJStringFormatter::setMultiLine(bool multiLine) noexcept {
7047     d->multiLine_ = multiLine;
7048     return *this;
7049 }
7050 
7051 // ---------------------------------------------------------------------------
7052 
7053 /** \brief Set number of spaces for each indentation level (defaults to 2).
7054  */
7055 PROJStringFormatter &
setIndentationWidth(int width)7056 PROJStringFormatter::setIndentationWidth(int width) noexcept {
7057     d->indentWidth_ = width;
7058     return *this;
7059 }
7060 
7061 // ---------------------------------------------------------------------------
7062 
7063 /** \brief Set the maximum size of a line (when multiline output is enable).
7064  * Can be set to 0 for unlimited length.
7065  */
7066 PROJStringFormatter &
setMaxLineLength(int maxLineLength)7067 PROJStringFormatter::setMaxLineLength(int maxLineLength) noexcept {
7068     d->maxLineLength_ = maxLineLength;
7069     return *this;
7070 }
7071 
7072 // ---------------------------------------------------------------------------
7073 
7074 /** \brief Returns the PROJ string. */
toString() const7075 const std::string &PROJStringFormatter::toString() const {
7076 
7077     assert(d->inversionStack_.size() == 1);
7078 
7079     d->result_.clear();
7080 
7081     for (auto iter = d->steps_.begin(); iter != d->steps_.end();) {
7082         // Remove no-op helmert
7083         auto &step = *iter;
7084         const auto paramCount = step.paramValues.size();
7085         if (step.name == "helmert" && (paramCount == 3 || paramCount == 8) &&
7086             step.paramValues[0].equals("x", "0") &&
7087             step.paramValues[1].equals("y", "0") &&
7088             step.paramValues[2].equals("z", "0") &&
7089             (paramCount == 3 ||
7090              (step.paramValues[3].equals("rx", "0") &&
7091               step.paramValues[4].equals("ry", "0") &&
7092               step.paramValues[5].equals("rz", "0") &&
7093               step.paramValues[6].equals("s", "0") &&
7094               step.paramValues[7].keyEquals("convention")))) {
7095             iter = d->steps_.erase(iter);
7096         } else if (d->coordOperationOptimizations_ &&
7097                    step.name == "unitconvert" && paramCount == 2 &&
7098                    step.paramValues[0].keyEquals("xy_in") &&
7099                    step.paramValues[1].keyEquals("xy_out") &&
7100                    step.paramValues[0].value == step.paramValues[1].value) {
7101             iter = d->steps_.erase(iter);
7102         } else if (step.name == "push" && step.inverted) {
7103             step.name = "pop";
7104             step.inverted = false;
7105         } else if (step.name == "pop" && step.inverted) {
7106             step.name = "push";
7107             step.inverted = false;
7108         } else {
7109             ++iter;
7110         }
7111     }
7112 
7113     for (auto &step : d->steps_) {
7114         if (!step.inverted) {
7115             continue;
7116         }
7117 
7118         const auto paramCount = step.paramValues.size();
7119 
7120         // axisswap order=2,1 is its own inverse
7121         if (step.name == "axisswap" && paramCount == 1 &&
7122             step.paramValues[0].equals("order", "2,1")) {
7123             step.inverted = false;
7124             continue;
7125         }
7126 
7127         // handle unitconvert inverse
7128         if (step.name == "unitconvert" && paramCount == 2 &&
7129             step.paramValues[0].keyEquals("xy_in") &&
7130             step.paramValues[1].keyEquals("xy_out")) {
7131             std::swap(step.paramValues[0].value, step.paramValues[1].value);
7132             step.inverted = false;
7133             continue;
7134         }
7135 
7136         if (step.name == "unitconvert" && paramCount == 2 &&
7137             step.paramValues[0].keyEquals("z_in") &&
7138             step.paramValues[1].keyEquals("z_out")) {
7139             std::swap(step.paramValues[0].value, step.paramValues[1].value);
7140             step.inverted = false;
7141             continue;
7142         }
7143 
7144         if (step.name == "unitconvert" && paramCount == 4 &&
7145             step.paramValues[0].keyEquals("xy_in") &&
7146             step.paramValues[1].keyEquals("z_in") &&
7147             step.paramValues[2].keyEquals("xy_out") &&
7148             step.paramValues[3].keyEquals("z_out")) {
7149             std::swap(step.paramValues[0].value, step.paramValues[2].value);
7150             std::swap(step.paramValues[1].value, step.paramValues[3].value);
7151             step.inverted = false;
7152             continue;
7153         }
7154     }
7155 
7156     bool changeDone;
7157     do {
7158         changeDone = false;
7159         auto iterPrev = d->steps_.begin();
7160         if (iterPrev == d->steps_.end()) {
7161             break;
7162         }
7163         auto iterCur = iterPrev;
7164         iterCur++;
7165         for (size_t i = 1; i < d->steps_.size(); ++i, ++iterCur, ++iterPrev) {
7166 
7167             auto &prevStep = *iterPrev;
7168             auto &curStep = *iterCur;
7169 
7170             const auto curStepParamCount = curStep.paramValues.size();
7171             const auto prevStepParamCount = prevStep.paramValues.size();
7172 
7173             // longlat (or its inverse) with ellipsoid only is a no-op
7174             // do that only for an internal step
7175             if (i + 1 < d->steps_.size() && curStep.name == "longlat" &&
7176                 curStepParamCount == 1 &&
7177                 curStep.paramValues[0].keyEquals("ellps")) {
7178                 d->steps_.erase(iterCur);
7179                 changeDone = true;
7180                 break;
7181             }
7182 
7183             // push v_x followed by pop v_x is a no-op.
7184             if (curStep.name == "pop" && prevStep.name == "push" &&
7185                 !curStep.inverted && !prevStep.inverted &&
7186                 curStepParamCount == 1 && prevStepParamCount == 1 &&
7187                 curStep.paramValues[0].key == prevStep.paramValues[0].key) {
7188                 ++iterCur;
7189                 d->steps_.erase(iterPrev, iterCur);
7190                 changeDone = true;
7191                 break;
7192             }
7193 
7194             // pop v_x followed by push v_x is, almost, a no-op. For our
7195             // purposes,
7196             // we consider it as a no-op for better pipeline optimizations.
7197             if (curStep.name == "push" && prevStep.name == "pop" &&
7198                 !curStep.inverted && !prevStep.inverted &&
7199                 curStepParamCount == 1 && prevStepParamCount == 1 &&
7200                 curStep.paramValues[0].key == prevStep.paramValues[0].key) {
7201                 ++iterCur;
7202                 d->steps_.erase(iterPrev, iterCur);
7203                 changeDone = true;
7204                 break;
7205             }
7206 
7207             // unitconvert (xy) followed by its inverse is a no-op
7208             if (curStep.name == "unitconvert" &&
7209                 prevStep.name == "unitconvert" && !curStep.inverted &&
7210                 !prevStep.inverted && curStepParamCount == 2 &&
7211                 prevStepParamCount == 2 &&
7212                 curStep.paramValues[0].keyEquals("xy_in") &&
7213                 prevStep.paramValues[0].keyEquals("xy_in") &&
7214                 curStep.paramValues[1].keyEquals("xy_out") &&
7215                 prevStep.paramValues[1].keyEquals("xy_out") &&
7216                 curStep.paramValues[0].value == prevStep.paramValues[1].value &&
7217                 curStep.paramValues[1].value == prevStep.paramValues[0].value) {
7218                 ++iterCur;
7219                 d->steps_.erase(iterPrev, iterCur);
7220                 changeDone = true;
7221                 break;
7222             }
7223 
7224             // unitconvert (z) followed by its inverse is a no-op
7225             if (curStep.name == "unitconvert" &&
7226                 prevStep.name == "unitconvert" && !curStep.inverted &&
7227                 !prevStep.inverted && curStepParamCount == 2 &&
7228                 prevStepParamCount == 2 &&
7229                 curStep.paramValues[0].keyEquals("z_in") &&
7230                 prevStep.paramValues[0].keyEquals("z_in") &&
7231                 curStep.paramValues[1].keyEquals("z_out") &&
7232                 prevStep.paramValues[1].keyEquals("z_out") &&
7233                 curStep.paramValues[0].value == prevStep.paramValues[1].value &&
7234                 curStep.paramValues[1].value == prevStep.paramValues[0].value) {
7235                 ++iterCur;
7236                 d->steps_.erase(iterPrev, iterCur);
7237                 changeDone = true;
7238                 break;
7239             }
7240 
7241             // unitconvert (xyz) followed by its inverse is a no-op
7242             if (curStep.name == "unitconvert" &&
7243                 prevStep.name == "unitconvert" && !curStep.inverted &&
7244                 !prevStep.inverted && curStepParamCount == 4 &&
7245                 prevStepParamCount == 4 &&
7246                 curStep.paramValues[0].keyEquals("xy_in") &&
7247                 prevStep.paramValues[0].keyEquals("xy_in") &&
7248                 curStep.paramValues[1].keyEquals("z_in") &&
7249                 prevStep.paramValues[1].keyEquals("z_in") &&
7250                 curStep.paramValues[2].keyEquals("xy_out") &&
7251                 prevStep.paramValues[2].keyEquals("xy_out") &&
7252                 curStep.paramValues[3].keyEquals("z_out") &&
7253                 prevStep.paramValues[3].keyEquals("z_out") &&
7254                 curStep.paramValues[0].value == prevStep.paramValues[2].value &&
7255                 curStep.paramValues[1].value == prevStep.paramValues[3].value &&
7256                 curStep.paramValues[2].value == prevStep.paramValues[0].value &&
7257                 curStep.paramValues[3].value == prevStep.paramValues[1].value) {
7258                 ++iterCur;
7259                 d->steps_.erase(iterPrev, iterCur);
7260                 changeDone = true;
7261                 break;
7262             }
7263 
7264             // combine unitconvert (xy) and unitconvert (z)
7265             for (int k = 0; k < 2; ++k) {
7266                 auto &first = (k == 0) ? curStep : prevStep;
7267                 auto &second = (k == 0) ? prevStep : curStep;
7268                 if (first.name == "unitconvert" &&
7269                     second.name == "unitconvert" && !first.inverted &&
7270                     !second.inverted && first.paramValues.size() == 2 &&
7271                     second.paramValues.size() == 2 &&
7272                     second.paramValues[0].keyEquals("xy_in") &&
7273                     second.paramValues[1].keyEquals("xy_out") &&
7274                     first.paramValues[0].keyEquals("z_in") &&
7275                     first.paramValues[1].keyEquals("z_out")) {
7276 
7277                     auto xy_in = second.paramValues[0].value;
7278                     auto xy_out = second.paramValues[1].value;
7279                     auto z_in = first.paramValues[0].value;
7280                     auto z_out = first.paramValues[1].value;
7281                     d->steps_.erase(iterPrev, iterCur);
7282                     iterCur->paramValues.clear();
7283                     iterCur->paramValues.emplace_back(
7284                         Step::KeyValue("xy_in", xy_in));
7285                     iterCur->paramValues.emplace_back(
7286                         Step::KeyValue("z_in", z_in));
7287                     iterCur->paramValues.emplace_back(
7288                         Step::KeyValue("xy_out", xy_out));
7289                     iterCur->paramValues.emplace_back(
7290                         Step::KeyValue("z_out", z_out));
7291                     changeDone = true;
7292                     break;
7293                 }
7294             }
7295             if (changeDone) {
7296                 break;
7297             }
7298 
7299             // +step +proj=unitconvert +xy_in=X1 +xy_out=X2
7300             //  +step +proj=unitconvert +xy_in=X2 +z_in=Z1 +xy_out=X1 +z_out=Z2
7301             // ==> step +proj=unitconvert +z_in=Z1 +z_out=Z2
7302             for (int k = 0; k < 2; ++k) {
7303                 auto &first = (k == 0) ? curStep : prevStep;
7304                 auto &second = (k == 0) ? prevStep : curStep;
7305                 if (first.name == "unitconvert" &&
7306                     second.name == "unitconvert" && !first.inverted &&
7307                     !second.inverted && first.paramValues.size() == 4 &&
7308                     second.paramValues.size() == 2 &&
7309                     first.paramValues[0].keyEquals("xy_in") &&
7310                     first.paramValues[1].keyEquals("z_in") &&
7311                     first.paramValues[2].keyEquals("xy_out") &&
7312                     first.paramValues[3].keyEquals("z_out") &&
7313                     second.paramValues[0].keyEquals("xy_in") &&
7314                     second.paramValues[1].keyEquals("xy_out") &&
7315                     first.paramValues[0].value == second.paramValues[1].value &&
7316                     first.paramValues[2].value == second.paramValues[0].value) {
7317                     auto z_in = first.paramValues[1].value;
7318                     auto z_out = first.paramValues[3].value;
7319                     if (z_in != z_out) {
7320                         d->steps_.erase(iterPrev, iterCur);
7321                         iterCur->paramValues.clear();
7322                         iterCur->paramValues.emplace_back(
7323                             Step::KeyValue("z_in", z_in));
7324                         iterCur->paramValues.emplace_back(
7325                             Step::KeyValue("z_out", z_out));
7326                     } else {
7327                         ++iterCur;
7328                         d->steps_.erase(iterPrev, iterCur);
7329                     }
7330                     changeDone = true;
7331                     break;
7332                 }
7333             }
7334             if (changeDone) {
7335                 break;
7336             }
7337 
7338             // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2
7339             //  +step +proj=unitconvert +z_in=Z2 +z_out=Z3
7340             // ==> step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2
7341             // +z_out=Z3
7342             if (prevStep.name == "unitconvert" &&
7343                 curStep.name == "unitconvert" && !prevStep.inverted &&
7344                 !curStep.inverted && prevStep.paramValues.size() == 4 &&
7345                 curStep.paramValues.size() == 2 &&
7346                 prevStep.paramValues[0].keyEquals("xy_in") &&
7347                 prevStep.paramValues[1].keyEquals("z_in") &&
7348                 prevStep.paramValues[2].keyEquals("xy_out") &&
7349                 prevStep.paramValues[3].keyEquals("z_out") &&
7350                 curStep.paramValues[0].keyEquals("z_in") &&
7351                 curStep.paramValues[1].keyEquals("z_out") &&
7352                 prevStep.paramValues[3].value == curStep.paramValues[0].value) {
7353                 auto xy_in = prevStep.paramValues[0].value;
7354                 auto z_in = prevStep.paramValues[1].value;
7355                 auto xy_out = prevStep.paramValues[2].value;
7356                 auto z_out = curStep.paramValues[1].value;
7357                 d->steps_.erase(iterPrev, iterCur);
7358                 iterCur->paramValues.clear();
7359                 iterCur->paramValues.emplace_back(
7360                     Step::KeyValue("xy_in", xy_in));
7361                 iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
7362                 iterCur->paramValues.emplace_back(
7363                     Step::KeyValue("xy_out", xy_out));
7364                 iterCur->paramValues.emplace_back(
7365                     Step::KeyValue("z_out", z_out));
7366                 changeDone = true;
7367                 break;
7368             }
7369 
7370             // unitconvert (1), axisswap order=2,1, unitconvert(2)  ==>
7371             // axisswap order=2,1, unitconvert (1), unitconvert(2) which
7372             // will get further optimized by previous case
7373             if (i + 1 < d->steps_.size() && prevStep.name == "unitconvert" &&
7374                 curStep.name == "axisswap" && curStepParamCount == 1 &&
7375                 curStep.paramValues[0].equals("order", "2,1")) {
7376                 auto iterNext = iterCur;
7377                 ++iterNext;
7378                 auto &nextStep = *iterNext;
7379                 if (nextStep.name == "unitconvert") {
7380                     std::swap(*iterPrev, *iterCur);
7381                     changeDone = true;
7382                     break;
7383                 }
7384             }
7385 
7386             // axisswap order=2,1 followed by itself is a no-op
7387             if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
7388                 curStepParamCount == 1 && prevStepParamCount == 1 &&
7389                 curStep.paramValues[0].equals("order", "2,1") &&
7390                 prevStep.paramValues[0].equals("order", "2,1")) {
7391                 ++iterCur;
7392                 d->steps_.erase(iterPrev, iterCur);
7393                 changeDone = true;
7394                 break;
7395             }
7396 
7397             // axisswap order=2,1, unitconvert, axisswap order=2,1 -> can
7398             // suppress axisswap
7399             if (i + 1 < d->steps_.size() && prevStep.name == "axisswap" &&
7400                 curStep.name == "unitconvert" && prevStepParamCount == 1 &&
7401                 prevStep.paramValues[0].equals("order", "2,1")) {
7402                 auto iterNext = iterCur;
7403                 ++iterNext;
7404                 auto &nextStep = *iterNext;
7405                 if (nextStep.name == "axisswap" &&
7406                     nextStep.paramValues.size() == 1 &&
7407                     nextStep.paramValues[0].equals("order", "2,1")) {
7408                     d->steps_.erase(iterPrev);
7409                     d->steps_.erase(iterNext);
7410                     changeDone = true;
7411                     break;
7412                 }
7413             }
7414 
7415             // for practical purposes WGS84 and GRS80 ellipsoids are
7416             // equivalents (cartesian transform between both lead to differences
7417             // of the order of 1e-14 deg..).
7418             // No need to do a cart roundtrip for that...
7419             // and actually IGNF uses the GRS80 definition for the WGS84 datum
7420             if (curStep.name == "cart" && prevStep.name == "cart" &&
7421                 curStep.inverted == !prevStep.inverted &&
7422                 curStepParamCount == 1 && prevStepParamCount == 1 &&
7423                 ((curStep.paramValues[0].equals("ellps", "WGS84") &&
7424                   prevStep.paramValues[0].equals("ellps", "GRS80")) ||
7425                  (curStep.paramValues[0].equals("ellps", "GRS80") &&
7426                   prevStep.paramValues[0].equals("ellps", "WGS84")))) {
7427                 ++iterCur;
7428                 d->steps_.erase(iterPrev, iterCur);
7429                 changeDone = true;
7430                 break;
7431             }
7432 
7433             if (curStep.name == "helmert" && prevStep.name == "helmert" &&
7434                 !curStep.inverted && !prevStep.inverted &&
7435                 curStepParamCount == 3 &&
7436                 curStepParamCount == prevStepParamCount) {
7437                 std::map<std::string, double> leftParamsMap;
7438                 std::map<std::string, double> rightParamsMap;
7439                 try {
7440                     for (const auto &kv : prevStep.paramValues) {
7441                         leftParamsMap[kv.key] = c_locale_stod(kv.value);
7442                     }
7443                     for (const auto &kv : curStep.paramValues) {
7444                         rightParamsMap[kv.key] = c_locale_stod(kv.value);
7445                     }
7446                 } catch (const std::invalid_argument &) {
7447                     break;
7448                 }
7449                 const std::string x("x");
7450                 const std::string y("y");
7451                 const std::string z("z");
7452                 if (leftParamsMap.find(x) != leftParamsMap.end() &&
7453                     leftParamsMap.find(y) != leftParamsMap.end() &&
7454                     leftParamsMap.find(z) != leftParamsMap.end() &&
7455                     rightParamsMap.find(x) != rightParamsMap.end() &&
7456                     rightParamsMap.find(y) != rightParamsMap.end() &&
7457                     rightParamsMap.find(z) != rightParamsMap.end()) {
7458 
7459                     const double xSum = leftParamsMap[x] + rightParamsMap[x];
7460                     const double ySum = leftParamsMap[y] + rightParamsMap[y];
7461                     const double zSum = leftParamsMap[z] + rightParamsMap[z];
7462                     if (xSum == 0.0 && ySum == 0.0 && zSum == 0.0) {
7463                         ++iterCur;
7464                         d->steps_.erase(iterPrev, iterCur);
7465                     } else {
7466                         prevStep.paramValues[0] =
7467                             Step::KeyValue("x", internal::toString(xSum));
7468                         prevStep.paramValues[1] =
7469                             Step::KeyValue("y", internal::toString(ySum));
7470                         prevStep.paramValues[2] =
7471                             Step::KeyValue("z", internal::toString(zSum));
7472 
7473                         d->steps_.erase(iterCur);
7474                     }
7475                     changeDone = true;
7476                     break;
7477                 }
7478             }
7479 
7480             // hermert followed by its inverse is a no-op
7481             if (curStep.name == "helmert" && prevStep.name == "helmert" &&
7482                 !curStep.inverted && !prevStep.inverted &&
7483                 curStepParamCount == prevStepParamCount) {
7484                 std::set<std::string> leftParamsSet;
7485                 std::set<std::string> rightParamsSet;
7486                 std::map<std::string, std::string> leftParamsMap;
7487                 std::map<std::string, std::string> rightParamsMap;
7488                 for (const auto &kv : prevStep.paramValues) {
7489                     leftParamsSet.insert(kv.key);
7490                     leftParamsMap[kv.key] = kv.value;
7491                 }
7492                 for (const auto &kv : curStep.paramValues) {
7493                     rightParamsSet.insert(kv.key);
7494                     rightParamsMap[kv.key] = kv.value;
7495                 }
7496                 if (leftParamsSet == rightParamsSet) {
7497                     bool doErase = true;
7498                     try {
7499                         for (const auto &param : leftParamsSet) {
7500                             if (param == "convention" || param == "t_epoch" ||
7501                                 param == "t_obs") {
7502                                 if (leftParamsMap[param] !=
7503                                     rightParamsMap[param]) {
7504                                     doErase = false;
7505                                     break;
7506                                 }
7507                             } else if (c_locale_stod(leftParamsMap[param]) !=
7508                                        -c_locale_stod(rightParamsMap[param])) {
7509                                 doErase = false;
7510                                 break;
7511                             }
7512                         }
7513                     } catch (const std::invalid_argument &) {
7514                         break;
7515                     }
7516                     if (doErase) {
7517                         ++iterCur;
7518                         d->steps_.erase(iterPrev, iterCur);
7519                         changeDone = true;
7520                         break;
7521                     }
7522                 }
7523             }
7524 
7525             // +step +proj=hgridshift +grids=grid_A
7526             // +step +proj=vgridshift [...] <== curStep
7527             // +step +inv +proj=hgridshift +grids=grid_A
7528             // ==>
7529             // +step +proj=push +v_1 +v_2
7530             // +step +proj=hgridshift +grids=grid_A +omit_inv
7531             // +step +proj=vgridshift [...]
7532             // +step +inv +proj=hgridshift +grids=grid_A +omit_fwd
7533             // +step +proj=pop +v_1 +v_2
7534             if (i + 1 < d->steps_.size() && prevStep.name == "hgridshift" &&
7535                 prevStepParamCount == 1 && curStep.name == "vgridshift") {
7536                 auto iterNext = iterCur;
7537                 ++iterNext;
7538                 auto &nextStep = *iterNext;
7539                 if (nextStep.name == "hgridshift" &&
7540                     nextStep.inverted != prevStep.inverted &&
7541                     nextStep.paramValues.size() == 1 &&
7542                     prevStep.paramValues[0] == nextStep.paramValues[0]) {
7543                     Step pushStep;
7544                     pushStep.name = "push";
7545                     pushStep.paramValues.emplace_back("v_1");
7546                     pushStep.paramValues.emplace_back("v_2");
7547                     d->steps_.insert(iterPrev, pushStep);
7548 
7549                     prevStep.paramValues.emplace_back("omit_inv");
7550 
7551                     nextStep.paramValues.emplace_back("omit_fwd");
7552 
7553                     Step popStep;
7554                     popStep.name = "pop";
7555                     popStep.paramValues.emplace_back("v_1");
7556                     popStep.paramValues.emplace_back("v_2");
7557                     ++iterNext;
7558                     d->steps_.insert(iterNext, popStep);
7559 
7560                     changeDone = true;
7561                     break;
7562                 }
7563             }
7564 
7565             // detect a step and its inverse
7566             if (curStep.inverted != prevStep.inverted &&
7567                 curStep.name == prevStep.name &&
7568                 curStepParamCount == prevStepParamCount) {
7569                 bool allSame = true;
7570                 for (size_t j = 0; j < curStepParamCount; j++) {
7571                     if (curStep.paramValues[j] != prevStep.paramValues[j]) {
7572                         allSame = false;
7573                         break;
7574                     }
7575                 }
7576                 if (allSame) {
7577                     ++iterCur;
7578                     d->steps_.erase(iterPrev, iterCur);
7579                     changeDone = true;
7580                     break;
7581                 }
7582             }
7583         }
7584     } while (changeDone);
7585 
7586     if (d->steps_.size() > 1 ||
7587         (d->steps_.size() == 1 &&
7588          (d->steps_.front().inverted || d->steps_.front().hasKey("omit_inv") ||
7589           d->steps_.front().hasKey("omit_fwd") ||
7590           !d->globalParamValues_.empty()))) {
7591         d->appendToResult("+proj=pipeline");
7592 
7593         for (const auto &paramValue : d->globalParamValues_) {
7594             d->appendToResult("+");
7595             d->result_ += paramValue.key;
7596             if (!paramValue.value.empty()) {
7597                 d->result_ += '=';
7598                 d->result_ +=
7599                     pj_double_quote_string_param_if_needed(paramValue.value);
7600             }
7601         }
7602 
7603         if (d->multiLine_) {
7604             d->indentLevel_++;
7605         }
7606     }
7607 
7608     for (const auto &step : d->steps_) {
7609         std::string curLine;
7610         if (!d->result_.empty()) {
7611             if (d->multiLine_) {
7612                 curLine = std::string(d->indentLevel_ * d->indentWidth_, ' ');
7613                 curLine += "+step";
7614             } else {
7615                 curLine = " +step";
7616             }
7617         }
7618         if (step.inverted) {
7619             curLine += " +inv";
7620         }
7621         if (!step.name.empty()) {
7622             if (!curLine.empty())
7623                 curLine += ' ';
7624             curLine += step.isInit ? "+init=" : "+proj=";
7625             curLine += step.name;
7626         }
7627         for (const auto &paramValue : step.paramValues) {
7628             std::string newKV = "+";
7629             newKV += paramValue.key;
7630             if (!paramValue.value.empty()) {
7631                 newKV += '=';
7632                 newKV +=
7633                     pj_double_quote_string_param_if_needed(paramValue.value);
7634             }
7635             if (d->maxLineLength_ > 0 && d->multiLine_ &&
7636                 curLine.size() + newKV.size() >
7637                     static_cast<size_t>(d->maxLineLength_)) {
7638                 if (d->multiLine_ && !d->result_.empty())
7639                     d->result_ += '\n';
7640                 d->result_ += curLine;
7641                 curLine = std::string(
7642                     d->indentLevel_ * d->indentWidth_ + strlen("+step "), ' ');
7643             } else {
7644                 if (!curLine.empty())
7645                     curLine += ' ';
7646             }
7647             curLine += newKV;
7648         }
7649         if (d->multiLine_ && !d->result_.empty())
7650             d->result_ += '\n';
7651         d->result_ += curLine;
7652     }
7653 
7654     if (d->result_.empty()) {
7655         d->appendToResult("+proj=noop");
7656     }
7657 
7658     return d->result_;
7659 }
7660 
7661 // ---------------------------------------------------------------------------
7662 
7663 //! @cond Doxygen_Suppress
7664 
convention() const7665 PROJStringFormatter::Convention PROJStringFormatter::convention() const {
7666     return d->convention_;
7667 }
7668 
7669 // ---------------------------------------------------------------------------
7670 
getUseApproxTMerc() const7671 bool PROJStringFormatter::getUseApproxTMerc() const {
7672     return d->useApproxTMerc_;
7673 }
7674 
7675 // ---------------------------------------------------------------------------
7676 
setCoordinateOperationOptimizations(bool enable)7677 void PROJStringFormatter::setCoordinateOperationOptimizations(bool enable) {
7678     d->coordOperationOptimizations_ = enable;
7679 }
7680 
7681 // ---------------------------------------------------------------------------
7682 
appendToResult(const char * str)7683 void PROJStringFormatter::Private::appendToResult(const char *str) {
7684     if (!result_.empty()) {
7685         result_ += ' ';
7686     }
7687     result_ += str;
7688 }
7689 
7690 // ---------------------------------------------------------------------------
7691 
7692 static void
PROJStringSyntaxParser(const std::string & projString,std::vector<Step> & steps,std::vector<Step::KeyValue> & globalParamValues,std::string & title)7693 PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps,
7694                        std::vector<Step::KeyValue> &globalParamValues,
7695                        std::string &title) {
7696     const char *c_str = projString.c_str();
7697     std::vector<std::string> tokens;
7698 
7699     bool hasProj = false;
7700     bool hasInit = false;
7701     bool hasPipeline = false;
7702     {
7703         size_t i = 0;
7704         while (true) {
7705             for (; isspace(static_cast<unsigned char>(c_str[i])); i++) {
7706             }
7707             std::string token;
7708             bool in_string = false;
7709             for (; c_str[i]; i++) {
7710                 if (in_string) {
7711                     if (c_str[i] == '"' && c_str[i + 1] == '"') {
7712                         i++;
7713                     } else if (c_str[i] == '"') {
7714                         in_string = false;
7715                         continue;
7716                     }
7717                 } else if (c_str[i] == '=' && c_str[i + 1] == '"') {
7718                     in_string = true;
7719                     token += c_str[i];
7720                     i++;
7721                     continue;
7722                 } else if (isspace(static_cast<unsigned char>(c_str[i]))) {
7723                     break;
7724                 }
7725                 token += c_str[i];
7726             }
7727             if (in_string) {
7728                 throw ParsingException("Unbalanced double quote");
7729             }
7730             if (token.empty()) {
7731                 break;
7732             }
7733             if (!hasPipeline &&
7734                 (token == "proj=pipeline" || token == "+proj=pipeline")) {
7735                 hasPipeline = true;
7736             } else if (!hasProj && (starts_with(token, "proj=") ||
7737                                     starts_with(token, "+proj="))) {
7738                 hasProj = true;
7739             } else if (!hasInit && (starts_with(token, "init=") ||
7740                                     starts_with(token, "+init="))) {
7741                 hasInit = true;
7742             }
7743             tokens.emplace_back(token);
7744         }
7745     }
7746 
7747     bool prevWasTitle = false;
7748 
7749     if (!hasPipeline) {
7750         if (hasProj || hasInit) {
7751             steps.push_back(Step());
7752         }
7753 
7754         for (auto &word : tokens) {
7755             if (word[0] == '+') {
7756                 word = word.substr(1);
7757             } else if (prevWasTitle && word.find('=') == std::string::npos) {
7758                 title += " ";
7759                 title += word;
7760                 continue;
7761             }
7762 
7763             prevWasTitle = false;
7764             if (starts_with(word, "proj=") && !hasInit) {
7765                 assert(hasProj);
7766                 auto stepName = word.substr(strlen("proj="));
7767                 steps.back().name = stepName;
7768             } else if (starts_with(word, "init=")) {
7769                 assert(hasInit);
7770                 auto initName = word.substr(strlen("init="));
7771                 steps.back().name = initName;
7772                 steps.back().isInit = true;
7773             } else if (word == "inv") {
7774                 if (!steps.empty()) {
7775                     steps.back().inverted = true;
7776                 }
7777             } else if (starts_with(word, "title=")) {
7778                 title = word.substr(strlen("title="));
7779                 prevWasTitle = true;
7780             } else if (word != "step") {
7781                 const auto pos = word.find('=');
7782                 auto key = word.substr(0, pos);
7783 
7784                 auto pair = (pos != std::string::npos)
7785                                 ? Step::KeyValue(key, word.substr(pos + 1))
7786                                 : Step::KeyValue(key);
7787                 if (steps.empty()) {
7788                     globalParamValues.push_back(pair);
7789                 } else {
7790                     steps.back().paramValues.push_back(pair);
7791                 }
7792             }
7793         }
7794         return;
7795     }
7796 
7797     bool inPipeline = false;
7798     bool invGlobal = false;
7799     for (auto &word : tokens) {
7800         if (word[0] == '+') {
7801             word = word.substr(1);
7802         } else if (prevWasTitle && word.find('=') == std::string::npos) {
7803             title += " ";
7804             title += word;
7805             continue;
7806         }
7807 
7808         prevWasTitle = false;
7809         if (word == "proj=pipeline") {
7810             if (inPipeline) {
7811                 throw ParsingException("nested pipeline not supported");
7812             }
7813             inPipeline = true;
7814         } else if (word == "step") {
7815             if (!inPipeline) {
7816                 throw ParsingException("+step found outside pipeline");
7817             }
7818             steps.push_back(Step());
7819         } else if (word == "inv") {
7820             if (steps.empty()) {
7821                 invGlobal = true;
7822             } else {
7823                 steps.back().inverted = true;
7824             }
7825         } else if (inPipeline && !steps.empty() && starts_with(word, "proj=") &&
7826                    steps.back().name.empty()) {
7827             auto stepName = word.substr(strlen("proj="));
7828             steps.back().name = stepName;
7829         } else if (inPipeline && !steps.empty() && starts_with(word, "init=") &&
7830                    steps.back().name.empty()) {
7831             auto initName = word.substr(strlen("init="));
7832             steps.back().name = initName;
7833             steps.back().isInit = true;
7834         } else if (!inPipeline && starts_with(word, "title=")) {
7835             title = word.substr(strlen("title="));
7836             prevWasTitle = true;
7837         } else {
7838             const auto pos = word.find('=');
7839             auto key = word.substr(0, pos);
7840             auto pair = (pos != std::string::npos)
7841                             ? Step::KeyValue(key, word.substr(pos + 1))
7842                             : Step::KeyValue(key);
7843             if (steps.empty()) {
7844                 globalParamValues.push_back(pair);
7845             } else {
7846                 steps.back().paramValues.push_back(pair);
7847             }
7848         }
7849     }
7850     if (invGlobal) {
7851         for (auto &step : steps) {
7852             step.inverted = !step.inverted;
7853         }
7854         std::reverse(steps.begin(), steps.end());
7855     }
7856 }
7857 
7858 // ---------------------------------------------------------------------------
7859 
ingestPROJString(const std::string & str)7860 void PROJStringFormatter::ingestPROJString(
7861     const std::string &str) // throw ParsingException
7862 {
7863     std::vector<Step> steps;
7864     std::string title;
7865     PROJStringSyntaxParser(str, steps, d->globalParamValues_, title);
7866     d->steps_.insert(d->steps_.end(), steps.begin(), steps.end());
7867 }
7868 
7869 // ---------------------------------------------------------------------------
7870 
setCRSExport(bool b)7871 void PROJStringFormatter::setCRSExport(bool b) { d->crsExport_ = b; }
7872 
7873 // ---------------------------------------------------------------------------
7874 
getCRSExport() const7875 bool PROJStringFormatter::getCRSExport() const { return d->crsExport_; }
7876 
7877 // ---------------------------------------------------------------------------
7878 
startInversion()7879 void PROJStringFormatter::startInversion() {
7880     PROJStringFormatter::Private::InversionStackElt elt;
7881     elt.startIter = d->steps_.end();
7882     if (elt.startIter != d->steps_.begin()) {
7883         elt.iterValid = true;
7884         --elt.startIter; // point to the last valid element
7885     } else {
7886         elt.iterValid = false;
7887     }
7888     elt.currentInversionState =
7889         !d->inversionStack_.back().currentInversionState;
7890     d->inversionStack_.push_back(elt);
7891 }
7892 
7893 // ---------------------------------------------------------------------------
7894 
stopInversion()7895 void PROJStringFormatter::stopInversion() {
7896     assert(!d->inversionStack_.empty());
7897     auto startIter = d->inversionStack_.back().startIter;
7898     if (!d->inversionStack_.back().iterValid) {
7899         startIter = d->steps_.begin();
7900     } else {
7901         ++startIter; // advance after the last valid element we marked above
7902     }
7903     // Invert the inversion status of the steps between the start point and
7904     // the current end of steps
7905     for (auto iter = startIter; iter != d->steps_.end(); ++iter) {
7906         iter->inverted = !iter->inverted;
7907         for (auto &paramValue : iter->paramValues) {
7908             if (paramValue.key == "omit_fwd")
7909                 paramValue.key = "omit_inv";
7910             else if (paramValue.key == "omit_inv")
7911                 paramValue.key = "omit_fwd";
7912         }
7913     }
7914     // And reverse the order of steps in that range as well.
7915     std::reverse(startIter, d->steps_.end());
7916     d->inversionStack_.pop_back();
7917 }
7918 
7919 // ---------------------------------------------------------------------------
7920 
isInverted() const7921 bool PROJStringFormatter::isInverted() const {
7922     return d->inversionStack_.back().currentInversionState;
7923 }
7924 
7925 // ---------------------------------------------------------------------------
7926 
addStep()7927 void PROJStringFormatter::Private::addStep() { steps_.emplace_back(Step()); }
7928 
7929 // ---------------------------------------------------------------------------
7930 
addStep(const char * stepName)7931 void PROJStringFormatter::addStep(const char *stepName) {
7932     d->addStep();
7933     d->steps_.back().name.assign(stepName);
7934 }
7935 
7936 // ---------------------------------------------------------------------------
7937 
addStep(const std::string & stepName)7938 void PROJStringFormatter::addStep(const std::string &stepName) {
7939     d->addStep();
7940     d->steps_.back().name = stepName;
7941 }
7942 
7943 // ---------------------------------------------------------------------------
7944 
setCurrentStepInverted(bool inverted)7945 void PROJStringFormatter::setCurrentStepInverted(bool inverted) {
7946     assert(!d->steps_.empty());
7947     d->steps_.back().inverted = inverted;
7948 }
7949 
7950 // ---------------------------------------------------------------------------
7951 
hasParam(const char * paramName) const7952 bool PROJStringFormatter::hasParam(const char *paramName) const {
7953     if (!d->steps_.empty()) {
7954         for (const auto &paramValue : d->steps_.back().paramValues) {
7955             if (paramValue.keyEquals(paramName)) {
7956                 return true;
7957             }
7958         }
7959     }
7960     return false;
7961 }
7962 
7963 // ---------------------------------------------------------------------------
7964 
addNoDefs(bool b)7965 void PROJStringFormatter::addNoDefs(bool b) { d->addNoDefs_ = b; }
7966 
7967 // ---------------------------------------------------------------------------
7968 
getAddNoDefs() const7969 bool PROJStringFormatter::getAddNoDefs() const { return d->addNoDefs_; }
7970 
7971 // ---------------------------------------------------------------------------
7972 
addParam(const std::string & paramName)7973 void PROJStringFormatter::addParam(const std::string &paramName) {
7974     if (d->steps_.empty()) {
7975         d->addStep();
7976     }
7977     d->steps_.back().paramValues.push_back(Step::KeyValue(paramName));
7978 }
7979 
7980 // ---------------------------------------------------------------------------
7981 
addParam(const char * paramName,int val)7982 void PROJStringFormatter::addParam(const char *paramName, int val) {
7983     addParam(std::string(paramName), val);
7984 }
7985 
addParam(const std::string & paramName,int val)7986 void PROJStringFormatter::addParam(const std::string &paramName, int val) {
7987     addParam(paramName, internal::toString(val));
7988 }
7989 
7990 // ---------------------------------------------------------------------------
7991 
formatToString(double val)7992 static std::string formatToString(double val) {
7993     if (std::abs(val * 10 - std::round(val * 10)) < 1e-8) {
7994         // For the purpose of
7995         // https://www.epsg-registry.org/export.htm?wkt=urn:ogc:def:crs:EPSG::27561
7996         // Latitude of natural of origin to be properly rounded from 55 grad
7997         // to
7998         // 49.5 deg
7999         val = std::round(val * 10) / 10;
8000     }
8001     return normalizeSerializedString(internal::toString(val));
8002 }
8003 
8004 // ---------------------------------------------------------------------------
8005 
addParam(const char * paramName,double val)8006 void PROJStringFormatter::addParam(const char *paramName, double val) {
8007     addParam(std::string(paramName), val);
8008 }
8009 
addParam(const std::string & paramName,double val)8010 void PROJStringFormatter::addParam(const std::string &paramName, double val) {
8011     addParam(paramName, formatToString(val));
8012 }
8013 
8014 // ---------------------------------------------------------------------------
8015 
addParam(const char * paramName,const std::vector<double> & vals)8016 void PROJStringFormatter::addParam(const char *paramName,
8017                                    const std::vector<double> &vals) {
8018     std::string paramValue;
8019     for (size_t i = 0; i < vals.size(); ++i) {
8020         if (i > 0) {
8021             paramValue += ',';
8022         }
8023         paramValue += formatToString(vals[i]);
8024     }
8025     addParam(paramName, paramValue);
8026 }
8027 
8028 // ---------------------------------------------------------------------------
8029 
addParam(const char * paramName,const char * val)8030 void PROJStringFormatter::addParam(const char *paramName, const char *val) {
8031     addParam(std::string(paramName), val);
8032 }
8033 
addParam(const char * paramName,const std::string & val)8034 void PROJStringFormatter::addParam(const char *paramName,
8035                                    const std::string &val) {
8036     addParam(std::string(paramName), val);
8037 }
8038 
addParam(const std::string & paramName,const char * val)8039 void PROJStringFormatter::addParam(const std::string &paramName,
8040                                    const char *val) {
8041     addParam(paramName, std::string(val));
8042 }
8043 
8044 // ---------------------------------------------------------------------------
8045 
addParam(const std::string & paramName,const std::string & val)8046 void PROJStringFormatter::addParam(const std::string &paramName,
8047                                    const std::string &val) {
8048     if (d->steps_.empty()) {
8049         d->addStep();
8050     }
8051     d->steps_.back().paramValues.push_back(Step::KeyValue(paramName, val));
8052 }
8053 
8054 // ---------------------------------------------------------------------------
8055 
setTOWGS84Parameters(const std::vector<double> & params)8056 void PROJStringFormatter::setTOWGS84Parameters(
8057     const std::vector<double> &params) {
8058     d->toWGS84Parameters_ = params;
8059 }
8060 
8061 // ---------------------------------------------------------------------------
8062 
getTOWGS84Parameters() const8063 const std::vector<double> &PROJStringFormatter::getTOWGS84Parameters() const {
8064     return d->toWGS84Parameters_;
8065 }
8066 
8067 // ---------------------------------------------------------------------------
8068 
getUsedGridNames() const8069 std::set<std::string> PROJStringFormatter::getUsedGridNames() const {
8070     std::set<std::string> res;
8071     for (const auto &step : d->steps_) {
8072         for (const auto &param : step.paramValues) {
8073             if (param.keyEquals("grids") || param.keyEquals("file")) {
8074                 const auto gridNames = split(param.value, ",");
8075                 for (const auto &gridName : gridNames) {
8076                     res.insert(gridName);
8077                 }
8078             }
8079         }
8080     }
8081     return res;
8082 }
8083 
8084 // ---------------------------------------------------------------------------
8085 
setVDatumExtension(const std::string & filename)8086 void PROJStringFormatter::setVDatumExtension(const std::string &filename) {
8087     d->vDatumExtension_ = filename;
8088 }
8089 
8090 // ---------------------------------------------------------------------------
8091 
getVDatumExtension() const8092 const std::string &PROJStringFormatter::getVDatumExtension() const {
8093     return d->vDatumExtension_;
8094 }
8095 
8096 // ---------------------------------------------------------------------------
8097 
setHDatumExtension(const std::string & filename)8098 void PROJStringFormatter::setHDatumExtension(const std::string &filename) {
8099     d->hDatumExtension_ = filename;
8100 }
8101 
8102 // ---------------------------------------------------------------------------
8103 
getHDatumExtension() const8104 const std::string &PROJStringFormatter::getHDatumExtension() const {
8105     return d->hDatumExtension_;
8106 }
8107 
8108 // ---------------------------------------------------------------------------
8109 
setOmitProjLongLatIfPossible(bool omit)8110 void PROJStringFormatter::setOmitProjLongLatIfPossible(bool omit) {
8111     assert(d->omitProjLongLatIfPossible_ ^ omit);
8112     d->omitProjLongLatIfPossible_ = omit;
8113 }
8114 
8115 // ---------------------------------------------------------------------------
8116 
omitProjLongLatIfPossible() const8117 bool PROJStringFormatter::omitProjLongLatIfPossible() const {
8118     return d->omitProjLongLatIfPossible_;
8119 }
8120 
8121 // ---------------------------------------------------------------------------
8122 
pushOmitZUnitConversion()8123 void PROJStringFormatter::pushOmitZUnitConversion() {
8124     d->omitZUnitConversion_.push_back(true);
8125 }
8126 
8127 // ---------------------------------------------------------------------------
8128 
popOmitZUnitConversion()8129 void PROJStringFormatter::popOmitZUnitConversion() {
8130     assert(d->omitZUnitConversion_.size() > 1);
8131     d->omitZUnitConversion_.pop_back();
8132 }
8133 
8134 // ---------------------------------------------------------------------------
8135 
omitZUnitConversion() const8136 bool PROJStringFormatter::omitZUnitConversion() const {
8137     return d->omitZUnitConversion_.back();
8138 }
8139 
8140 // ---------------------------------------------------------------------------
8141 
pushOmitHorizontalConversionInVertTransformation()8142 void PROJStringFormatter::pushOmitHorizontalConversionInVertTransformation() {
8143     d->omitHorizontalConversionInVertTransformation_.push_back(true);
8144 }
8145 
8146 // ---------------------------------------------------------------------------
8147 
popOmitHorizontalConversionInVertTransformation()8148 void PROJStringFormatter::popOmitHorizontalConversionInVertTransformation() {
8149     assert(d->omitHorizontalConversionInVertTransformation_.size() > 1);
8150     d->omitHorizontalConversionInVertTransformation_.pop_back();
8151 }
8152 
8153 // ---------------------------------------------------------------------------
8154 
omitHorizontalConversionInVertTransformation() const8155 bool PROJStringFormatter::omitHorizontalConversionInVertTransformation() const {
8156     return d->omitHorizontalConversionInVertTransformation_.back();
8157 }
8158 
8159 // ---------------------------------------------------------------------------
8160 
setLegacyCRSToCRSContext(bool legacyContext)8161 void PROJStringFormatter::setLegacyCRSToCRSContext(bool legacyContext) {
8162     d->legacyCRSToCRSContext_ = legacyContext;
8163 }
8164 
8165 // ---------------------------------------------------------------------------
8166 
getLegacyCRSToCRSContext() const8167 bool PROJStringFormatter::getLegacyCRSToCRSContext() const {
8168     return d->legacyCRSToCRSContext_;
8169 }
8170 
8171 // ---------------------------------------------------------------------------
8172 
databaseContext() const8173 const DatabaseContextPtr &PROJStringFormatter::databaseContext() const {
8174     return d->dbContext_;
8175 }
8176 
8177 //! @endcond
8178 
8179 // ---------------------------------------------------------------------------
8180 
8181 //! @cond Doxygen_Suppress
8182 
8183 struct PROJStringParser::Private {
8184     DatabaseContextPtr dbContext_{};
8185     PJ_CONTEXT *ctx_{};
8186     bool usePROJ4InitRules_ = false;
8187     std::vector<std::string> warningList_{};
8188 
8189     std::string projString_{};
8190 
8191     std::vector<Step> steps_{};
8192     std::vector<Step::KeyValue> globalParamValues_{};
8193     std::string title_{};
8194 
8195     bool ignoreNadgrids_ = false;
8196 
8197     template <class T>
8198     // cppcheck-suppress functionStatic
hasParamValueio::PROJStringParser::Private8199     bool hasParamValue(Step &step, const T key) {
8200         for (auto &pair : globalParamValues_) {
8201             if (ci_equal(pair.key, key)) {
8202                 pair.usedByParser = true;
8203                 return true;
8204             }
8205         }
8206         for (auto &pair : step.paramValues) {
8207             if (ci_equal(pair.key, key)) {
8208                 pair.usedByParser = true;
8209                 return true;
8210             }
8211         }
8212         return false;
8213     }
8214 
8215     template <class T>
8216     // cppcheck-suppress functionStatic
getGlobalParamValueio::PROJStringParser::Private8217     const std::string &getGlobalParamValue(T key) {
8218         for (auto &pair : globalParamValues_) {
8219             if (ci_equal(pair.key, key)) {
8220                 pair.usedByParser = true;
8221                 return pair.value;
8222             }
8223         }
8224         return emptyString;
8225     }
8226 
8227     template <class T>
8228     // cppcheck-suppress functionStatic
getParamValueio::PROJStringParser::Private8229     const std::string &getParamValue(Step &step, const T key) {
8230         for (auto &pair : globalParamValues_) {
8231             if (ci_equal(pair.key, key)) {
8232                 pair.usedByParser = true;
8233                 return pair.value;
8234             }
8235         }
8236         for (auto &pair : step.paramValues) {
8237             if (ci_equal(pair.key, key)) {
8238                 pair.usedByParser = true;
8239                 return pair.value;
8240             }
8241         }
8242         return emptyString;
8243     }
8244 
getParamValueKio::PROJStringParser::Private8245     static const std::string &getParamValueK(Step &step) {
8246         for (auto &pair : step.paramValues) {
8247             if (ci_equal(pair.key, "k") || ci_equal(pair.key, "k_0")) {
8248                 pair.usedByParser = true;
8249                 return pair.value;
8250             }
8251         }
8252         return emptyString;
8253     }
8254 
8255     // cppcheck-suppress functionStatic
hasUnusedParametersio::PROJStringParser::Private8256     bool hasUnusedParameters(const Step &step) const {
8257         if (steps_.size() == 1) {
8258             for (const auto &pair : step.paramValues) {
8259                 if (pair.key != "no_defs" && !pair.usedByParser) {
8260                     return true;
8261                 }
8262             }
8263         }
8264         return false;
8265     }
8266 
8267     // cppcheck-suppress functionStatic
8268     std::string guessBodyName(double a);
8269 
8270     PrimeMeridianNNPtr buildPrimeMeridian(Step &step);
8271     GeodeticReferenceFrameNNPtr buildDatum(Step &step,
8272                                            const std::string &title);
8273     GeographicCRSNNPtr buildGeographicCRS(int iStep, int iUnitConvert,
8274                                           int iAxisSwap, bool ignorePROJAxis);
8275     GeodeticCRSNNPtr buildGeocentricCRS(int iStep, int iUnitConvert);
8276     CRSNNPtr buildProjectedCRS(int iStep, GeographicCRSNNPtr geogCRS,
8277                                int iUnitConvert, int iAxisSwap);
8278     CRSNNPtr buildBoundOrCompoundCRSIfNeeded(int iStep, CRSNNPtr crs);
8279     UnitOfMeasure buildUnit(Step &step, const std::string &unitsParamName,
8280                             const std::string &toMeterParamName);
8281 
8282     enum class AxisType { REGULAR, NORTH_POLE, SOUTH_POLE };
8283 
8284     std::vector<CoordinateSystemAxisNNPtr>
8285     processAxisSwap(Step &step, const UnitOfMeasure &unit, int iAxisSwap,
8286                     AxisType axisType, bool ignorePROJAxis);
8287 
8288     EllipsoidalCSNNPtr buildEllipsoidalCS(int iStep, int iUnitConvert,
8289                                           int iAxisSwap, bool ignorePROJAxis);
8290 };
8291 
8292 // ---------------------------------------------------------------------------
8293 
PROJStringParser()8294 PROJStringParser::PROJStringParser() : d(internal::make_unique<Private>()) {}
8295 
8296 // ---------------------------------------------------------------------------
8297 
8298 //! @cond Doxygen_Suppress
8299 PROJStringParser::~PROJStringParser() = default;
8300 //! @endcond
8301 
8302 // ---------------------------------------------------------------------------
8303 
8304 /** \brief Attach a database context, to allow queries in it if needed.
8305  */
8306 PROJStringParser &
attachDatabaseContext(const DatabaseContextPtr & dbContext)8307 PROJStringParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) {
8308     d->dbContext_ = dbContext;
8309     return *this;
8310 }
8311 
8312 // ---------------------------------------------------------------------------
8313 
8314 //! @cond Doxygen_Suppress
attachContext(PJ_CONTEXT * ctx)8315 PROJStringParser &PROJStringParser::attachContext(PJ_CONTEXT *ctx) {
8316     d->ctx_ = ctx;
8317     return *this;
8318 }
8319 //! @endcond
8320 
8321 // ---------------------------------------------------------------------------
8322 
8323 /** \brief Set how init=epsg:XXXX syntax should be interpreted.
8324  *
8325  * @param enable When set to true,
8326  * init=epsg:XXXX syntax will be allowed and will be interpreted according to
8327  * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
8328  * order and will expect/output coordinates in radians. ProjectedCRS will have
8329  * easting, northing axis order (except the ones with Transverse Mercator South
8330  * Orientated projection).
8331  */
setUsePROJ4InitRules(bool enable)8332 PROJStringParser &PROJStringParser::setUsePROJ4InitRules(bool enable) {
8333     d->usePROJ4InitRules_ = enable;
8334     return *this;
8335 }
8336 
8337 // ---------------------------------------------------------------------------
8338 
8339 /** \brief Return the list of warnings found during parsing.
8340  */
warningList() const8341 std::vector<std::string> PROJStringParser::warningList() const {
8342     return d->warningList_;
8343 }
8344 
8345 // ---------------------------------------------------------------------------
8346 
8347 //! @cond Doxygen_Suppress
8348 
8349 // ---------------------------------------------------------------------------
8350 
8351 static const struct LinearUnitDesc {
8352     const char *projName;
8353     const char *convToMeter;
8354     const char *name;
8355     int epsgCode;
8356 } linearUnitDescs[] = {
8357     {"mm", "0.001", "millimetre", 1025},
8358     {"cm", "0.01", "centimetre", 1033},
8359     {"m", "1.0", "metre", 9001},
8360     {"meter", "1.0", "metre", 9001}, // alternative
8361     {"metre", "1.0", "metre", 9001}, // alternative
8362     {"ft", "0.3048", "foot", 9002},
8363     {"us-ft", "0.3048006096012192", "US survey foot", 9003},
8364     {"fath", "1.8288", "fathom", 9014},
8365     {"kmi", "1852", "nautical mile", 9030},
8366     {"us-ch", "20.11684023368047", "US survey chain", 9033},
8367     {"us-mi", "1609.347218694437", "US survey mile", 9035},
8368     {"km", "1000.0", "kilometre", 9036},
8369     {"ind-ft", "0.30479841", "Indian foot (1937)", 9081},
8370     {"ind-yd", "0.91439523", "Indian yard (1937)", 9085},
8371     {"mi", "1609.344", "Statute mile", 9093},
8372     {"yd", "0.9144", "yard", 9096},
8373     {"ch", "20.1168", "chain", 9097},
8374     {"link", "0.201168", "link", 9098},
8375     {"dm", "0.1", "decimetre", 0},                       // no EPSG equivalent
8376     {"in", "0.0254", "inch", 0},                         // no EPSG equivalent
8377     {"us-in", "0.025400050800101", "US survey inch", 0}, // no EPSG equivalent
8378     {"us-yd", "0.914401828803658", "US survey yard", 0}, // no EPSG equivalent
8379     {"ind-ch", "20.11669506", "Indian chain", 0},        // no EPSG equivalent
8380 };
8381 
getLinearUnits(const std::string & projName)8382 static const LinearUnitDesc *getLinearUnits(const std::string &projName) {
8383     for (const auto &desc : linearUnitDescs) {
8384         if (desc.projName == projName)
8385             return &desc;
8386     }
8387     return nullptr;
8388 }
8389 
getLinearUnits(double toMeter)8390 static const LinearUnitDesc *getLinearUnits(double toMeter) {
8391     for (const auto &desc : linearUnitDescs) {
8392         if (std::fabs(c_locale_stod(desc.convToMeter) - toMeter) <
8393             1e-10 * toMeter) {
8394             return &desc;
8395         }
8396     }
8397     return nullptr;
8398 }
8399 
8400 // ---------------------------------------------------------------------------
8401 
_buildUnit(const LinearUnitDesc * unitsMatch)8402 static UnitOfMeasure _buildUnit(const LinearUnitDesc *unitsMatch) {
8403     std::string unitsCode;
8404     if (unitsMatch->epsgCode) {
8405         std::ostringstream buffer;
8406         buffer.imbue(std::locale::classic());
8407         buffer << unitsMatch->epsgCode;
8408         unitsCode = buffer.str();
8409     }
8410     return UnitOfMeasure(
8411         unitsMatch->name, c_locale_stod(unitsMatch->convToMeter),
8412         UnitOfMeasure::Type::LINEAR,
8413         unitsMatch->epsgCode ? Identifier::EPSG : std::string(), unitsCode);
8414 }
8415 
8416 // ---------------------------------------------------------------------------
8417 
_buildUnit(double to_meter_value)8418 static UnitOfMeasure _buildUnit(double to_meter_value) {
8419     // TODO: look-up in EPSG catalog
8420     if (to_meter_value == 0) {
8421         throw ParsingException("invalid unit value");
8422     }
8423     return UnitOfMeasure("unknown", to_meter_value,
8424                          UnitOfMeasure::Type::LINEAR);
8425 }
8426 
8427 // ---------------------------------------------------------------------------
8428 
8429 UnitOfMeasure
buildUnit(Step & step,const std::string & unitsParamName,const std::string & toMeterParamName)8430 PROJStringParser::Private::buildUnit(Step &step,
8431                                      const std::string &unitsParamName,
8432                                      const std::string &toMeterParamName) {
8433     UnitOfMeasure unit = UnitOfMeasure::METRE;
8434     const LinearUnitDesc *unitsMatch = nullptr;
8435     const auto &projUnits = getParamValue(step, unitsParamName);
8436     if (!projUnits.empty()) {
8437         unitsMatch = getLinearUnits(projUnits);
8438         if (unitsMatch == nullptr) {
8439             throw ParsingException("unhandled " + unitsParamName + "=" +
8440                                    projUnits);
8441         }
8442     }
8443 
8444     const auto &toMeter = getParamValue(step, toMeterParamName);
8445     if (!toMeter.empty()) {
8446         double to_meter_value;
8447         try {
8448             to_meter_value = c_locale_stod(toMeter);
8449         } catch (const std::invalid_argument &) {
8450             throw ParsingException("invalid value for " + toMeterParamName);
8451         }
8452         unitsMatch = getLinearUnits(to_meter_value);
8453         if (unitsMatch == nullptr) {
8454             unit = _buildUnit(to_meter_value);
8455         }
8456     }
8457 
8458     if (unitsMatch) {
8459         unit = _buildUnit(unitsMatch);
8460     }
8461 
8462     return unit;
8463 }
8464 
8465 // ---------------------------------------------------------------------------
8466 
8467 static const struct DatumDesc {
8468     const char *projName;
8469     const char *gcsName;
8470     int gcsCode;
8471     const char *datumName;
8472     int datumCode;
8473     const char *ellipsoidName;
8474     int ellipsoidCode;
8475     double a;
8476     double rf;
8477 } datumDescs[] = {
8478     {"GGRS87", "GGRS87", 4121, "Greek Geodetic Reference System 1987", 6121,
8479      "GRS 1980", 7019, 6378137, 298.257222101},
8480     {"potsdam", "DHDN", 4314, "Deutsches Hauptdreiecksnetz", 6314,
8481      "Bessel 1841", 7004, 6377397.155, 299.1528128},
8482     {"carthage", "Carthage", 4223, "Carthage", 6223, "Clarke 1880 (IGN)", 7011,
8483      6378249.2, 293.4660213},
8484     {"hermannskogel", "MGI", 4312, "Militar-Geographische Institut", 6312,
8485      "Bessel 1841", 7004, 6377397.155, 299.1528128},
8486     {"ire65", "TM65", 4299, "TM65", 6299, "Airy Modified 1849", 7002,
8487      6377340.189, 299.3249646},
8488     {"nzgd49", "NZGD49", 4272, "New Zealand Geodetic Datum 1949", 6272,
8489      "International 1924", 7022, 6378388, 297},
8490     {"OSGB36", "OSGB 1936", 4277, "OSGB 1936", 6277, "Airy 1830", 7001,
8491      6377563.396, 299.3249646},
8492 };
8493 
8494 // ---------------------------------------------------------------------------
8495 
isGeographicStep(const std::string & name)8496 static bool isGeographicStep(const std::string &name) {
8497     return name == "longlat" || name == "lonlat" || name == "latlong" ||
8498            name == "latlon";
8499 }
8500 
8501 // ---------------------------------------------------------------------------
8502 
isGeocentricStep(const std::string & name)8503 static bool isGeocentricStep(const std::string &name) {
8504     return name == "geocent" || name == "cart";
8505 }
8506 
8507 // ---------------------------------------------------------------------------
8508 
isProjectedStep(const std::string & name)8509 static bool isProjectedStep(const std::string &name) {
8510     if (name == "etmerc" || name == "utm" ||
8511         !getMappingsFromPROJName(name).empty()) {
8512         return true;
8513     }
8514     // IMPROVE ME: have a better way of distinguishing projections from
8515     // other
8516     // transformations.
8517     if (name == "pipeline" || name == "geoc" || name == "deformation" ||
8518         name == "helmert" || name == "hgridshift" || name == "molodensky" ||
8519         name == "vgridshift") {
8520         return false;
8521     }
8522     const auto *operations = proj_list_operations();
8523     for (int i = 0; operations[i].id != nullptr; ++i) {
8524         if (name == operations[i].id) {
8525             return true;
8526         }
8527     }
8528     return false;
8529 }
8530 
8531 // ---------------------------------------------------------------------------
8532 
createMapWithUnknownName()8533 static PropertyMap createMapWithUnknownName() {
8534     return PropertyMap().set(common::IdentifiedObject::NAME_KEY, "unknown");
8535 }
8536 
8537 // ---------------------------------------------------------------------------
8538 
buildPrimeMeridian(Step & step)8539 PrimeMeridianNNPtr PROJStringParser::Private::buildPrimeMeridian(Step &step) {
8540 
8541     PrimeMeridianNNPtr pm = PrimeMeridian::GREENWICH;
8542     const auto &pmStr = getParamValue(step, "pm");
8543     if (!pmStr.empty()) {
8544         char *end;
8545         double pmValue = dmstor(pmStr.c_str(), &end) * RAD_TO_DEG;
8546         if (pmValue != HUGE_VAL && *end == '\0') {
8547             pm = PrimeMeridian::create(createMapWithUnknownName(),
8548                                        Angle(pmValue));
8549         } else {
8550             bool found = false;
8551             if (pmStr == "paris") {
8552                 found = true;
8553                 pm = PrimeMeridian::PARIS;
8554             }
8555             auto proj_prime_meridians = proj_list_prime_meridians();
8556             for (int i = 0; !found && proj_prime_meridians[i].id != nullptr;
8557                  i++) {
8558                 if (pmStr == proj_prime_meridians[i].id) {
8559                     found = true;
8560                     std::string name = static_cast<char>(::toupper(pmStr[0])) +
8561                                        pmStr.substr(1);
8562                     pmValue = dmstor(proj_prime_meridians[i].defn, nullptr) *
8563                               RAD_TO_DEG;
8564                     pm = PrimeMeridian::create(
8565                         PropertyMap().set(IdentifiedObject::NAME_KEY, name),
8566                         Angle(pmValue));
8567                     break;
8568                 }
8569             }
8570             if (!found) {
8571                 throw ParsingException("unknown pm " + pmStr);
8572             }
8573         }
8574     }
8575     return pm;
8576 }
8577 
8578 // ---------------------------------------------------------------------------
8579 
guessBodyName(double a)8580 std::string PROJStringParser::Private::guessBodyName(double a) {
8581     return Ellipsoid::guessBodyName(dbContext_, a);
8582 }
8583 
8584 // ---------------------------------------------------------------------------
8585 
8586 GeodeticReferenceFrameNNPtr
buildDatum(Step & step,const std::string & title)8587 PROJStringParser::Private::buildDatum(Step &step, const std::string &title) {
8588 
8589     std::string ellpsStr = getParamValue(step, "ellps");
8590     const auto &datumStr = getParamValue(step, "datum");
8591     const auto &RStr = getParamValue(step, "R");
8592     const auto &aStr = getParamValue(step, "a");
8593     const auto &bStr = getParamValue(step, "b");
8594     const auto &rfStr = getParamValue(step, "rf");
8595     const auto &fStr = getParamValue(step, "f");
8596     const auto &esStr = getParamValue(step, "es");
8597     const auto &eStr = getParamValue(step, "e");
8598     double a = -1.0;
8599     double b = -1.0;
8600     double rf = -1.0;
8601     const util::optional<std::string> optionalEmptyString{};
8602     const bool numericParamPresent =
8603         !RStr.empty() || !aStr.empty() || !bStr.empty() || !rfStr.empty() ||
8604         !fStr.empty() || !esStr.empty() || !eStr.empty();
8605 
8606     if (!numericParamPresent && ellpsStr.empty() && datumStr.empty() &&
8607         step.name == "krovak") {
8608         ellpsStr = "bessel";
8609     }
8610 
8611     PrimeMeridianNNPtr pm(buildPrimeMeridian(step));
8612     PropertyMap grfMap;
8613 
8614     // It is arguable that we allow the prime meridian of a datum defined by
8615     // its name to be overridden, but this is found at least in a regression
8616     // test
8617     // of GDAL. So let's keep the ellipsoid part of the datum in that case and
8618     // use the specified prime meridian.
8619     const auto overridePmIfNeeded =
8620         [&pm](const GeodeticReferenceFrameNNPtr &grf) {
8621             if (pm->_isEquivalentTo(PrimeMeridian::GREENWICH.get())) {
8622                 return grf;
8623             } else {
8624                 return GeodeticReferenceFrame::create(
8625                     PropertyMap().set(IdentifiedObject::NAME_KEY,
8626                                       "Unknown based on " +
8627                                           grf->ellipsoid()->nameStr() +
8628                                           " ellipsoid"),
8629                     grf->ellipsoid(), grf->anchorDefinition(), pm);
8630             }
8631         };
8632 
8633     // R take precedence
8634     if (!RStr.empty()) {
8635         double R;
8636         try {
8637             R = c_locale_stod(RStr);
8638         } catch (const std::invalid_argument &) {
8639             throw ParsingException("Invalid R value");
8640         }
8641         auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(),
8642                                                  Length(R), guessBodyName(R));
8643         return GeodeticReferenceFrame::create(
8644             grfMap.set(IdentifiedObject::NAME_KEY,
8645                        title.empty() ? "unknown" : title.c_str()),
8646             ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
8647     }
8648 
8649     if (!datumStr.empty()) {
8650         auto l_datum = [&datumStr, &overridePmIfNeeded, &grfMap,
8651                         &optionalEmptyString, &pm]() {
8652             if (datumStr == "WGS84") {
8653                 return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
8654             } else if (datumStr == "NAD83") {
8655                 return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6269);
8656             } else if (datumStr == "NAD27") {
8657                 return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6267);
8658             } else {
8659 
8660                 for (const auto &datumDesc : datumDescs) {
8661                     if (datumStr == datumDesc.projName) {
8662                         (void)datumDesc.gcsName; // to please cppcheck
8663                         (void)datumDesc.gcsCode; // to please cppcheck
8664                         auto ellipsoid = Ellipsoid::createFlattenedSphere(
8665                             grfMap
8666                                 .set(IdentifiedObject::NAME_KEY,
8667                                      datumDesc.ellipsoidName)
8668                                 .set(Identifier::CODESPACE_KEY,
8669                                      Identifier::EPSG)
8670                                 .set(Identifier::CODE_KEY,
8671                                      datumDesc.ellipsoidCode),
8672                             Length(datumDesc.a), Scale(datumDesc.rf));
8673                         return GeodeticReferenceFrame::create(
8674                             grfMap
8675                                 .set(IdentifiedObject::NAME_KEY,
8676                                      datumDesc.datumName)
8677                                 .set(Identifier::CODESPACE_KEY,
8678                                      Identifier::EPSG)
8679                                 .set(Identifier::CODE_KEY, datumDesc.datumCode),
8680                             ellipsoid, optionalEmptyString, pm);
8681                     }
8682                 }
8683             }
8684             throw ParsingException("unknown datum " + datumStr);
8685         }();
8686         if (!numericParamPresent) {
8687             return l_datum;
8688         }
8689         a = l_datum->ellipsoid()->semiMajorAxis().getSIValue();
8690         rf = l_datum->ellipsoid()->computedInverseFlattening();
8691     }
8692 
8693     else if (!ellpsStr.empty()) {
8694         auto l_datum = [&ellpsStr, &title, &grfMap, &optionalEmptyString,
8695                         &pm]() {
8696             if (ellpsStr == "WGS84") {
8697                 return GeodeticReferenceFrame::create(
8698                     grfMap.set(IdentifiedObject::NAME_KEY,
8699                                title.empty()
8700                                    ? "Unknown based on WGS84 ellipsoid"
8701                                    : title.c_str()),
8702                     Ellipsoid::WGS84, optionalEmptyString, pm);
8703             } else if (ellpsStr == "GRS80") {
8704                 return GeodeticReferenceFrame::create(
8705                     grfMap.set(IdentifiedObject::NAME_KEY,
8706                                title.empty()
8707                                    ? "Unknown based on GRS80 ellipsoid"
8708                                    : title.c_str()),
8709                     Ellipsoid::GRS1980, optionalEmptyString, pm);
8710             } else {
8711                 auto proj_ellps = proj_list_ellps();
8712                 for (int i = 0; proj_ellps[i].id != nullptr; i++) {
8713                     if (ellpsStr == proj_ellps[i].id) {
8714                         assert(strncmp(proj_ellps[i].major, "a=", 2) == 0);
8715                         const double a_iter =
8716                             c_locale_stod(proj_ellps[i].major + 2);
8717                         EllipsoidPtr ellipsoid;
8718                         PropertyMap ellpsMap;
8719                         if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) {
8720                             const double b_iter =
8721                                 c_locale_stod(proj_ellps[i].ell + 2);
8722                             ellipsoid =
8723                                 Ellipsoid::createTwoAxis(
8724                                     ellpsMap.set(IdentifiedObject::NAME_KEY,
8725                                                  proj_ellps[i].name),
8726                                     Length(a_iter), Length(b_iter))
8727                                     .as_nullable();
8728                         } else {
8729                             assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0);
8730                             const double rf_iter =
8731                                 c_locale_stod(proj_ellps[i].ell + 3);
8732                             ellipsoid =
8733                                 Ellipsoid::createFlattenedSphere(
8734                                     ellpsMap.set(IdentifiedObject::NAME_KEY,
8735                                                  proj_ellps[i].name),
8736                                     Length(a_iter), Scale(rf_iter))
8737                                     .as_nullable();
8738                         }
8739                         return GeodeticReferenceFrame::create(
8740                             grfMap.set(IdentifiedObject::NAME_KEY,
8741                                        title.empty()
8742                                            ? std::string("Unknown based on ") +
8743                                                  proj_ellps[i].name +
8744                                                  " ellipsoid"
8745                                            : title),
8746                             NN_NO_CHECK(ellipsoid), optionalEmptyString, pm);
8747                     }
8748                 }
8749                 throw ParsingException("unknown ellipsoid " + ellpsStr);
8750             }
8751         }();
8752         if (!numericParamPresent) {
8753             return l_datum;
8754         }
8755         a = l_datum->ellipsoid()->semiMajorAxis().getSIValue();
8756         if (l_datum->ellipsoid()->semiMinorAxis().has_value()) {
8757             b = l_datum->ellipsoid()->semiMinorAxis()->getSIValue();
8758         } else {
8759             rf = l_datum->ellipsoid()->computedInverseFlattening();
8760         }
8761     }
8762 
8763     if (!aStr.empty()) {
8764         try {
8765             a = c_locale_stod(aStr);
8766         } catch (const std::invalid_argument &) {
8767             throw ParsingException("Invalid a value");
8768         }
8769     }
8770 
8771     if (a > 0 && (b > 0 || !bStr.empty())) {
8772         if (!bStr.empty()) {
8773             try {
8774                 b = c_locale_stod(bStr);
8775             } catch (const std::invalid_argument &) {
8776                 throw ParsingException("Invalid b value");
8777             }
8778         }
8779         auto ellipsoid =
8780             Ellipsoid::createTwoAxis(createMapWithUnknownName(), Length(a),
8781                                      Length(b), guessBodyName(a))
8782                 ->identify();
8783         return GeodeticReferenceFrame::create(
8784             grfMap.set(IdentifiedObject::NAME_KEY,
8785                        title.empty() ? "unknown" : title.c_str()),
8786             ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
8787     }
8788 
8789     else if (a > 0 && (rf >= 0 || !rfStr.empty())) {
8790         if (!rfStr.empty()) {
8791             try {
8792                 rf = c_locale_stod(rfStr);
8793             } catch (const std::invalid_argument &) {
8794                 throw ParsingException("Invalid rf value");
8795             }
8796         }
8797         auto ellipsoid = Ellipsoid::createFlattenedSphere(
8798                              createMapWithUnknownName(), Length(a), Scale(rf),
8799                              guessBodyName(a))
8800                              ->identify();
8801         return GeodeticReferenceFrame::create(
8802             grfMap.set(IdentifiedObject::NAME_KEY,
8803                        title.empty() ? "unknown" : title.c_str()),
8804             ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
8805     }
8806 
8807     else if (a > 0 && !fStr.empty()) {
8808         double f;
8809         try {
8810             f = c_locale_stod(fStr);
8811         } catch (const std::invalid_argument &) {
8812             throw ParsingException("Invalid f value");
8813         }
8814         auto ellipsoid = Ellipsoid::createFlattenedSphere(
8815                              createMapWithUnknownName(), Length(a),
8816                              Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
8817                              ->identify();
8818         return GeodeticReferenceFrame::create(
8819             grfMap.set(IdentifiedObject::NAME_KEY,
8820                        title.empty() ? "unknown" : title.c_str()),
8821             ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
8822     }
8823 
8824     else if (a > 0 && !eStr.empty()) {
8825         double e;
8826         try {
8827             e = c_locale_stod(eStr);
8828         } catch (const std::invalid_argument &) {
8829             throw ParsingException("Invalid e value");
8830         }
8831         double alpha = asin(e);    /* angular eccentricity */
8832         double f = 1 - cos(alpha); /* = 1 - sqrt (1 - es); */
8833         auto ellipsoid = Ellipsoid::createFlattenedSphere(
8834                              createMapWithUnknownName(), Length(a),
8835                              Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
8836                              ->identify();
8837         return GeodeticReferenceFrame::create(
8838             grfMap.set(IdentifiedObject::NAME_KEY,
8839                        title.empty() ? "unknown" : title.c_str()),
8840             ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
8841     }
8842 
8843     else if (a > 0 && !esStr.empty()) {
8844         double es;
8845         try {
8846             es = c_locale_stod(esStr);
8847         } catch (const std::invalid_argument &) {
8848             throw ParsingException("Invalid es value");
8849         }
8850         double f = 1 - sqrt(1 - es);
8851         auto ellipsoid = Ellipsoid::createFlattenedSphere(
8852                              createMapWithUnknownName(), Length(a),
8853                              Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
8854                              ->identify();
8855         return GeodeticReferenceFrame::create(
8856             grfMap.set(IdentifiedObject::NAME_KEY,
8857                        title.empty() ? "unknown" : title.c_str()),
8858             ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
8859     }
8860 
8861     // If only a is specified, create a sphere
8862     if (a > 0 && bStr.empty() && rfStr.empty() && eStr.empty() &&
8863         esStr.empty()) {
8864         auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(),
8865                                                  Length(a), guessBodyName(a));
8866         return GeodeticReferenceFrame::create(
8867             grfMap.set(IdentifiedObject::NAME_KEY,
8868                        title.empty() ? "unknown" : title.c_str()),
8869             ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
8870     }
8871 
8872     if (!bStr.empty() && aStr.empty()) {
8873         throw ParsingException("b found, but a missing");
8874     }
8875 
8876     if (!rfStr.empty() && aStr.empty()) {
8877         throw ParsingException("rf found, but a missing");
8878     }
8879 
8880     if (!fStr.empty() && aStr.empty()) {
8881         throw ParsingException("f found, but a missing");
8882     }
8883 
8884     if (!eStr.empty() && aStr.empty()) {
8885         throw ParsingException("e found, but a missing");
8886     }
8887 
8888     if (!esStr.empty() && aStr.empty()) {
8889         throw ParsingException("es found, but a missing");
8890     }
8891 
8892     return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
8893 }
8894 
8895 // ---------------------------------------------------------------------------
8896 
8897 static const MeridianPtr nullMeridian{};
8898 
8899 static CoordinateSystemAxisNNPtr
createAxis(const std::string & name,const std::string & abbreviation,const AxisDirection & direction,const common::UnitOfMeasure & unit,const MeridianPtr & meridian=nullMeridian)8900 createAxis(const std::string &name, const std::string &abbreviation,
8901            const AxisDirection &direction, const common::UnitOfMeasure &unit,
8902            const MeridianPtr &meridian = nullMeridian) {
8903     return CoordinateSystemAxis::create(
8904         PropertyMap().set(IdentifiedObject::NAME_KEY, name), abbreviation,
8905         direction, unit, meridian);
8906 }
8907 
8908 std::vector<CoordinateSystemAxisNNPtr>
processAxisSwap(Step & step,const UnitOfMeasure & unit,int iAxisSwap,AxisType axisType,bool ignorePROJAxis)8909 PROJStringParser::Private::processAxisSwap(Step &step,
8910                                            const UnitOfMeasure &unit,
8911                                            int iAxisSwap, AxisType axisType,
8912                                            bool ignorePROJAxis) {
8913     assert(iAxisSwap < 0 || ci_equal(steps_[iAxisSwap].name, "axisswap"));
8914 
8915     const bool isGeographic = unit.type() == UnitOfMeasure::Type::ANGULAR;
8916     const auto &eastName =
8917         isGeographic ? AxisName::Longitude : AxisName::Easting;
8918     const auto &eastAbbev =
8919         isGeographic ? AxisAbbreviation::lon : AxisAbbreviation::E;
8920     const auto &eastDir = isGeographic
8921                               ? AxisDirection::EAST
8922                               : (axisType == AxisType::NORTH_POLE)
8923                                     ? AxisDirection::SOUTH
8924                                     : (axisType == AxisType::SOUTH_POLE)
8925                                           ? AxisDirection::NORTH
8926                                           : AxisDirection::EAST;
8927     CoordinateSystemAxisNNPtr east = createAxis(
8928         eastName, eastAbbev, eastDir, unit,
8929         (!isGeographic &&
8930          (axisType == AxisType::NORTH_POLE || axisType == AxisType::SOUTH_POLE))
8931             ? Meridian::create(Angle(90, UnitOfMeasure::DEGREE)).as_nullable()
8932             : nullMeridian);
8933 
8934     const auto &northName =
8935         isGeographic ? AxisName::Latitude : AxisName::Northing;
8936     const auto &northAbbev =
8937         isGeographic ? AxisAbbreviation::lat : AxisAbbreviation::N;
8938     const auto &northDir = isGeographic
8939                                ? AxisDirection::NORTH
8940                                : (axisType == AxisType::NORTH_POLE)
8941                                      ? AxisDirection::SOUTH
8942                                      /*: (axisType == AxisType::SOUTH_POLE)
8943                                            ? AxisDirection::NORTH*/
8944                                      : AxisDirection::NORTH;
8945     CoordinateSystemAxisNNPtr north = createAxis(
8946         northName, northAbbev, northDir, unit,
8947         (!isGeographic && axisType == AxisType::NORTH_POLE)
8948             ? Meridian::create(Angle(180, UnitOfMeasure::DEGREE)).as_nullable()
8949             : (!isGeographic && axisType == AxisType::SOUTH_POLE)
8950                   ? Meridian::create(Angle(0, UnitOfMeasure::DEGREE))
8951                         .as_nullable()
8952                   : nullMeridian);
8953 
8954     CoordinateSystemAxisNNPtr west =
8955         createAxis(isGeographic ? AxisName::Longitude : AxisName::Westing,
8956                    isGeographic ? AxisAbbreviation::lon : std::string(),
8957                    AxisDirection::WEST, unit);
8958 
8959     CoordinateSystemAxisNNPtr south =
8960         createAxis(isGeographic ? AxisName::Latitude : AxisName::Southing,
8961                    isGeographic ? AxisAbbreviation::lat : std::string(),
8962                    AxisDirection::SOUTH, unit);
8963 
8964     std::vector<CoordinateSystemAxisNNPtr> axis{east, north};
8965 
8966     const auto &axisStr = getParamValue(step, "axis");
8967     if (!ignorePROJAxis && !axisStr.empty()) {
8968         if (axisStr.size() == 3) {
8969             for (int i = 0; i < 2; i++) {
8970                 if (axisStr[i] == 'n') {
8971                     axis[i] = north;
8972                 } else if (axisStr[i] == 's') {
8973                     axis[i] = south;
8974                 } else if (axisStr[i] == 'e') {
8975                     axis[i] = east;
8976                 } else if (axisStr[i] == 'w') {
8977                     axis[i] = west;
8978                 } else {
8979                     throw ParsingException("Unhandled axis=" + axisStr);
8980                 }
8981             }
8982         } else {
8983             throw ParsingException("Unhandled axis=" + axisStr);
8984         }
8985     } else if (iAxisSwap >= 0) {
8986         auto &stepAxisSwap = steps_[iAxisSwap];
8987         const auto &orderStr = getParamValue(stepAxisSwap, "order");
8988         auto orderTab = split(orderStr, ',');
8989         if (orderTab.size() != 2) {
8990             throw ParsingException("Unhandled order=" + orderStr);
8991         }
8992         if (stepAxisSwap.inverted) {
8993             throw ParsingException("Unhandled +inv for +proj=axisswap");
8994         }
8995 
8996         for (size_t i = 0; i < 2; i++) {
8997             if (orderTab[i] == "1") {
8998                 axis[i] = east;
8999             } else if (orderTab[i] == "-1") {
9000                 axis[i] = west;
9001             } else if (orderTab[i] == "2") {
9002                 axis[i] = north;
9003             } else if (orderTab[i] == "-2") {
9004                 axis[i] = south;
9005             } else {
9006                 throw ParsingException("Unhandled order=" + orderStr);
9007             }
9008         }
9009     } else if (step.name == "krovak" && hasParamValue(step, "czech")) {
9010         axis[0] = west;
9011         axis[1] = south;
9012     }
9013     return axis;
9014 }
9015 
9016 // ---------------------------------------------------------------------------
9017 
buildEllipsoidalCS(int iStep,int iUnitConvert,int iAxisSwap,bool ignorePROJAxis)9018 EllipsoidalCSNNPtr PROJStringParser::Private::buildEllipsoidalCS(
9019     int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) {
9020     auto &step = steps_[iStep];
9021     assert(iUnitConvert < 0 ||
9022            ci_equal(steps_[iUnitConvert].name, "unitconvert"));
9023 
9024     UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE;
9025     if (iUnitConvert >= 0) {
9026         auto &stepUnitConvert = steps_[iUnitConvert];
9027         const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
9028         const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
9029         if (stepUnitConvert.inverted) {
9030             std::swap(xy_in, xy_out);
9031         }
9032         if (iUnitConvert < iStep) {
9033             std::swap(xy_in, xy_out);
9034         }
9035         if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" ||
9036             (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) {
9037             throw ParsingException("unhandled values for xy_in and/or xy_out");
9038         }
9039         if (*xy_out == "rad") {
9040             angularUnit = UnitOfMeasure::RADIAN;
9041         } else if (*xy_out == "grad") {
9042             angularUnit = UnitOfMeasure::GRAD;
9043         }
9044     }
9045 
9046     std::vector<CoordinateSystemAxisNNPtr> axis = processAxisSwap(
9047         step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis);
9048     CoordinateSystemAxisNNPtr up = CoordinateSystemAxis::create(
9049         util::PropertyMap().set(IdentifiedObject::NAME_KEY,
9050                                 AxisName::Ellipsoidal_height),
9051         AxisAbbreviation::h, AxisDirection::UP,
9052         buildUnit(step, "vunits", "vto_meter"));
9053 
9054     return (!hasParamValue(step, "geoidgrids") &&
9055             (hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter")))
9056                ? EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1], up)
9057                : EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1]);
9058 }
9059 
9060 // ---------------------------------------------------------------------------
9061 
getNumericValue(const std::string & paramValue,bool * pHasError=nullptr)9062 static double getNumericValue(const std::string &paramValue,
9063                               bool *pHasError = nullptr) {
9064     try {
9065         double value = c_locale_stod(paramValue);
9066         if (pHasError)
9067             *pHasError = false;
9068         return value;
9069     } catch (const std::invalid_argument &) {
9070         if (pHasError)
9071             *pHasError = true;
9072         return 0.0;
9073     }
9074 }
9075 
9076 // ---------------------------------------------------------------------------
9077 namespace {
ignoreRetVal(T)9078 template <class T> inline void ignoreRetVal(T) {}
9079 }
9080 
buildGeographicCRS(int iStep,int iUnitConvert,int iAxisSwap,bool ignorePROJAxis)9081 GeographicCRSNNPtr PROJStringParser::Private::buildGeographicCRS(
9082     int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) {
9083     auto &step = steps_[iStep];
9084 
9085     const bool l_isGeographicStep = isGeographicStep(step.name);
9086     const auto &title = l_isGeographicStep ? title_ : emptyString;
9087 
9088     // units=m is often found in the wild.
9089     // No need to create a extension string for this
9090     ignoreRetVal(hasParamValue(step, "units"));
9091 
9092     auto datum = buildDatum(step, title);
9093 
9094     auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
9095                                    title.empty() ? "unknown" : title);
9096     auto cs =
9097         buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, ignorePROJAxis);
9098 
9099     if (l_isGeographicStep &&
9100         (hasUnusedParameters(step) ||
9101          getNumericValue(getParamValue(step, "lon_0")) != 0.0)) {
9102         props.set("EXTENSION_PROJ4", projString_);
9103     }
9104     props.set("IMPLICIT_CS", true);
9105 
9106     return GeographicCRS::create(props, datum, cs);
9107 }
9108 
9109 // ---------------------------------------------------------------------------
9110 
9111 GeodeticCRSNNPtr
buildGeocentricCRS(int iStep,int iUnitConvert)9112 PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) {
9113     auto &step = steps_[iStep];
9114 
9115     assert(isGeocentricStep(step.name));
9116     assert(iUnitConvert < 0 ||
9117            ci_equal(steps_[iUnitConvert].name, "unitconvert"));
9118 
9119     const auto &title = title_;
9120 
9121     auto datum = buildDatum(step, title);
9122 
9123     UnitOfMeasure unit = buildUnit(step, "units", "");
9124     if (iUnitConvert >= 0) {
9125         auto &stepUnitConvert = steps_[iUnitConvert];
9126         const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
9127         const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
9128         const std::string *z_in = &getParamValue(stepUnitConvert, "z_in");
9129         const std::string *z_out = &getParamValue(stepUnitConvert, "z_out");
9130         if (stepUnitConvert.inverted) {
9131             std::swap(xy_in, xy_out);
9132             std::swap(z_in, z_out);
9133         }
9134         if (xy_in->empty() || xy_out->empty() || *xy_in != "m" ||
9135             *z_in != "m" || *xy_out != *z_out) {
9136             throw ParsingException(
9137                 "unhandled values for xy_in, z_in, xy_out or z_out");
9138         }
9139 
9140         const LinearUnitDesc *unitsMatch = nullptr;
9141         try {
9142             double to_meter_value = c_locale_stod(*xy_out);
9143             unitsMatch = getLinearUnits(to_meter_value);
9144             if (unitsMatch == nullptr) {
9145                 unit = _buildUnit(to_meter_value);
9146             }
9147         } catch (const std::invalid_argument &) {
9148             unitsMatch = getLinearUnits(*xy_out);
9149             if (!unitsMatch) {
9150                 throw ParsingException(
9151                     "unhandled values for xy_in, z_in, xy_out or z_out");
9152             }
9153             unit = _buildUnit(unitsMatch);
9154         }
9155     }
9156 
9157     auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
9158                                    title.empty() ? "unknown" : title);
9159     auto cs = CartesianCS::createGeocentric(unit);
9160 
9161     if (hasUnusedParameters(step)) {
9162         props.set("EXTENSION_PROJ4", projString_);
9163     }
9164 
9165     return GeodeticCRS::create(props, datum, cs);
9166 }
9167 
9168 // ---------------------------------------------------------------------------
9169 
9170 CRSNNPtr
buildBoundOrCompoundCRSIfNeeded(int iStep,CRSNNPtr crs)9171 PROJStringParser::Private::buildBoundOrCompoundCRSIfNeeded(int iStep,
9172                                                            CRSNNPtr crs) {
9173     auto &step = steps_[iStep];
9174     const auto &nadgrids = getParamValue(step, "nadgrids");
9175     const auto &towgs84 = getParamValue(step, "towgs84");
9176     // nadgrids has the priority over towgs84
9177     if (!ignoreNadgrids_ && !nadgrids.empty()) {
9178         crs = BoundCRS::createFromNadgrids(crs, nadgrids);
9179     } else if (!towgs84.empty()) {
9180         std::vector<double> towgs84Values;
9181         const auto tokens = split(towgs84, ',');
9182         for (const auto &str : tokens) {
9183             try {
9184                 towgs84Values.push_back(c_locale_stod(str));
9185             } catch (const std::invalid_argument &) {
9186                 throw ParsingException("Non numerical value in towgs84 clause");
9187             }
9188         }
9189         crs = BoundCRS::createFromTOWGS84(crs, towgs84Values);
9190     }
9191 
9192     const auto &geoidgrids = getParamValue(step, "geoidgrids");
9193     if (!geoidgrids.empty()) {
9194         auto vdatum =
9195             VerticalReferenceFrame::create(createMapWithUnknownName());
9196 
9197         const UnitOfMeasure unit = buildUnit(step, "vunits", "vto_meter");
9198 
9199         auto vcrs =
9200             VerticalCRS::create(createMapWithUnknownName(), vdatum,
9201                                 VerticalCS::createGravityRelatedHeight(unit));
9202 
9203         auto transformation =
9204             Transformation::createGravityRelatedHeightToGeographic3D(
9205                 PropertyMap().set(IdentifiedObject::NAME_KEY,
9206                                   "unknown to WGS84 ellipsoidal height"),
9207                 VerticalCRS::create(createMapWithUnknownName(), vdatum,
9208                                     VerticalCS::createGravityRelatedHeight(
9209                                         common::UnitOfMeasure::METRE)),
9210                 GeographicCRS::EPSG_4979, nullptr, geoidgrids,
9211                 std::vector<PositionalAccuracyNNPtr>());
9212         auto boundvcrs =
9213             BoundCRS::create(vcrs, GeographicCRS::EPSG_4979, transformation);
9214 
9215         crs = CompoundCRS::create(createMapWithUnknownName(),
9216                                   std::vector<CRSNNPtr>{crs, boundvcrs});
9217     }
9218 
9219     return crs;
9220 }
9221 
9222 // ---------------------------------------------------------------------------
9223 
getAngularValue(const std::string & paramValue,bool * pHasError=nullptr)9224 static double getAngularValue(const std::string &paramValue,
9225                               bool *pHasError = nullptr) {
9226     char *endptr = nullptr;
9227     double value = dmstor(paramValue.c_str(), &endptr) * RAD_TO_DEG;
9228     if (value == HUGE_VAL || endptr != paramValue.c_str() + paramValue.size()) {
9229         if (pHasError)
9230             *pHasError = true;
9231         return 0.0;
9232     }
9233     if (pHasError)
9234         *pHasError = false;
9235     return value;
9236 }
9237 
9238 // ---------------------------------------------------------------------------
9239 
is_in_stringlist(const std::string & str,const char * stringlist)9240 static bool is_in_stringlist(const std::string &str, const char *stringlist) {
9241     if (str.empty())
9242         return false;
9243     const char *haystack = stringlist;
9244     while (true) {
9245         const char *res = strstr(haystack, str.c_str());
9246         if (res == nullptr)
9247             return false;
9248         if ((res == stringlist || res[-1] == ',') &&
9249             (res[str.size()] == ',' || res[str.size()] == '\0'))
9250             return true;
9251         haystack += str.size();
9252     }
9253 }
9254 
9255 // ---------------------------------------------------------------------------
9256 
buildProjectedCRS(int iStep,GeographicCRSNNPtr geogCRS,int iUnitConvert,int iAxisSwap)9257 CRSNNPtr PROJStringParser::Private::buildProjectedCRS(
9258     int iStep, GeographicCRSNNPtr geogCRS, int iUnitConvert, int iAxisSwap) {
9259     auto &step = steps_[iStep];
9260     const auto mappings = getMappingsFromPROJName(step.name);
9261     const MethodMapping *mapping = mappings.empty() ? nullptr : mappings[0];
9262 
9263     if (mappings.size() >= 2) {
9264         // To distinguish for example +ortho from +ortho +f=0
9265         for (const auto *mappingIter : mappings) {
9266             if (mappingIter->proj_name_aux != nullptr &&
9267                 strchr(mappingIter->proj_name_aux, '=') == nullptr &&
9268                 hasParamValue(step, mappingIter->proj_name_aux)) {
9269                 mapping = mappingIter;
9270                 break;
9271             } else if (mappingIter->proj_name_aux != nullptr &&
9272                        strchr(mappingIter->proj_name_aux, '=') != nullptr) {
9273                 const auto tokens = split(mappingIter->proj_name_aux, '=');
9274                 if (tokens.size() == 2 &&
9275                     getParamValue(step, tokens[0]) == tokens[1]) {
9276                     mapping = mappingIter;
9277                     break;
9278                 }
9279             }
9280         }
9281     }
9282 
9283     if (mapping) {
9284         mapping = selectSphericalOrEllipsoidal(mapping, geogCRS);
9285     }
9286 
9287     assert(isProjectedStep(step.name));
9288     assert(iUnitConvert < 0 ||
9289            ci_equal(steps_[iUnitConvert].name, "unitconvert"));
9290 
9291     const auto &title = title_;
9292 
9293     if (!buildPrimeMeridian(step)->longitude()._isEquivalentTo(
9294             geogCRS->primeMeridian()->longitude(),
9295             util::IComparable::Criterion::EQUIVALENT)) {
9296         throw ParsingException("inconsistent pm values between projectedCRS "
9297                                "and its base geographicalCRS");
9298     }
9299 
9300     auto axisType = AxisType::REGULAR;
9301     bool bWebMercator = false;
9302     std::string webMercatorName("WGS 84 / Pseudo-Mercator");
9303 
9304     if (step.name == "tmerc" &&
9305         ((getParamValue(step, "axis") == "wsu" && iAxisSwap < 0) ||
9306          (iAxisSwap > 0 &&
9307           getParamValue(steps_[iAxisSwap], "order") == "-1,-2"))) {
9308         mapping =
9309             getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED);
9310     } else if (step.name == "etmerc") {
9311         mapping = getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR);
9312     } else if (step.name == "lcc") {
9313         const auto &lat_0 = getParamValue(step, "lat_0");
9314         const auto &lat_1 = getParamValue(step, "lat_1");
9315         const auto &lat_2 = getParamValue(step, "lat_2");
9316         const auto &k = getParamValueK(step);
9317         if (lat_2.empty() && !lat_0.empty() && !lat_1.empty() &&
9318             (lat_0 == lat_1 ||
9319              // For some reason with gcc 5.3.1-14ubuntu2 32bit, the following
9320              // comparison returns false even if lat_0 == lat_1. Smells like
9321              // a compiler bug
9322              getAngularValue(lat_0) == getAngularValue(lat_1))) {
9323             mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
9324         } else if (!k.empty() && getNumericValue(k) != 1.0) {
9325             mapping = getMapping(
9326                 EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN);
9327         } else {
9328             mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
9329         }
9330     } else if (step.name == "aeqd" && hasParamValue(step, "guam")) {
9331         mapping = getMapping(EPSG_CODE_METHOD_GUAM_PROJECTION);
9332     } else if (step.name == "geos" && getParamValue(step, "sweep") == "x") {
9333         mapping =
9334             getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X);
9335     } else if (step.name == "geos") {
9336         mapping =
9337             getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y);
9338     } else if (step.name == "omerc") {
9339         if (hasParamValue(step, "no_rot")) {
9340             mapping = nullptr;
9341         } else if (hasParamValue(step, "no_uoff") ||
9342                    hasParamValue(step, "no_off")) {
9343             mapping =
9344                 getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A);
9345         } else if (hasParamValue(step, "lat_1") &&
9346                    hasParamValue(step, "lon_1") &&
9347                    hasParamValue(step, "lat_2") &&
9348                    hasParamValue(step, "lon_2")) {
9349             mapping = getMapping(
9350                 PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN);
9351         } else {
9352             mapping =
9353                 getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B);
9354         }
9355     } else if (step.name == "somerc") {
9356         mapping =
9357             getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B);
9358         if (!hasParamValue(step, "alpha") && !hasParamValue(step, "gamma") &&
9359             !hasParamValue(step, "lonc")) {
9360             step.paramValues.emplace_back(Step::KeyValue("alpha", "90"));
9361             step.paramValues.emplace_back(Step::KeyValue("gamma", "90"));
9362             step.paramValues.emplace_back(
9363                 Step::KeyValue("lonc", getParamValue(step, "lon_0")));
9364         }
9365     } else if (step.name == "krovak" &&
9366                ((iAxisSwap < 0 && getParamValue(step, "axis") == "swu" &&
9367                  !hasParamValue(step, "czech")) ||
9368                 (iAxisSwap > 0 &&
9369                  getParamValue(steps_[iAxisSwap], "order") == "-2,-1" &&
9370                  !hasParamValue(step, "czech")))) {
9371         mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
9372     } else if (step.name == "krovak" && iAxisSwap < 0 &&
9373                hasParamValue(step, "czech") && !hasParamValue(step, "axis")) {
9374         mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
9375     } else if (step.name == "merc") {
9376         if (hasParamValue(step, "a") && hasParamValue(step, "b") &&
9377             getParamValue(step, "a") == getParamValue(step, "b") &&
9378             (!hasParamValue(step, "lat_ts") ||
9379              getAngularValue(getParamValue(step, "lat_ts")) == 0.0) &&
9380             getNumericValue(getParamValueK(step)) == 1.0 &&
9381             getParamValue(step, "nadgrids") == "@null") {
9382             mapping = getMapping(
9383                 EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR);
9384             for (size_t i = 0; i < step.paramValues.size(); ++i) {
9385                 if (ci_equal(step.paramValues[i].key, "nadgrids")) {
9386                     ignoreNadgrids_ = true;
9387                     break;
9388                 }
9389             }
9390             if (getNumericValue(getParamValue(step, "a")) == 6378137 &&
9391                 getAngularValue(getParamValue(step, "lon_0")) == 0.0 &&
9392                 getAngularValue(getParamValue(step, "lat_0")) == 0.0 &&
9393                 getAngularValue(getParamValue(step, "x_0")) == 0.0 &&
9394                 getAngularValue(getParamValue(step, "y_0")) == 0.0) {
9395                 bWebMercator = true;
9396                 if (hasParamValue(step, "units") &&
9397                     getParamValue(step, "units") != "m") {
9398                     webMercatorName +=
9399                         " (unit " + getParamValue(step, "units") + ')';
9400                 }
9401             }
9402         } else if (hasParamValue(step, "lat_ts")) {
9403             mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
9404         } else {
9405             mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A);
9406         }
9407     } else if (step.name == "stere") {
9408         if (hasParamValue(step, "lat_0") &&
9409             std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) -
9410                       90.0) < 1e-10) {
9411             const double lat_0 = getAngularValue(getParamValue(step, "lat_0"));
9412             if (lat_0 > 0) {
9413                 axisType = AxisType::NORTH_POLE;
9414             } else {
9415                 axisType = AxisType::SOUTH_POLE;
9416             }
9417             const auto &lat_ts = getParamValue(step, "lat_ts");
9418             const auto &k = getParamValueK(step);
9419             if (!lat_ts.empty() &&
9420                 std::fabs(getAngularValue(lat_ts) - lat_0) > 1e-10 &&
9421                 !k.empty() && std::fabs(getNumericValue(k) - 1) > 1e-10) {
9422                 throw ParsingException("lat_ts != lat_0 and k != 1 not "
9423                                        "supported for Polar Stereographic");
9424             }
9425             if (!lat_ts.empty() &&
9426                 (k.empty() || std::fabs(getNumericValue(k) - 1) < 1e-10)) {
9427                 mapping =
9428                     getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B);
9429             } else {
9430                 mapping =
9431                     getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A);
9432             }
9433         } else {
9434             mapping = getMapping(PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC);
9435         }
9436     } else if (step.name == "laea") {
9437         if (hasParamValue(step, "lat_0") &&
9438             std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) -
9439                       90.0) < 1e-10) {
9440             const double lat_0 = getAngularValue(getParamValue(step, "lat_0"));
9441             if (lat_0 > 0) {
9442                 axisType = AxisType::NORTH_POLE;
9443             } else {
9444                 axisType = AxisType::SOUTH_POLE;
9445             }
9446         }
9447     }
9448 
9449     UnitOfMeasure unit = buildUnit(step, "units", "to_meter");
9450     if (iUnitConvert >= 0) {
9451         auto &stepUnitConvert = steps_[iUnitConvert];
9452         const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
9453         const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
9454         if (stepUnitConvert.inverted) {
9455             std::swap(xy_in, xy_out);
9456         }
9457         if (xy_in->empty() || xy_out->empty() || *xy_in != "m") {
9458             if (step.name != "ob_tran") {
9459                 throw ParsingException(
9460                     "unhandled values for xy_in and/or xy_out");
9461             }
9462         }
9463 
9464         const LinearUnitDesc *unitsMatch = nullptr;
9465         try {
9466             double to_meter_value = c_locale_stod(*xy_out);
9467             unitsMatch = getLinearUnits(to_meter_value);
9468             if (unitsMatch == nullptr) {
9469                 unit = _buildUnit(to_meter_value);
9470             }
9471         } catch (const std::invalid_argument &) {
9472             unitsMatch = getLinearUnits(*xy_out);
9473             if (!unitsMatch) {
9474                 if (step.name != "ob_tran") {
9475                     throw ParsingException(
9476                         "unhandled values for xy_in and/or xy_out");
9477                 }
9478             } else {
9479                 unit = _buildUnit(unitsMatch);
9480             }
9481         }
9482     }
9483 
9484     ConversionPtr conv;
9485 
9486     auto mapWithUnknownName = createMapWithUnknownName();
9487 
9488     if (step.name == "utm") {
9489         const int zone = std::atoi(getParamValue(step, "zone").c_str());
9490         const bool north = !hasParamValue(step, "south");
9491         conv =
9492             Conversion::createUTM(emptyPropertyMap, zone, north).as_nullable();
9493     } else if (mapping) {
9494 
9495         auto methodMap =
9496             PropertyMap().set(IdentifiedObject::NAME_KEY, mapping->wkt2_name);
9497         if (mapping->epsg_code) {
9498             methodMap.set(Identifier::CODESPACE_KEY, Identifier::EPSG)
9499                 .set(Identifier::CODE_KEY, mapping->epsg_code);
9500         }
9501         std::vector<OperationParameterNNPtr> parameters;
9502         std::vector<ParameterValueNNPtr> values;
9503         for (int i = 0; mapping->params[i] != nullptr; i++) {
9504             const auto *param = mapping->params[i];
9505             std::string proj_name(param->proj_name ? param->proj_name : "");
9506             const std::string *paramValue =
9507                 (proj_name == "k" || proj_name == "k_0")
9508                     ? &getParamValueK(step)
9509                     : !proj_name.empty() ? &getParamValue(step, proj_name)
9510                                          : &emptyString;
9511             double value = 0;
9512             if (!paramValue->empty()) {
9513                 bool hasError = false;
9514                 if (param->unit_type == UnitOfMeasure::Type::ANGULAR) {
9515                     value = getAngularValue(*paramValue, &hasError);
9516                 } else {
9517                     value = getNumericValue(*paramValue, &hasError);
9518                 }
9519                 if (hasError) {
9520                     throw ParsingException("invalid value for " + proj_name);
9521                 }
9522             }
9523             // For omerc, if gamma is missing, the default value is
9524             // alpha
9525             else if (step.name == "omerc" && proj_name == "gamma") {
9526                 paramValue = &getParamValue(step, "alpha");
9527                 if (!paramValue->empty()) {
9528                     value = getAngularValue(*paramValue);
9529                 }
9530             } else if (step.name == "krovak") {
9531                 // Keep it in sync with defaults of krovak.cpp
9532                 if (param->epsg_code ==
9533                     EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE) {
9534                     value = 49.5;
9535                 } else if (param->epsg_code ==
9536                            EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN) {
9537                     value = 24.833333333333333333;
9538                 } else if (param->epsg_code ==
9539                            EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS) {
9540                     value = 30.28813975277777776;
9541                 } else if (
9542                     param->epsg_code ==
9543                     EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL) {
9544                     value = 78.5;
9545                 } else if (
9546                     param->epsg_code ==
9547                     EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL) {
9548                     value = 0.9999;
9549                 }
9550             } else if (step.name == "cea" && proj_name == "lat_ts") {
9551                 paramValue = &getParamValueK(step);
9552                 if (!paramValue->empty()) {
9553                     bool hasError = false;
9554                     const double k = getNumericValue(*paramValue, &hasError);
9555                     if (hasError) {
9556                         throw ParsingException("invalid value for k/k_0");
9557                     }
9558                     if (k >= 0 && k <= 1) {
9559                         const double es =
9560                             geogCRS->ellipsoid()->squaredEccentricity();
9561                         if (es < 0 || es == 1) {
9562                             throw ParsingException("Invalid flattening");
9563                         }
9564                         value =
9565                             Angle(acos(k * sqrt((1 - es) / (1 - k * k * es))),
9566                                   UnitOfMeasure::RADIAN)
9567                                 .convertToUnit(UnitOfMeasure::DEGREE);
9568                     } else {
9569                         throw ParsingException("k/k_0 should be in [0,1]");
9570                     }
9571                 }
9572             } else if (param->unit_type == UnitOfMeasure::Type::SCALE) {
9573                 value = 1;
9574             }
9575 
9576             PropertyMap propertiesParameter;
9577             propertiesParameter.set(IdentifiedObject::NAME_KEY,
9578                                     param->wkt2_name);
9579             if (param->epsg_code) {
9580                 propertiesParameter.set(Identifier::CODE_KEY, param->epsg_code);
9581                 propertiesParameter.set(Identifier::CODESPACE_KEY,
9582                                         Identifier::EPSG);
9583             }
9584             parameters.push_back(
9585                 OperationParameter::create(propertiesParameter));
9586             // In PROJ convention, angular parameters are always in degree
9587             // and linear parameters always in metre.
9588             double valRounded =
9589                 param->unit_type == UnitOfMeasure::Type::LINEAR
9590                     ? Length(value, UnitOfMeasure::METRE).convertToUnit(unit)
9591                     : value;
9592             if (std::fabs(valRounded - std::round(valRounded)) < 1e-8) {
9593                 valRounded = std::round(valRounded);
9594             }
9595             values.push_back(ParameterValue::create(Measure(
9596                 valRounded,
9597                 param->unit_type == UnitOfMeasure::Type::ANGULAR
9598                     ? UnitOfMeasure::DEGREE
9599                     : param->unit_type == UnitOfMeasure::Type::LINEAR
9600                           ? unit
9601                           : param->unit_type == UnitOfMeasure::Type::SCALE
9602                                 ? UnitOfMeasure::SCALE_UNITY
9603                                 : UnitOfMeasure::NONE)));
9604         }
9605 
9606         if (step.name == "tmerc" && hasParamValue(step, "approx")) {
9607             methodMap.set("proj_method", "tmerc approx");
9608         } else if (step.name == "utm" && hasParamValue(step, "approx")) {
9609             methodMap.set("proj_method", "utm approx");
9610         }
9611 
9612         conv = Conversion::create(mapWithUnknownName, methodMap, parameters,
9613                                   values)
9614                    .as_nullable();
9615     } else {
9616         std::vector<OperationParameterNNPtr> parameters;
9617         std::vector<ParameterValueNNPtr> values;
9618         std::string methodName = "PROJ " + step.name;
9619         for (const auto &param : step.paramValues) {
9620             if (is_in_stringlist(param.key,
9621                                  "wktext,no_defs,datum,ellps,a,b,R,f,rf,"
9622                                  "towgs84,nadgrids,geoidgrids,"
9623                                  "units,to_meter,vunits,vto_meter,type")) {
9624                 continue;
9625             }
9626             if (param.value.empty()) {
9627                 methodName += " " + param.key;
9628             } else if (isalpha(param.value[0])) {
9629                 methodName += " " + param.key + "=" + param.value;
9630             } else {
9631                 parameters.push_back(OperationParameter::create(
9632                     PropertyMap().set(IdentifiedObject::NAME_KEY, param.key)));
9633                 bool hasError = false;
9634                 if (is_in_stringlist(param.key, "x_0,y_0,h,h_0")) {
9635                     double value = getNumericValue(param.value, &hasError);
9636                     values.push_back(ParameterValue::create(
9637                         Measure(value, UnitOfMeasure::METRE)));
9638                 } else if (is_in_stringlist(
9639                                param.key,
9640                                "k,k_0,"
9641                                "north_square,south_square," // rhealpix
9642                                "n,m,"                       // sinu
9643                                "q,"                         // urm5
9644                                "path,lsat,"                 // lsat
9645                                "W,M,"                       // hammer
9646                                "aperture,resolution,"       // isea
9647                                )) {
9648                     double value = getNumericValue(param.value, &hasError);
9649                     values.push_back(ParameterValue::create(
9650                         Measure(value, UnitOfMeasure::SCALE_UNITY)));
9651                 } else {
9652                     double value = getAngularValue(param.value, &hasError);
9653                     values.push_back(ParameterValue::create(
9654                         Measure(value, UnitOfMeasure::DEGREE)));
9655                 }
9656                 if (hasError) {
9657                     throw ParsingException("invalid value for " + param.key);
9658                 }
9659             }
9660         }
9661         conv = Conversion::create(
9662                    mapWithUnknownName,
9663                    PropertyMap().set(IdentifiedObject::NAME_KEY, methodName),
9664                    parameters, values)
9665                    .as_nullable();
9666 
9667         if (is_in_stringlist(methodName, "PROJ ob_tran o_proj=longlat,"
9668                                          "PROJ ob_tran o_proj=lonlat,"
9669                                          "PROJ ob_tran o_proj=latlon,"
9670                                          "PROJ ob_tran o_proj=latlong")) {
9671             return DerivedGeographicCRS::create(
9672                 PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
9673                 geogCRS, NN_NO_CHECK(conv),
9674                 buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, false));
9675         }
9676     }
9677 
9678     std::vector<CoordinateSystemAxisNNPtr> axis =
9679         processAxisSwap(step, unit, iAxisSwap, axisType, false);
9680 
9681     auto csGeogCRS = geogCRS->coordinateSystem();
9682     auto cs = csGeogCRS->axisList().size() == 2
9683                   ? CartesianCS::create(emptyPropertyMap, axis[0], axis[1])
9684                   : CartesianCS::create(emptyPropertyMap, axis[0], axis[1],
9685                                         csGeogCRS->axisList()[2]);
9686 
9687     auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
9688                                    title.empty() ? "unknown" : title);
9689     if (hasUnusedParameters(step)) {
9690         props.set("EXTENSION_PROJ4", projString_);
9691     }
9692 
9693     props.set("IMPLICIT_CS", true);
9694 
9695     CRSNNPtr crs =
9696         bWebMercator
9697             ? createPseudoMercator(
9698                   props.set(IdentifiedObject::NAME_KEY, webMercatorName), cs)
9699             : ProjectedCRS::create(props, geogCRS, NN_NO_CHECK(conv), cs);
9700 
9701     return crs;
9702 }
9703 
9704 //! @endcond
9705 
9706 // ---------------------------------------------------------------------------
9707 
9708 //! @cond Doxygen_Suppress
9709 static const metadata::ExtentPtr nullExtent{};
9710 
getExtent(const crs::CRS * crs)9711 static const metadata::ExtentPtr &getExtent(const crs::CRS *crs) {
9712     const auto &domains = crs->domains();
9713     if (!domains.empty()) {
9714         return domains[0]->domainOfValidity();
9715     }
9716     return nullExtent;
9717 }
9718 
9719 //! @endcond
9720 
9721 namespace {
9722 struct PJContextHolder {
9723     PJ_CONTEXT *ctx_;
9724     bool bFree_;
9725 
PJContextHolderio::__anon57fb82580b11::PJContextHolder9726     PJContextHolder(PJ_CONTEXT *ctx, bool bFree) : ctx_(ctx), bFree_(bFree) {}
~PJContextHolderio::__anon57fb82580b11::PJContextHolder9727     ~PJContextHolder() {
9728         if (bFree_)
9729             proj_context_destroy(ctx_);
9730     }
9731     PJContextHolder(const PJContextHolder &) = delete;
9732     PJContextHolder &operator=(const PJContextHolder &) = delete;
9733 };
9734 } // namespace
9735 
9736 // ---------------------------------------------------------------------------
9737 
9738 /** \brief Instantiate a sub-class of BaseObject from a PROJ string.
9739  *
9740  * The projString must contain +type=crs for the object to be detected as a
9741  * CRS instead of a CoordinateOperation.
9742  *
9743  * @throw ParsingException
9744  */
9745 BaseObjectNNPtr
createFromPROJString(const std::string & projString)9746 PROJStringParser::createFromPROJString(const std::string &projString) {
9747 
9748     // In some abnormal situations involving init=epsg:XXXX syntax, we could
9749     // have infinite loop
9750     if (d->ctx_ &&
9751         d->ctx_->projStringParserCreateFromPROJStringRecursionCounter == 2) {
9752         throw ParsingException(
9753             "Infinite recursion in PROJStringParser::createFromPROJString()");
9754     }
9755 
9756     d->steps_.clear();
9757     d->title_.clear();
9758     d->globalParamValues_.clear();
9759     d->projString_ = projString;
9760     PROJStringSyntaxParser(projString, d->steps_, d->globalParamValues_,
9761                            d->title_);
9762 
9763     if (d->steps_.empty()) {
9764         const auto &vunits = d->getGlobalParamValue("vunits");
9765         const auto &vto_meter = d->getGlobalParamValue("vto_meter");
9766         if (!vunits.empty() || !vto_meter.empty()) {
9767             Step fakeStep;
9768             if (!vunits.empty()) {
9769                 fakeStep.paramValues.emplace_back(
9770                     Step::KeyValue("vunits", vunits));
9771             }
9772             if (!vto_meter.empty()) {
9773                 fakeStep.paramValues.emplace_back(
9774                     Step::KeyValue("vto_meter", vto_meter));
9775             }
9776             auto vdatum =
9777                 VerticalReferenceFrame::create(createMapWithUnknownName());
9778             auto vcrs = VerticalCRS::create(
9779                 createMapWithUnknownName(), vdatum,
9780                 VerticalCS::createGravityRelatedHeight(
9781                     d->buildUnit(fakeStep, "vunits", "vto_meter")));
9782             return vcrs;
9783         }
9784     }
9785 
9786     const bool isGeocentricCRS =
9787         ((d->steps_.size() == 1 &&
9788           d->getParamValue(d->steps_[0], "type") == "crs") ||
9789          (d->steps_.size() == 2 && d->steps_[1].name == "unitconvert")) &&
9790         !d->steps_[0].inverted && isGeocentricStep(d->steps_[0].name);
9791 
9792     // +init=xxxx:yyyy syntax
9793     if (d->steps_.size() == 1 && d->steps_[0].isInit &&
9794         !d->steps_[0].inverted) {
9795 
9796         // Those used to come from a text init file
9797         // We only support them in compatibility mode
9798         const std::string &stepName = d->steps_[0].name;
9799         if (ci_starts_with(stepName, "epsg:") ||
9800             ci_starts_with(stepName, "IGNF:")) {
9801 
9802             /* We create a new context so as to avoid messing up with the */
9803             /* errorno of the main context, when trying to find the likely */
9804             /* missing epsg file */
9805             auto ctx = proj_context_create();
9806             if (!ctx) {
9807                 throw ParsingException("out of memory");
9808             }
9809             PJContextHolder contextHolder(ctx, true);
9810             if (d->ctx_) {
9811                 ctx->set_search_paths(d->ctx_->search_paths);
9812                 ctx->file_finder = d->ctx_->file_finder;
9813                 ctx->file_finder_legacy = d->ctx_->file_finder_legacy;
9814                 ctx->file_finder_user_data = d->ctx_->file_finder_user_data;
9815             }
9816 
9817             bool usePROJ4InitRules = d->usePROJ4InitRules_;
9818             if (!usePROJ4InitRules) {
9819                 usePROJ4InitRules =
9820                     proj_context_get_use_proj4_init_rules(ctx, FALSE) == TRUE;
9821             }
9822             if (!usePROJ4InitRules) {
9823                 throw ParsingException("init=epsg:/init=IGNF: syntax not "
9824                                        "supported in non-PROJ4 emulation mode");
9825             }
9826 
9827             char unused[256];
9828             std::string initname(stepName);
9829             initname.resize(initname.find(':'));
9830             int file_found =
9831                 pj_find_file(ctx, initname.c_str(), unused, sizeof(unused));
9832 
9833             if (!file_found) {
9834                 auto obj = createFromUserInput(stepName, d->dbContext_, true);
9835                 auto crs = dynamic_cast<CRS *>(obj.get());
9836 
9837                 bool hasSignificantParamValues = false;
9838                 for (const auto &kv : d->steps_[0].paramValues) {
9839                     if (!((kv.key == "type" && kv.value == "crs") ||
9840                           kv.key == "wktext" || kv.key == "no_defs")) {
9841                         hasSignificantParamValues = true;
9842                         break;
9843                     }
9844                 }
9845 
9846                 if (crs && !hasSignificantParamValues) {
9847                     PropertyMap properties;
9848                     properties.set(IdentifiedObject::NAME_KEY,
9849                                    d->title_.empty() ? crs->nameStr()
9850                                                      : d->title_);
9851                     const auto &extent = getExtent(crs);
9852                     if (extent) {
9853                         properties.set(
9854                             common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
9855                             NN_NO_CHECK(extent));
9856                     }
9857                     auto geogCRS = dynamic_cast<GeographicCRS *>(crs);
9858                     if (geogCRS) {
9859                         // Override with longitude latitude in degrees
9860                         return GeographicCRS::create(
9861                             properties, geogCRS->datum(),
9862                             geogCRS->datumEnsemble(),
9863                             EllipsoidalCS::createLongitudeLatitude(
9864                                 UnitOfMeasure::DEGREE));
9865                     }
9866                     auto projCRS = dynamic_cast<ProjectedCRS *>(crs);
9867                     if (projCRS) {
9868                         // Override with easting northing order
9869                         const auto conv = projCRS->derivingConversion();
9870                         if (conv->method()->getEPSGCode() !=
9871                             EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
9872                             return ProjectedCRS::create(
9873                                 properties, projCRS->baseCRS(), conv,
9874                                 CartesianCS::createEastingNorthing(
9875                                     projCRS->coordinateSystem()
9876                                         ->axisList()[0]
9877                                         ->unit()));
9878                         }
9879                     }
9880                     return obj;
9881                 }
9882                 auto projStringExportable =
9883                     dynamic_cast<IPROJStringExportable *>(crs);
9884                 if (projStringExportable) {
9885                     std::string expanded;
9886                     if (!d->title_.empty()) {
9887                         expanded = "title=";
9888                         expanded += d->title_;
9889                     }
9890                     for (const auto &pair : d->steps_[0].paramValues) {
9891                         if (!expanded.empty())
9892                             expanded += ' ';
9893                         expanded += '+';
9894                         expanded += pair.key;
9895                         if (!pair.value.empty()) {
9896                             expanded += '=';
9897                             expanded += pair.value;
9898                         }
9899                     }
9900                     expanded += ' ';
9901                     expanded += projStringExportable->exportToPROJString(
9902                         PROJStringFormatter::create().get());
9903                     return createFromPROJString(expanded);
9904                 }
9905             }
9906         }
9907 
9908         auto ctx = d->ctx_ ? d->ctx_ : proj_context_create();
9909         if (!ctx) {
9910             throw ParsingException("out of memory");
9911         }
9912         PJContextHolder contextHolder(ctx, ctx != d->ctx_);
9913 
9914         paralist *init = pj_mkparam(("init=" + d->steps_[0].name).c_str());
9915         if (!init) {
9916             throw ParsingException("out of memory");
9917         }
9918         ctx->projStringParserCreateFromPROJStringRecursionCounter++;
9919         paralist *list = pj_expand_init(ctx, init);
9920         ctx->projStringParserCreateFromPROJStringRecursionCounter--;
9921         if (!list) {
9922             pj_dealloc(init);
9923             throw ParsingException("cannot expand " + projString);
9924         }
9925         std::string expanded;
9926         if (!d->title_.empty()) {
9927             expanded = "title=" + d->title_;
9928         }
9929         bool first = true;
9930         bool has_init_term = false;
9931         for (auto t = list; t;) {
9932             if (!expanded.empty()) {
9933                 expanded += ' ';
9934             }
9935             if (first) {
9936                 // first parameter is the init= itself
9937                 first = false;
9938             } else if (starts_with(t->param, "init=")) {
9939                 has_init_term = true;
9940             } else {
9941                 expanded += t->param;
9942             }
9943 
9944             auto n = t->next;
9945             pj_dealloc(t);
9946             t = n;
9947         }
9948         for (const auto &pair : d->steps_[0].paramValues) {
9949             expanded += " +";
9950             expanded += pair.key;
9951             if (!pair.value.empty()) {
9952                 expanded += '=';
9953                 expanded += pair.value;
9954             }
9955         }
9956 
9957         if (!has_init_term) {
9958             return createFromPROJString(expanded);
9959         }
9960     }
9961 
9962     int iFirstGeogStep = -1;
9963     int iSecondGeogStep = -1;
9964     int iProjStep = -1;
9965     int iFirstUnitConvert = -1;
9966     int iSecondUnitConvert = -1;
9967     int iFirstAxisSwap = -1;
9968     int iSecondAxisSwap = -1;
9969     bool unexpectedStructure = d->steps_.empty();
9970     for (int i = 0; i < static_cast<int>(d->steps_.size()); i++) {
9971         const auto &stepName = d->steps_[i].name;
9972         if (isGeographicStep(stepName)) {
9973             if (iFirstGeogStep < 0) {
9974                 iFirstGeogStep = i;
9975             } else if (iSecondGeogStep < 0) {
9976                 iSecondGeogStep = i;
9977             } else {
9978                 unexpectedStructure = true;
9979                 break;
9980             }
9981         } else if (ci_equal(stepName, "unitconvert")) {
9982             if (iFirstUnitConvert < 0) {
9983                 iFirstUnitConvert = i;
9984             } else if (iSecondUnitConvert < 0) {
9985                 iSecondUnitConvert = i;
9986             } else {
9987                 unexpectedStructure = true;
9988                 break;
9989             }
9990         } else if (ci_equal(stepName, "axisswap")) {
9991             if (iFirstAxisSwap < 0) {
9992                 iFirstAxisSwap = i;
9993             } else if (iSecondAxisSwap < 0) {
9994                 iSecondAxisSwap = i;
9995             } else {
9996                 unexpectedStructure = true;
9997                 break;
9998             }
9999         } else if (isProjectedStep(stepName)) {
10000             if (iProjStep >= 0) {
10001                 unexpectedStructure = true;
10002                 break;
10003             }
10004             iProjStep = i;
10005         } else {
10006             unexpectedStructure = true;
10007             break;
10008         }
10009     }
10010 
10011     if (!d->steps_.empty()) {
10012         // CRS candidate
10013         if ((d->steps_.size() == 1 &&
10014              d->getParamValue(d->steps_[0], "type") != "crs") ||
10015             (d->steps_.size() > 1 && d->getGlobalParamValue("type") != "crs")) {
10016             unexpectedStructure = true;
10017         }
10018     }
10019 
10020     struct Logger {
10021         std::string msg{};
10022 
10023         // cppcheck-suppress functionStatic
10024         void setMessage(const char *msgIn) noexcept {
10025             try {
10026                 msg = msgIn;
10027             } catch (const std::exception &) {
10028             }
10029         }
10030 
10031         static void log(void *user_data, int level, const char *msg) {
10032             if (level == PJ_LOG_ERROR) {
10033                 static_cast<Logger *>(user_data)->setMessage(msg);
10034             }
10035         }
10036     };
10037 
10038     // If the structure is not recognized, then try to instantiate the
10039     // pipeline, and if successful, wrap it in a PROJBasedOperation
10040     Logger logger;
10041     bool valid;
10042 
10043     auto pj_context = d->ctx_ ? d->ctx_ : proj_context_create();
10044     if (!pj_context) {
10045         throw ParsingException("out of memory");
10046     }
10047 
10048     // Backup error logger and level, and install temporary handler
10049     auto old_logger = pj_context->logger;
10050     auto old_logger_app_data = pj_context->logger_app_data;
10051     auto log_level = proj_log_level(pj_context, PJ_LOG_ERROR);
10052     proj_log_func(pj_context, &logger, Logger::log);
10053 
10054     if (pj_context != d->ctx_) {
10055         proj_context_use_proj4_init_rules(pj_context, d->usePROJ4InitRules_);
10056     }
10057     pj_context->projStringParserCreateFromPROJStringRecursionCounter++;
10058     auto pj = pj_create_internal(
10059         pj_context, (projString.find("type=crs") != std::string::npos
10060                          ? projString + " +disable_grid_presence_check"
10061                          : projString)
10062                         .c_str());
10063     pj_context->projStringParserCreateFromPROJStringRecursionCounter--;
10064     valid = pj != nullptr;
10065 
10066     // Restore initial error logger and level
10067     proj_log_level(pj_context, log_level);
10068     pj_context->logger = old_logger;
10069     pj_context->logger_app_data = old_logger_app_data;
10070 
10071     // Remove parameters not understood by PROJ.
10072     if (valid && d->steps_.size() == 1) {
10073         std::vector<Step::KeyValue> newParamValues{};
10074         std::set<std::string> foundKeys;
10075         auto &step = d->steps_[0];
10076 
10077         for (auto &kv : step.paramValues) {
10078             bool recognizedByPROJ = false;
10079             if (foundKeys.find(kv.key) != foundKeys.end()) {
10080                 continue;
10081             }
10082             foundKeys.insert(kv.key);
10083             if (step.name == "krovak" && kv.key == "alpha") {
10084                 // We recognize it in our CRS parsing code
10085                 recognizedByPROJ = true;
10086             } else {
10087                 for (auto cur = pj->params; cur; cur = cur->next) {
10088                     const char *equal = strchr(cur->param, '=');
10089                     if (equal &&
10090                         static_cast<size_t>(equal - cur->param) ==
10091                             kv.key.size()) {
10092                         if (memcmp(cur->param, kv.key.c_str(), kv.key.size()) ==
10093                             0) {
10094                             recognizedByPROJ = (cur->used == 1);
10095                             break;
10096                         }
10097                     } else if (strcmp(cur->param, kv.key.c_str()) == 0) {
10098                         recognizedByPROJ = (cur->used == 1);
10099                         break;
10100                     }
10101                 }
10102             }
10103             if (recognizedByPROJ) {
10104                 newParamValues.emplace_back(kv);
10105             }
10106         }
10107         step.paramValues = newParamValues;
10108 
10109         d->projString_.clear();
10110         if (!step.name.empty()) {
10111             d->projString_ += step.isInit ? "+init=" : "+proj=";
10112             d->projString_ += step.name;
10113         }
10114         for (const auto &paramValue : step.paramValues) {
10115             if (!d->projString_.empty()) {
10116                 d->projString_ += ' ';
10117             }
10118             d->projString_ += '+';
10119             d->projString_ += paramValue.key;
10120             if (!paramValue.value.empty()) {
10121                 d->projString_ += '=';
10122                 d->projString_ +=
10123                     pj_double_quote_string_param_if_needed(paramValue.value);
10124             }
10125         }
10126     }
10127 
10128     proj_destroy(pj);
10129 
10130     if (!valid) {
10131         const int l_errno = proj_context_errno(pj_context);
10132         std::string prefix("Error " + toString(l_errno) + " (" +
10133                            proj_errno_string(l_errno) + ")");
10134         if (logger.msg.empty()) {
10135             logger.msg = prefix;
10136         } else {
10137             logger.msg = prefix + ": " + logger.msg;
10138         }
10139     }
10140 
10141     if (pj_context != d->ctx_) {
10142         proj_context_destroy(pj_context);
10143     }
10144 
10145     if (!valid) {
10146         throw ParsingException(logger.msg);
10147     }
10148 
10149     if (isGeocentricCRS) {
10150         // First run is dry run to mark all recognized/unrecognized tokens
10151         for (int iter = 0; iter < 2; iter++) {
10152             auto obj = d->buildBoundOrCompoundCRSIfNeeded(
10153                 0, d->buildGeocentricCRS(0, (d->steps_.size() == 2 &&
10154                                              d->steps_[1].name == "unitconvert")
10155                                                 ? 1
10156                                                 : -1));
10157             if (iter == 1) {
10158                 return nn_static_pointer_cast<BaseObject>(obj);
10159             }
10160         }
10161     }
10162 
10163     if (!unexpectedStructure) {
10164         if (iFirstGeogStep == 0 && !d->steps_[iFirstGeogStep].inverted &&
10165             iSecondGeogStep < 0 && iProjStep < 0 &&
10166             (iFirstUnitConvert < 0 || iSecondUnitConvert < 0) &&
10167             (iFirstAxisSwap < 0 || iSecondAxisSwap < 0)) {
10168             // First run is dry run to mark all recognized/unrecognized tokens
10169             for (int iter = 0; iter < 2; iter++) {
10170                 auto obj = d->buildBoundOrCompoundCRSIfNeeded(
10171                     0, d->buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert,
10172                                              iFirstAxisSwap, false));
10173                 if (iter == 1) {
10174                     return nn_static_pointer_cast<BaseObject>(obj);
10175                 }
10176             }
10177         }
10178         if (iProjStep >= 0 && !d->steps_[iProjStep].inverted &&
10179             (iFirstGeogStep < 0 || iFirstGeogStep + 1 == iProjStep) &&
10180             iSecondGeogStep < 0) {
10181             if (iFirstGeogStep < 0)
10182                 iFirstGeogStep = iProjStep;
10183             // First run is dry run to mark all recognized/unrecognized tokens
10184             for (int iter = 0; iter < 2; iter++) {
10185                 auto obj = d->buildBoundOrCompoundCRSIfNeeded(
10186                     iProjStep,
10187                     d->buildProjectedCRS(
10188                         iProjStep,
10189                         d->buildGeographicCRS(
10190                             iFirstGeogStep, iFirstUnitConvert < iFirstGeogStep
10191                                                 ? iFirstUnitConvert
10192                                                 : -1,
10193                             iFirstAxisSwap < iFirstGeogStep ? iFirstAxisSwap
10194                                                             : -1,
10195                             true),
10196                         iFirstUnitConvert < iFirstGeogStep ? iSecondUnitConvert
10197                                                            : iFirstUnitConvert,
10198                         iFirstAxisSwap < iFirstGeogStep ? iSecondAxisSwap
10199                                                         : iFirstAxisSwap));
10200                 if (iter == 1) {
10201                     return nn_static_pointer_cast<BaseObject>(obj);
10202                 }
10203             }
10204         }
10205     }
10206 
10207     auto props = PropertyMap();
10208     if (!d->title_.empty()) {
10209         props.set(IdentifiedObject::NAME_KEY, d->title_);
10210     }
10211     return operation::SingleOperation::createPROJBased(props, projString,
10212                                                        nullptr, nullptr, {});
10213 }
10214 
10215 // ---------------------------------------------------------------------------
10216 
10217 //! @cond Doxygen_Suppress
10218 struct JSONFormatter::Private {
10219     CPLJSonStreamingWriter writer_{nullptr, nullptr};
10220     DatabaseContextPtr dbContext_{};
10221 
10222     std::vector<bool> stackHasId_{false};
10223     std::vector<bool> outputIdStack_{true};
10224     bool allowIDInImmediateChild_ = false;
10225     bool omitTypeInImmediateChild_ = false;
10226     bool abridgedTransformation_ = false;
10227     std::string schema_ = PROJJSON_CURRENT_VERSION;
10228 
10229     std::string result_{};
10230 
10231     // cppcheck-suppress functionStatic
pushOutputIdio::JSONFormatter::Private10232     void pushOutputId(bool outputIdIn) { outputIdStack_.push_back(outputIdIn); }
10233 
10234     // cppcheck-suppress functionStatic
popOutputIdio::JSONFormatter::Private10235     void popOutputId() { outputIdStack_.pop_back(); }
10236 };
10237 //! @endcond
10238 
10239 // ---------------------------------------------------------------------------
10240 
10241 /** \brief Constructs a new formatter.
10242  *
10243  * A formatter can be used only once (its internal state is mutated)
10244  *
10245  * @return new formatter.
10246  */
create(DatabaseContextPtr dbContext)10247 JSONFormatterNNPtr JSONFormatter::create( // cppcheck-suppress passedByValue
10248     DatabaseContextPtr dbContext) {
10249     auto ret = NN_NO_CHECK(JSONFormatter::make_unique<JSONFormatter>());
10250     ret->d->dbContext_ = dbContext;
10251     return ret;
10252 }
10253 
10254 // ---------------------------------------------------------------------------
10255 
10256 /** \brief Whether to use multi line output or not. */
setMultiLine(bool multiLine)10257 JSONFormatter &JSONFormatter::setMultiLine(bool multiLine) noexcept {
10258     d->writer_.SetPrettyFormatting(multiLine);
10259     return *this;
10260 }
10261 
10262 // ---------------------------------------------------------------------------
10263 
10264 /** \brief Set number of spaces for each indentation level (defaults to 4).
10265  */
setIndentationWidth(int width)10266 JSONFormatter &JSONFormatter::setIndentationWidth(int width) noexcept {
10267     d->writer_.SetIndentationSize(width);
10268     return *this;
10269 }
10270 
10271 // ---------------------------------------------------------------------------
10272 
10273 /** \brief Set the value of the "$schema" key in the top level object.
10274  *
10275  * If set to empty string, it will not be written.
10276  */
setSchema(const std::string & schema)10277 JSONFormatter &JSONFormatter::setSchema(const std::string &schema) noexcept {
10278     d->schema_ = schema;
10279     return *this;
10280 }
10281 
10282 // ---------------------------------------------------------------------------
10283 
10284 //! @cond Doxygen_Suppress
10285 
JSONFormatter()10286 JSONFormatter::JSONFormatter() : d(internal::make_unique<Private>()) {}
10287 
10288 // ---------------------------------------------------------------------------
10289 
10290 JSONFormatter::~JSONFormatter() = default;
10291 
10292 // ---------------------------------------------------------------------------
10293 
writer() const10294 CPLJSonStreamingWriter *JSONFormatter::writer() const { return &(d->writer_); }
10295 
10296 // ---------------------------------------------------------------------------
10297 
outputId() const10298 bool JSONFormatter::outputId() const { return d->outputIdStack_.back(); }
10299 
10300 // ---------------------------------------------------------------------------
10301 
outputUsage() const10302 bool JSONFormatter::outputUsage() const {
10303     return outputId() && d->outputIdStack_.size() == 2;
10304 }
10305 
10306 // ---------------------------------------------------------------------------
10307 
setAllowIDInImmediateChild()10308 void JSONFormatter::setAllowIDInImmediateChild() {
10309     d->allowIDInImmediateChild_ = true;
10310 }
10311 
10312 // ---------------------------------------------------------------------------
10313 
setOmitTypeInImmediateChild()10314 void JSONFormatter::setOmitTypeInImmediateChild() {
10315     d->omitTypeInImmediateChild_ = true;
10316 }
10317 
10318 // ---------------------------------------------------------------------------
10319 
ObjectContext(JSONFormatter & formatter,const char * objectType,bool hasId)10320 JSONFormatter::ObjectContext::ObjectContext(JSONFormatter &formatter,
10321                                             const char *objectType, bool hasId)
10322     : m_formatter(formatter) {
10323     m_formatter.d->writer_.StartObj();
10324     if (m_formatter.d->outputIdStack_.size() == 1 &&
10325         !m_formatter.d->schema_.empty()) {
10326         m_formatter.d->writer_.AddObjKey("$schema");
10327         m_formatter.d->writer_.Add(m_formatter.d->schema_);
10328     }
10329     if (objectType && !m_formatter.d->omitTypeInImmediateChild_) {
10330         m_formatter.d->writer_.AddObjKey("type");
10331         m_formatter.d->writer_.Add(objectType);
10332     }
10333     m_formatter.d->omitTypeInImmediateChild_ = false;
10334     // All intermediate nodes shouldn't have ID if a parent has an ID
10335     // unless explicitly enabled.
10336     if (m_formatter.d->allowIDInImmediateChild_) {
10337         m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0]);
10338         m_formatter.d->allowIDInImmediateChild_ = false;
10339     } else {
10340         m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0] &&
10341                                     !m_formatter.d->stackHasId_.back());
10342     }
10343 
10344     m_formatter.d->stackHasId_.push_back(hasId ||
10345                                          m_formatter.d->stackHasId_.back());
10346 }
10347 
10348 // ---------------------------------------------------------------------------
10349 
~ObjectContext()10350 JSONFormatter::ObjectContext::~ObjectContext() {
10351     m_formatter.d->writer_.EndObj();
10352     m_formatter.d->stackHasId_.pop_back();
10353     m_formatter.d->popOutputId();
10354 }
10355 
10356 // ---------------------------------------------------------------------------
10357 
setAbridgedTransformation(bool outputIn)10358 void JSONFormatter::setAbridgedTransformation(bool outputIn) {
10359     d->abridgedTransformation_ = outputIn;
10360 }
10361 
10362 // ---------------------------------------------------------------------------
10363 
abridgedTransformation() const10364 bool JSONFormatter::abridgedTransformation() const {
10365     return d->abridgedTransformation_;
10366 }
10367 
10368 //! @endcond
10369 
10370 // ---------------------------------------------------------------------------
10371 
10372 /** \brief Return the serialized JSON.
10373  */
toString() const10374 const std::string &JSONFormatter::toString() const {
10375     return d->writer_.GetString();
10376 }
10377 
10378 // ---------------------------------------------------------------------------
10379 
10380 //! @cond Doxygen_Suppress
10381 IJSONExportable::~IJSONExportable() = default;
10382 
10383 // ---------------------------------------------------------------------------
10384 
exportToJSON(JSONFormatter * formatter) const10385 std::string IJSONExportable::exportToJSON(JSONFormatter *formatter) const {
10386     _exportToJSON(formatter);
10387     return formatter->toString();
10388 }
10389 
10390 //! @endcond
10391 
10392 } // namespace io
10393 NS_PROJ_END
10394