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> ¶ms) {
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 ¶mName,
1318 const UnitOfMeasure &defaultLinearUnit,
1319 const UnitOfMeasure &defaultAngularUnit);
1320
1321 void consumeParameters(const WKTNodeNNPtr &node, bool isAbridged,
1322 std::vector<OperationParameterNNPtr> ¶meters,
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 ¶mValue =
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 ¶mName, 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> ¶meters,
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 ¶mValue = 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 ¶mName =
3122 childNodeChildren[0]->GP()->value();
3123 unit = guessUnitForParameter(
3124 paramName, defaultLinearUnit, defaultAngularUnit);
3125 }
3126
3127 if (isAbridged) {
3128 const auto ¶mName = parameters.back()->nameStr();
3129 int paramEPSGCode = 0;
3130 const auto ¶mIds = 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 ¶mValue = 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 ¶mValue = 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 ¶m : 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 ¶m : 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 ¶m : 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 ¶m : 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 ¶mValue : 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 ¶mValue : 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 ¶mValue : 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 ¶mValue : 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 ¶mName) {
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 ¶mName, 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 ¶mName, 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 ¶mName,
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 ¶mName,
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> ¶ms) {
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 ¶m : 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 ¶mValue,
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 ¶mValue,
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 ¶m : 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 ¶mValue : 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