Coverage Report

Created: 2026-09-28 07:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/iso19111/io.cpp
Line
Count
Source
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 <limits>
39
#include <list>
40
#include <locale>
41
#include <map>
42
#include <set>
43
#include <sstream> // std::istringstream
44
#include <string>
45
#include <utility>
46
#include <vector>
47
48
#include "proj/common.hpp"
49
#include "proj/coordinateoperation.hpp"
50
#include "proj/coordinates.hpp"
51
#include "proj/coordinatesystem.hpp"
52
#include "proj/crs.hpp"
53
#include "proj/datum.hpp"
54
#include "proj/io.hpp"
55
#include "proj/metadata.hpp"
56
#include "proj/util.hpp"
57
58
#include "operation/coordinateoperation_internal.hpp"
59
#include "operation/esriparammappings.hpp"
60
#include "operation/oputils.hpp"
61
#include "operation/parammappings.hpp"
62
63
#include "proj/internal/coordinatesystem_internal.hpp"
64
#include "proj/internal/datum_internal.hpp"
65
#include "proj/internal/internal.hpp"
66
#include "proj/internal/io_internal.hpp"
67
68
#include "proj/internal/include_nlohmann_json.hpp"
69
70
#include "proj_constants.h"
71
72
#include "proj_json_streaming_writer.hpp"
73
#include "wkt1_parser.h"
74
#include "wkt2_parser.h"
75
76
// PROJ include order is sensitive
77
// clang-format off
78
#include "proj.h"
79
#include "proj_internal.h"
80
// clang-format on
81
82
using namespace NS_PROJ::common;
83
using namespace NS_PROJ::coordinates;
84
using namespace NS_PROJ::crs;
85
using namespace NS_PROJ::cs;
86
using namespace NS_PROJ::datum;
87
using namespace NS_PROJ::internal;
88
using namespace NS_PROJ::metadata;
89
using namespace NS_PROJ::operation;
90
using namespace NS_PROJ::util;
91
92
using json = nlohmann::json;
93
94
//! @cond Doxygen_Suppress
95
static const std::string emptyString{};
96
//! @endcond
97
98
#if 0
99
namespace dropbox{ namespace oxygen {
100
template<> nn<NS_PROJ::io::DatabaseContextPtr>::~nn() = default;
101
template<> nn<NS_PROJ::io::AuthorityFactoryPtr>::~nn() = default;
102
template<> nn<std::shared_ptr<NS_PROJ::io::IPROJStringExportable>>::~nn() = default;
103
template<> nn<std::unique_ptr<NS_PROJ::io::PROJStringFormatter, std::default_delete<NS_PROJ::io::PROJStringFormatter> > >::~nn() = default;
104
template<> nn<std::unique_ptr<NS_PROJ::io::WKTFormatter, std::default_delete<NS_PROJ::io::WKTFormatter> > >::~nn() = default;
105
template<> nn<std::unique_ptr<NS_PROJ::io::WKTNode, std::default_delete<NS_PROJ::io::WKTNode> > >::~nn() = default;
106
}}
107
#endif
108
109
NS_PROJ_START
110
namespace io {
111
112
//! @cond Doxygen_Suppress
113
const char *JSONFormatter::PROJJSON_v0_7 =
114
    "https://proj.org/schemas/v0.7/projjson.schema.json";
115
116
#define PROJJSON_DEFAULT_VERSION JSONFormatter::PROJJSON_v0_7
117
118
//! @endcond
119
120
// ---------------------------------------------------------------------------
121
122
//! @cond Doxygen_Suppress
123
13.2M
IWKTExportable::~IWKTExportable() = default;
124
125
// ---------------------------------------------------------------------------
126
127
0
std::string IWKTExportable::exportToWKT(WKTFormatter *formatter) const {
128
0
    _exportToWKT(formatter);
129
0
    return formatter->toString();
130
0
}
131
132
//! @endcond
133
134
// ---------------------------------------------------------------------------
135
136
//! @cond Doxygen_Suppress
137
struct WKTFormatter::Private {
138
    struct Params {
139
        WKTFormatter::Convention convention_ = WKTFormatter::Convention::WKT2;
140
        WKTFormatter::Version version_ = WKTFormatter::Version::WKT2;
141
        bool multiLine_ = true;
142
        bool strict_ = true;
143
        int indentWidth_ = 4;
144
        bool idOnTopLevelOnly_ = false;
145
        bool outputAxisOrder_ = false;
146
        bool primeMeridianOmittedIfGreenwich_ = false;
147
        bool ellipsoidUnitOmittedIfMetre_ = false;
148
        bool primeMeridianOrParameterUnitOmittedIfSameAsAxis_ = false;
149
        bool forceUNITKeyword_ = false;
150
        bool outputCSUnitOnlyOnceIfSame_ = false;
151
        bool primeMeridianInDegree_ = false;
152
        bool use2019Keywords_ = false;
153
        bool useESRIDialect_ = false;
154
        bool allowEllipsoidalHeightAsVerticalCRS_ = false;
155
        bool allowLINUNITNode_ = false;
156
        OutputAxisRule outputAxis_ = WKTFormatter::OutputAxisRule::YES;
157
    };
158
    Params params_{};
159
    DatabaseContextPtr dbContext_{};
160
161
    int indentLevel_ = 0;
162
    int level_ = 0;
163
    std::vector<bool> stackHasChild_{};
164
    std::vector<bool> stackHasId_{false};
165
    std::vector<bool> stackEmptyKeyword_{};
166
    std::vector<bool> stackDisableUsage_{};
167
    std::vector<bool> outputUnitStack_{true};
168
    std::vector<bool> outputIdStack_{true};
169
    std::vector<UnitOfMeasureNNPtr> axisLinearUnitStack_{
170
        util::nn_make_shared<UnitOfMeasure>(UnitOfMeasure::METRE)};
171
    std::vector<UnitOfMeasureNNPtr> axisAngularUnitStack_{
172
        util::nn_make_shared<UnitOfMeasure>(UnitOfMeasure::DEGREE)};
173
    bool abridgedTransformation_ = false;
174
    bool useDerivingConversion_ = false;
175
    std::vector<double> toWGS84Parameters_{};
176
    std::string hDatumExtension_{};
177
    std::string vDatumExtension_{};
178
    crs::GeographicCRSPtr geogCRSOfCompoundCRS_{};
179
    std::string result_{};
180
181
    // cppcheck-suppress functionStatic
182
    void addNewLine();
183
    void addIndentation();
184
    // cppcheck-suppress functionStatic
185
    void startNewChild();
186
};
187
//! @endcond
188
189
// ---------------------------------------------------------------------------
190
191
/** \brief Constructs a new formatter.
192
 *
193
 * A formatter can be used only once (its internal state is mutated)
194
 *
195
 * Its default behavior can be adjusted with the different setters.
196
 *
197
 * @param convention WKT flavor. Defaults to Convention::WKT2
198
 * @param dbContext Database context, to allow queries in it if needed.
199
 * This is used for example for WKT1_ESRI output to do name substitutions.
200
 *
201
 * @return new formatter.
202
 */
203
WKTFormatterNNPtr WKTFormatter::create(Convention convention,
204
                                       // cppcheck-suppress passedByValue
205
0
                                       DatabaseContextPtr dbContext) {
206
0
    auto ret = NN_NO_CHECK(WKTFormatter::make_unique<WKTFormatter>(convention));
207
0
    ret->d->dbContext_ = std::move(dbContext);
208
0
    return ret;
209
0
}
210
211
// ---------------------------------------------------------------------------
212
213
/** \brief Constructs a new formatter from another one.
214
 *
215
 * A formatter can be used only once (its internal state is mutated)
216
 *
217
 * Its default behavior can be adjusted with the different setters.
218
 *
219
 * @param other source formatter.
220
 * @return new formatter.
221
 */
222
0
WKTFormatterNNPtr WKTFormatter::create(const WKTFormatterNNPtr &other) {
223
0
    auto f = create(other->d->params_.convention_, other->d->dbContext_);
224
0
    f->d->params_ = other->d->params_;
225
0
    return f;
226
0
}
227
228
// ---------------------------------------------------------------------------
229
230
//! @cond Doxygen_Suppress
231
0
WKTFormatter::~WKTFormatter() = default;
232
//! @endcond
233
234
// ---------------------------------------------------------------------------
235
236
/** \brief Whether to use multi line output or not. */
237
0
WKTFormatter &WKTFormatter::setMultiLine(bool multiLine) noexcept {
238
0
    d->params_.multiLine_ = multiLine;
239
0
    return *this;
240
0
}
241
242
// ---------------------------------------------------------------------------
243
244
/** \brief Set number of spaces for each indentation level (defaults to 4).
245
 */
246
0
WKTFormatter &WKTFormatter::setIndentationWidth(int width) noexcept {
247
0
    d->params_.indentWidth_ = width;
248
0
    return *this;
249
0
}
250
251
// ---------------------------------------------------------------------------
252
253
/** \brief Set whether AXIS nodes should be output.
254
 */
255
WKTFormatter &
256
0
WKTFormatter::setOutputAxis(OutputAxisRule outputAxisIn) noexcept {
257
0
    d->params_.outputAxis_ = outputAxisIn;
258
0
    return *this;
259
0
}
260
261
// ---------------------------------------------------------------------------
262
263
/** \brief Set whether the formatter should operate on strict more or not.
264
 *
265
 * The default is strict mode, in which case a FormattingException can be
266
 * thrown.
267
 * In non-strict mode, a Geographic 3D CRS can be for example exported as
268
 * WKT1_GDAL with 3 axes, whereas this is normally not allowed.
269
 */
270
0
WKTFormatter &WKTFormatter::setStrict(bool strictIn) noexcept {
271
0
    d->params_.strict_ = strictIn;
272
0
    return *this;
273
0
}
274
275
// ---------------------------------------------------------------------------
276
277
/** \brief Returns whether the formatter is in strict mode. */
278
0
bool WKTFormatter::isStrict() const noexcept { return d->params_.strict_; }
279
280
// ---------------------------------------------------------------------------
281
282
/** \brief Set whether the formatter should export, in WKT1, a Geographic or
283
 * Projected 3D CRS as a compound CRS whose vertical part represents an
284
 * ellipsoidal height.
285
 */
286
WKTFormatter &
287
0
WKTFormatter::setAllowEllipsoidalHeightAsVerticalCRS(bool allow) noexcept {
288
0
    d->params_.allowEllipsoidalHeightAsVerticalCRS_ = allow;
289
0
    return *this;
290
0
}
291
292
// ---------------------------------------------------------------------------
293
294
/** \brief Return whether the formatter should export, in WKT1, a Geographic or
295
 * Projected 3D CRS as a compound CRS whose vertical part represents an
296
 * ellipsoidal height.
297
 */
298
0
bool WKTFormatter::isAllowedEllipsoidalHeightAsVerticalCRS() const noexcept {
299
0
    return d->params_.allowEllipsoidalHeightAsVerticalCRS_;
300
0
}
301
302
// ---------------------------------------------------------------------------
303
304
/** \brief Set whether the formatter should export, in WKT1_ESRI, a Geographic
305
 * 3D CRS with the relatively new (ArcGIS Pro >= 2.7) LINUNIT node.
306
 * Defaults to true.
307
 * @since PROJ 9.1
308
 */
309
0
WKTFormatter &WKTFormatter::setAllowLINUNITNode(bool allow) noexcept {
310
0
    d->params_.allowLINUNITNode_ = allow;
311
0
    return *this;
312
0
}
313
314
// ---------------------------------------------------------------------------
315
316
/** \brief Return whether the formatter should export, in WKT1_ESRI, a
317
 * Geographic 3D CRS with the relatively new (ArcGIS Pro >= 2.7) LINUNIT node.
318
 * Defaults to true.
319
 * @since PROJ 9.1
320
 */
321
0
bool WKTFormatter::isAllowedLINUNITNode() const noexcept {
322
0
    return d->params_.allowLINUNITNode_;
323
0
}
324
325
// ---------------------------------------------------------------------------
326
327
/** Returns the WKT string from the formatter. */
328
0
const std::string &WKTFormatter::toString() const {
329
0
    if (d->indentLevel_ > 0 || d->level_ > 0) {
330
        // For intermediary nodes, the formatter is in a inconsistent
331
        // state.
332
0
        throw FormattingException("toString() called on intermediate nodes");
333
0
    }
334
0
    if (d->axisLinearUnitStack_.size() != 1)
335
0
        throw FormattingException(
336
0
            "Unbalanced pushAxisLinearUnit() / popAxisLinearUnit()");
337
0
    if (d->axisAngularUnitStack_.size() != 1)
338
0
        throw FormattingException(
339
0
            "Unbalanced pushAxisAngularUnit() / popAxisAngularUnit()");
340
0
    if (d->outputIdStack_.size() != 1)
341
0
        throw FormattingException("Unbalanced pushOutputId() / popOutputId()");
342
0
    if (d->outputUnitStack_.size() != 1)
343
0
        throw FormattingException(
344
0
            "Unbalanced pushOutputUnit() / popOutputUnit()");
345
0
    if (d->stackHasId_.size() != 1)
346
0
        throw FormattingException("Unbalanced pushHasId() / popHasId()");
347
0
    if (!d->stackDisableUsage_.empty())
348
0
        throw FormattingException(
349
0
            "Unbalanced pushDisableUsage() / popDisableUsage()");
350
351
0
    return d->result_;
352
0
}
353
354
//! @cond Doxygen_Suppress
355
356
// ---------------------------------------------------------------------------
357
358
WKTFormatter::WKTFormatter(Convention convention)
359
0
    : d(std::make_unique<Private>()) {
360
0
    d->params_.convention_ = convention;
361
0
    switch (convention) {
362
0
    case Convention::WKT2_2019:
363
0
        d->params_.use2019Keywords_ = true;
364
0
        PROJ_FALLTHROUGH;
365
0
    case Convention::WKT2:
366
0
        d->params_.version_ = WKTFormatter::Version::WKT2;
367
0
        d->params_.outputAxisOrder_ = true;
368
0
        break;
369
370
0
    case Convention::WKT2_2019_SIMPLIFIED:
371
0
        d->params_.use2019Keywords_ = true;
372
0
        PROJ_FALLTHROUGH;
373
0
    case Convention::WKT2_SIMPLIFIED:
374
0
        d->params_.version_ = WKTFormatter::Version::WKT2;
375
0
        d->params_.idOnTopLevelOnly_ = true;
376
0
        d->params_.outputAxisOrder_ = false;
377
0
        d->params_.primeMeridianOmittedIfGreenwich_ = true;
378
0
        d->params_.ellipsoidUnitOmittedIfMetre_ = true;
379
0
        d->params_.primeMeridianOrParameterUnitOmittedIfSameAsAxis_ = true;
380
0
        d->params_.forceUNITKeyword_ = true;
381
0
        d->params_.outputCSUnitOnlyOnceIfSame_ = true;
382
0
        break;
383
384
0
    case Convention::WKT1_GDAL:
385
0
        d->params_.version_ = WKTFormatter::Version::WKT1;
386
0
        d->params_.outputAxisOrder_ = false;
387
0
        d->params_.forceUNITKeyword_ = true;
388
0
        d->params_.primeMeridianInDegree_ = true;
389
0
        d->params_.outputAxis_ =
390
0
            WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE;
391
0
        break;
392
393
0
    case Convention::WKT1_ESRI:
394
0
        d->params_.version_ = WKTFormatter::Version::WKT1;
395
0
        d->params_.outputAxisOrder_ = false;
396
0
        d->params_.forceUNITKeyword_ = true;
397
0
        d->params_.primeMeridianInDegree_ = true;
398
0
        d->params_.useESRIDialect_ = true;
399
0
        d->params_.multiLine_ = false;
400
0
        d->params_.outputAxis_ = WKTFormatter::OutputAxisRule::NO;
401
0
        d->params_.allowLINUNITNode_ = true;
402
0
        break;
403
404
0
    default:
405
0
        assert(false);
406
0
        break;
407
0
    }
408
0
}
409
410
// ---------------------------------------------------------------------------
411
412
0
WKTFormatter &WKTFormatter::setOutputId(bool outputIdIn) {
413
0
    if (d->indentLevel_ != 0) {
414
0
        throw Exception(
415
0
            "setOutputId() shall only be called when the stack state is empty");
416
0
    }
417
0
    d->outputIdStack_[0] = outputIdIn;
418
0
    return *this;
419
0
}
420
421
// ---------------------------------------------------------------------------
422
423
0
void WKTFormatter::Private::addNewLine() { result_ += '\n'; }
424
425
// ---------------------------------------------------------------------------
426
427
0
void WKTFormatter::Private::addIndentation() {
428
0
    result_ += std::string(
429
0
        static_cast<size_t>(indentLevel_) * params_.indentWidth_, ' ');
430
0
}
431
432
// ---------------------------------------------------------------------------
433
434
0
void WKTFormatter::enter() {
435
0
    if (d->indentLevel_ == 0 && d->level_ == 0) {
436
0
        d->stackHasChild_.push_back(false);
437
0
    }
438
0
    ++d->level_;
439
0
}
440
441
// ---------------------------------------------------------------------------
442
443
0
void WKTFormatter::leave() {
444
0
    assert(d->level_ > 0);
445
0
    --d->level_;
446
0
    if (d->indentLevel_ == 0 && d->level_ == 0) {
447
0
        d->stackHasChild_.pop_back();
448
0
    }
449
0
}
450
451
// ---------------------------------------------------------------------------
452
453
0
bool WKTFormatter::isAtTopLevel() const {
454
0
    return d->level_ == 0 && d->indentLevel_ == 0;
455
0
}
456
457
// ---------------------------------------------------------------------------
458
459
0
void WKTFormatter::startNode(const std::string &keyword, bool hasId) {
460
0
    if (!d->stackHasChild_.empty()) {
461
0
        d->startNewChild();
462
0
    } else if (!d->result_.empty()) {
463
0
        d->result_ += ',';
464
0
        if (d->params_.multiLine_ && !keyword.empty()) {
465
0
            d->addNewLine();
466
0
        }
467
0
    }
468
469
0
    if (d->params_.multiLine_) {
470
0
        if ((d->indentLevel_ || d->level_) && !keyword.empty()) {
471
0
            if (!d->result_.empty()) {
472
0
                d->addNewLine();
473
0
            }
474
0
            d->addIndentation();
475
0
        }
476
0
    }
477
478
0
    if (!keyword.empty()) {
479
0
        d->result_ += keyword;
480
0
        d->result_ += '[';
481
0
    }
482
0
    d->indentLevel_++;
483
0
    d->stackHasChild_.push_back(false);
484
0
    d->stackEmptyKeyword_.push_back(keyword.empty());
485
486
    // Starting from a node that has a ID, we should emit ID nodes for :
487
    // - this node
488
    // - and for METHOD&PARAMETER nodes in WKT2, unless idOnTopLevelOnly_ is
489
    // set.
490
    // For WKT2, all other intermediate nodes shouldn't have ID ("not
491
    // recommended")
492
0
    if (!d->params_.idOnTopLevelOnly_ && d->indentLevel_ >= 2 &&
493
0
        d->params_.version_ == WKTFormatter::Version::WKT2 &&
494
0
        (keyword == WKTConstants::METHOD ||
495
0
         keyword == WKTConstants::PARAMETER)) {
496
0
        pushOutputId(d->outputIdStack_[0]);
497
0
    } else if (d->indentLevel_ >= 2 &&
498
0
               d->params_.version_ == WKTFormatter::Version::WKT2) {
499
0
        pushOutputId(d->outputIdStack_[0] && !d->stackHasId_.back());
500
0
    } else {
501
0
        pushOutputId(outputId());
502
0
    }
503
504
0
    d->stackHasId_.push_back(hasId || d->stackHasId_.back());
505
0
}
506
507
// ---------------------------------------------------------------------------
508
509
0
void WKTFormatter::endNode() {
510
0
    assert(d->indentLevel_ > 0);
511
0
    d->stackHasId_.pop_back();
512
0
    popOutputId();
513
0
    d->indentLevel_--;
514
0
    bool emptyKeyword = d->stackEmptyKeyword_.back();
515
0
    d->stackEmptyKeyword_.pop_back();
516
0
    d->stackHasChild_.pop_back();
517
0
    if (!emptyKeyword)
518
0
        d->result_ += ']';
519
0
}
520
521
// ---------------------------------------------------------------------------
522
523
0
WKTFormatter &WKTFormatter::simulCurNodeHasId() {
524
0
    d->stackHasId_.back() = true;
525
0
    return *this;
526
0
}
527
528
// ---------------------------------------------------------------------------
529
530
0
void WKTFormatter::Private::startNewChild() {
531
0
    assert(!stackHasChild_.empty());
532
0
    if (stackHasChild_.back()) {
533
0
        result_ += ',';
534
0
    }
535
0
    stackHasChild_.back() = true;
536
0
}
537
538
// ---------------------------------------------------------------------------
539
540
0
void WKTFormatter::addQuotedString(const char *str) {
541
0
    addQuotedString(std::string(str));
542
0
}
543
544
0
void WKTFormatter::addQuotedString(const std::string &str) {
545
0
    d->startNewChild();
546
0
    d->result_ += '"';
547
0
    d->result_ += replaceAll(str, "\"", "\"\"");
548
0
    d->result_ += '"';
549
0
}
550
551
// ---------------------------------------------------------------------------
552
553
0
void WKTFormatter::add(const std::string &str) {
554
0
    d->startNewChild();
555
0
    d->result_ += str;
556
0
}
557
558
// ---------------------------------------------------------------------------
559
560
0
void WKTFormatter::add(int number) {
561
0
    d->startNewChild();
562
0
    d->result_ += internal::toString(number);
563
0
}
564
565
// ---------------------------------------------------------------------------
566
567
#ifdef __MINGW32__
568
static std::string normalizeExponent(const std::string &in) {
569
    // mingw will output 1e-0xy instead of 1e-xy. Fix that
570
    auto pos = in.find("e-0");
571
    if (pos == std::string::npos) {
572
        return in;
573
    }
574
    if (pos + 4 < in.size() && isdigit(in[pos + 3]) && isdigit(in[pos + 4])) {
575
        return in.substr(0, pos + 2) + in.substr(pos + 3);
576
    }
577
    return in;
578
}
579
#else
580
935k
static inline std::string normalizeExponent(const std::string &in) {
581
935k
    return in;
582
935k
}
583
#endif
584
585
935k
static inline std::string normalizeSerializedString(const std::string &in) {
586
935k
    auto ret(normalizeExponent(in));
587
935k
    return ret;
588
935k
}
589
590
// ---------------------------------------------------------------------------
591
592
0
void WKTFormatter::add(double number, int precision) {
593
0
    d->startNewChild();
594
0
    if (number == 0.0) {
595
0
        if (d->params_.useESRIDialect_) {
596
0
            d->result_ += "0.0";
597
0
        } else {
598
0
            d->result_ += '0';
599
0
        }
600
0
    } else {
601
0
        std::string val(
602
0
            normalizeSerializedString(internal::toString(number, precision)));
603
0
        d->result_ += replaceAll(val, "e", "E");
604
0
        if (d->params_.useESRIDialect_ && val.find('.') == std::string::npos) {
605
0
            d->result_ += ".0";
606
0
        }
607
0
    }
608
0
}
609
610
// ---------------------------------------------------------------------------
611
612
0
void WKTFormatter::pushOutputUnit(bool outputUnitIn) {
613
0
    d->outputUnitStack_.push_back(outputUnitIn);
614
0
}
615
616
// ---------------------------------------------------------------------------
617
618
0
void WKTFormatter::popOutputUnit() { d->outputUnitStack_.pop_back(); }
619
620
// ---------------------------------------------------------------------------
621
622
0
bool WKTFormatter::outputUnit() const { return d->outputUnitStack_.back(); }
623
624
// ---------------------------------------------------------------------------
625
626
0
void WKTFormatter::pushOutputId(bool outputIdIn) {
627
0
    d->outputIdStack_.push_back(outputIdIn);
628
0
}
629
630
// ---------------------------------------------------------------------------
631
632
0
void WKTFormatter::popOutputId() { d->outputIdStack_.pop_back(); }
633
634
// ---------------------------------------------------------------------------
635
636
0
bool WKTFormatter::outputId() const {
637
0
    return !d->params_.useESRIDialect_ && d->outputIdStack_.back();
638
0
}
639
640
// ---------------------------------------------------------------------------
641
642
0
void WKTFormatter::pushHasId(bool hasId) { d->stackHasId_.push_back(hasId); }
643
644
// ---------------------------------------------------------------------------
645
646
0
void WKTFormatter::popHasId() { d->stackHasId_.pop_back(); }
647
648
// ---------------------------------------------------------------------------
649
650
0
void WKTFormatter::pushDisableUsage() { d->stackDisableUsage_.push_back(true); }
651
652
// ---------------------------------------------------------------------------
653
654
0
void WKTFormatter::popDisableUsage() { d->stackDisableUsage_.pop_back(); }
655
656
// ---------------------------------------------------------------------------
657
658
0
bool WKTFormatter::outputUsage() const {
659
0
    return outputId() && d->stackDisableUsage_.empty();
660
0
}
661
662
// ---------------------------------------------------------------------------
663
664
0
void WKTFormatter::pushAxisLinearUnit(const UnitOfMeasureNNPtr &unit) {
665
0
    d->axisLinearUnitStack_.push_back(unit);
666
0
}
667
// ---------------------------------------------------------------------------
668
669
0
void WKTFormatter::popAxisLinearUnit() { d->axisLinearUnitStack_.pop_back(); }
670
671
// ---------------------------------------------------------------------------
672
673
0
const UnitOfMeasureNNPtr &WKTFormatter::axisLinearUnit() const {
674
0
    return d->axisLinearUnitStack_.back();
675
0
}
676
677
// ---------------------------------------------------------------------------
678
679
0
void WKTFormatter::pushAxisAngularUnit(const UnitOfMeasureNNPtr &unit) {
680
0
    d->axisAngularUnitStack_.push_back(unit);
681
0
}
682
// ---------------------------------------------------------------------------
683
684
0
void WKTFormatter::popAxisAngularUnit() { d->axisAngularUnitStack_.pop_back(); }
685
686
// ---------------------------------------------------------------------------
687
688
0
const UnitOfMeasureNNPtr &WKTFormatter::axisAngularUnit() const {
689
0
    return d->axisAngularUnitStack_.back();
690
0
}
691
692
// ---------------------------------------------------------------------------
693
694
0
WKTFormatter::OutputAxisRule WKTFormatter::outputAxis() const {
695
0
    return d->params_.outputAxis_;
696
0
}
697
698
// ---------------------------------------------------------------------------
699
700
0
bool WKTFormatter::outputAxisOrder() const {
701
0
    return d->params_.outputAxisOrder_;
702
0
}
703
704
// ---------------------------------------------------------------------------
705
706
0
bool WKTFormatter::primeMeridianOmittedIfGreenwich() const {
707
0
    return d->params_.primeMeridianOmittedIfGreenwich_;
708
0
}
709
710
// ---------------------------------------------------------------------------
711
712
0
bool WKTFormatter::ellipsoidUnitOmittedIfMetre() const {
713
0
    return d->params_.ellipsoidUnitOmittedIfMetre_;
714
0
}
715
716
// ---------------------------------------------------------------------------
717
718
0
bool WKTFormatter::primeMeridianOrParameterUnitOmittedIfSameAsAxis() const {
719
0
    return d->params_.primeMeridianOrParameterUnitOmittedIfSameAsAxis_;
720
0
}
721
722
// ---------------------------------------------------------------------------
723
724
0
bool WKTFormatter::outputCSUnitOnlyOnceIfSame() const {
725
0
    return d->params_.outputCSUnitOnlyOnceIfSame_;
726
0
}
727
728
// ---------------------------------------------------------------------------
729
730
0
bool WKTFormatter::forceUNITKeyword() const {
731
0
    return d->params_.forceUNITKeyword_;
732
0
}
733
734
// ---------------------------------------------------------------------------
735
736
0
bool WKTFormatter::primeMeridianInDegree() const {
737
0
    return d->params_.primeMeridianInDegree_;
738
0
}
739
740
// ---------------------------------------------------------------------------
741
742
0
bool WKTFormatter::idOnTopLevelOnly() const {
743
0
    return d->params_.idOnTopLevelOnly_;
744
0
}
745
746
// ---------------------------------------------------------------------------
747
748
0
bool WKTFormatter::topLevelHasId() const {
749
0
    return d->stackHasId_.size() >= 2 && d->stackHasId_[1];
750
0
}
751
752
// ---------------------------------------------------------------------------
753
754
0
WKTFormatter::Version WKTFormatter::version() const {
755
0
    return d->params_.version_;
756
0
}
757
758
// ---------------------------------------------------------------------------
759
760
0
bool WKTFormatter::use2019Keywords() const {
761
0
    return d->params_.use2019Keywords_;
762
0
}
763
764
// ---------------------------------------------------------------------------
765
766
0
bool WKTFormatter::useESRIDialect() const { return d->params_.useESRIDialect_; }
767
768
// ---------------------------------------------------------------------------
769
770
0
const DatabaseContextPtr &WKTFormatter::databaseContext() const {
771
0
    return d->dbContext_;
772
0
}
773
774
// ---------------------------------------------------------------------------
775
776
0
void WKTFormatter::setAbridgedTransformation(bool outputIn) {
777
0
    d->abridgedTransformation_ = outputIn;
778
0
}
779
780
// ---------------------------------------------------------------------------
781
782
0
bool WKTFormatter::abridgedTransformation() const {
783
0
    return d->abridgedTransformation_;
784
0
}
785
786
// ---------------------------------------------------------------------------
787
788
0
void WKTFormatter::setUseDerivingConversion(bool useDerivingConversionIn) {
789
0
    d->useDerivingConversion_ = useDerivingConversionIn;
790
0
}
791
792
// ---------------------------------------------------------------------------
793
794
0
bool WKTFormatter::useDerivingConversion() const {
795
0
    return d->useDerivingConversion_;
796
0
}
797
798
// ---------------------------------------------------------------------------
799
800
0
void WKTFormatter::setTOWGS84Parameters(const std::vector<double> &params) {
801
0
    d->toWGS84Parameters_ = params;
802
0
}
803
804
// ---------------------------------------------------------------------------
805
806
0
const std::vector<double> &WKTFormatter::getTOWGS84Parameters() const {
807
0
    return d->toWGS84Parameters_;
808
0
}
809
810
// ---------------------------------------------------------------------------
811
812
0
void WKTFormatter::setVDatumExtension(const std::string &filename) {
813
0
    d->vDatumExtension_ = filename;
814
0
}
815
816
// ---------------------------------------------------------------------------
817
818
0
const std::string &WKTFormatter::getVDatumExtension() const {
819
0
    return d->vDatumExtension_;
820
0
}
821
822
// ---------------------------------------------------------------------------
823
824
0
void WKTFormatter::setHDatumExtension(const std::string &filename) {
825
0
    d->hDatumExtension_ = filename;
826
0
}
827
828
// ---------------------------------------------------------------------------
829
830
0
const std::string &WKTFormatter::getHDatumExtension() const {
831
0
    return d->hDatumExtension_;
832
0
}
833
834
// ---------------------------------------------------------------------------
835
836
0
void WKTFormatter::setGeogCRSOfCompoundCRS(const crs::GeographicCRSPtr &crs) {
837
0
    d->geogCRSOfCompoundCRS_ = crs;
838
0
}
839
840
// ---------------------------------------------------------------------------
841
842
0
const crs::GeographicCRSPtr &WKTFormatter::getGeogCRSOfCompoundCRS() const {
843
0
    return d->geogCRSOfCompoundCRS_;
844
0
}
845
846
// ---------------------------------------------------------------------------
847
848
0
std::string WKTFormatter::morphNameToESRI(const std::string &name) {
849
850
0
    for (const auto *suffix : {"(m)", "(ftUS)", "(E-N)", "(N-E)"}) {
851
0
        if (ends_with(name, suffix)) {
852
0
            return morphNameToESRI(
853
0
                       name.substr(0, name.size() - strlen(suffix))) +
854
0
                   suffix;
855
0
        }
856
0
    }
857
858
0
    std::string ret;
859
0
    bool insertUnderscore = false;
860
    // Replace any special character by underscore, except at the beginning
861
    // and of the name where those characters are removed.
862
0
    for (char ch : name) {
863
0
        if (ch == '+' || ch == '-' || (ch >= '0' && ch <= '9') ||
864
0
            (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
865
0
            if (insertUnderscore && !ret.empty()) {
866
0
                ret += '_';
867
0
            }
868
0
            ret += ch;
869
0
            insertUnderscore = false;
870
0
        } else {
871
0
            insertUnderscore = true;
872
0
        }
873
0
    }
874
0
    return ret;
875
0
}
876
877
// ---------------------------------------------------------------------------
878
879
0
void WKTFormatter::ingestWKTNode(const WKTNodeNNPtr &node) {
880
0
    startNode(node->value(), true);
881
0
    for (const auto &child : node->children()) {
882
0
        if (!child->children().empty()) {
883
0
            ingestWKTNode(child);
884
0
        } else {
885
0
            add(child->value());
886
0
        }
887
0
    }
888
0
    endNode();
889
0
}
890
891
//! @endcond
892
893
// ---------------------------------------------------------------------------
894
895
//! @cond Doxygen_Suppress
896
897
static WKTNodeNNPtr
898
    null_node(NN_NO_CHECK(std::make_unique<WKTNode>(std::string())));
899
900
187k
static inline bool isNull(const WKTNodeNNPtr &node) {
901
187k
    return &node == &null_node;
902
187k
}
903
904
struct WKTNode::Private {
905
    std::string value_{};
906
    std::vector<WKTNodeNNPtr> children_{};
907
908
72.5k
    explicit Private(const std::string &valueIn) : value_(valueIn) {}
909
910
    // cppcheck-suppress functionStatic
911
967k
    inline const std::string &value() PROJ_PURE_DEFN { return value_; }
912
913
    // cppcheck-suppress functionStatic
914
66.9k
    inline const std::vector<WKTNodeNNPtr> &children() PROJ_PURE_DEFN {
915
66.9k
        return children_;
916
66.9k
    }
917
918
    // cppcheck-suppress functionStatic
919
12.9k
    inline size_t childrenSize() PROJ_PURE_DEFN { return children_.size(); }
920
921
    // cppcheck-suppress functionStatic
922
    const WKTNodeNNPtr &lookForChild(const std::string &childName,
923
                                     int occurrence) const noexcept;
924
925
    // cppcheck-suppress functionStatic
926
    const WKTNodeNNPtr &lookForChild(const std::string &name) const noexcept;
927
928
    // cppcheck-suppress functionStatic
929
    const WKTNodeNNPtr &lookForChild(const std::string &name,
930
                                     const std::string &name2) const noexcept;
931
932
    // cppcheck-suppress functionStatic
933
    const WKTNodeNNPtr &lookForChild(const std::string &name,
934
                                     const std::string &name2,
935
                                     const std::string &name3) const noexcept;
936
937
    // cppcheck-suppress functionStatic
938
    const WKTNodeNNPtr &lookForChild(const std::string &name,
939
                                     const std::string &name2,
940
                                     const std::string &name3,
941
                                     const std::string &name4) const noexcept;
942
};
943
944
1.04M
#define GP() getPrivate()
945
946
// ---------------------------------------------------------------------------
947
948
const WKTNodeNNPtr &
949
WKTNode::Private::lookForChild(const std::string &childName,
950
230
                               int occurrence) const noexcept {
951
230
    int occCount = 0;
952
671
    for (const auto &child : children_) {
953
671
        if (ci_equal(child->GP()->value(), childName)) {
954
231
            if (occurrence == occCount) {
955
230
                return child;
956
230
            }
957
1
            occCount++;
958
1
        }
959
671
    }
960
0
    return null_node;
961
230
}
962
963
const WKTNodeNNPtr &
964
174k
WKTNode::Private::lookForChild(const std::string &name) const noexcept {
965
715k
    for (const auto &child : children_) {
966
715k
        const auto &v = child->GP()->value();
967
715k
        if (ci_equal(v, name)) {
968
4.52k
            return child;
969
4.52k
        }
970
715k
    }
971
169k
    return null_node;
972
174k
}
973
974
const WKTNodeNNPtr &
975
WKTNode::Private::lookForChild(const std::string &name,
976
6.21k
                               const std::string &name2) const noexcept {
977
19.7k
    for (const auto &child : children_) {
978
19.7k
        const auto &v = child->GP()->value();
979
19.7k
        if (ci_equal(v, name) || ci_equal(v, name2)) {
980
2.41k
            return child;
981
2.41k
        }
982
19.7k
    }
983
3.80k
    return null_node;
984
6.21k
}
985
986
const WKTNodeNNPtr &
987
WKTNode::Private::lookForChild(const std::string &name,
988
                               const std::string &name2,
989
2.16k
                               const std::string &name3) const noexcept {
990
4.17k
    for (const auto &child : children_) {
991
4.17k
        const auto &v = child->GP()->value();
992
4.17k
        if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3)) {
993
1.95k
            return child;
994
1.95k
        }
995
4.17k
    }
996
211
    return null_node;
997
2.16k
}
998
999
const WKTNodeNNPtr &WKTNode::Private::lookForChild(
1000
    const std::string &name, const std::string &name2, const std::string &name3,
1001
762
    const std::string &name4) const noexcept {
1002
1.63k
    for (const auto &child : children_) {
1003
1.63k
        const auto &v = child->GP()->value();
1004
1.63k
        if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3) ||
1005
1.12k
            ci_equal(v, name4)) {
1006
697
            return child;
1007
697
        }
1008
1.63k
    }
1009
65
    return null_node;
1010
762
}
1011
1012
//! @endcond
1013
1014
// ---------------------------------------------------------------------------
1015
1016
/** \brief Instantiate a WKTNode.
1017
 *
1018
 * @param valueIn the name of the node.
1019
 */
1020
WKTNode::WKTNode(const std::string &valueIn)
1021
72.5k
    : d(std::make_unique<Private>(valueIn)) {}
1022
1023
// ---------------------------------------------------------------------------
1024
1025
//! @cond Doxygen_Suppress
1026
72.5k
WKTNode::~WKTNode() = default;
1027
//! @endcond
1028
1029
// ---------------------------------------------------------------------------
1030
1031
/** \brief Adds a child to the current node.
1032
 *
1033
 * @param child child to add. This should not be a parent of this node.
1034
 */
1035
68.9k
void WKTNode::addChild(WKTNodeNNPtr &&child) {
1036
68.9k
    d->children_.push_back(std::move(child));
1037
68.9k
}
1038
1039
// ---------------------------------------------------------------------------
1040
1041
/** \brief Return the (occurrence-1)th sub-node of name childName.
1042
 *
1043
 * @param childName name of the child.
1044
 * @param occurrence occurrence index (starting at 0)
1045
 * @return the child, or nullptr.
1046
 */
1047
const WKTNodePtr &WKTNode::lookForChild(const std::string &childName,
1048
1.78k
                                        int occurrence) const noexcept {
1049
1.78k
    int occCount = 0;
1050
11.3k
    for (const auto &child : d->children_) {
1051
11.3k
        if (ci_equal(child->GP()->value(), childName)) {
1052
162
            if (occurrence == occCount) {
1053
162
                return child;
1054
162
            }
1055
0
            occCount++;
1056
0
        }
1057
11.3k
    }
1058
1.62k
    return null_node;
1059
1.78k
}
1060
1061
// ---------------------------------------------------------------------------
1062
1063
/** \brief Return the count of children of given name.
1064
 *
1065
 * @param childName name of the children to look for.
1066
 * @return count
1067
 */
1068
3.57k
int WKTNode::countChildrenOfName(const std::string &childName) const noexcept {
1069
3.57k
    int occCount = 0;
1070
20.0k
    for (const auto &child : d->children_) {
1071
20.0k
        if (ci_equal(child->GP()->value(), childName)) {
1072
248
            occCount++;
1073
248
        }
1074
20.0k
    }
1075
3.57k
    return occCount;
1076
3.57k
}
1077
1078
// ---------------------------------------------------------------------------
1079
1080
/** \brief Return the value of a node.
1081
 */
1082
0
const std::string &WKTNode::value() const { return d->value_; }
1083
1084
// ---------------------------------------------------------------------------
1085
1086
/** \brief Return the children of a node.
1087
 */
1088
0
const std::vector<WKTNodeNNPtr> &WKTNode::children() const {
1089
0
    return d->children_;
1090
0
}
1091
1092
// ---------------------------------------------------------------------------
1093
1094
//! @cond Doxygen_Suppress
1095
251k
static size_t skipSpace(const std::string &str, size_t start) {
1096
251k
    size_t i = start;
1097
255k
    while (i < str.size() && ::isspace(static_cast<unsigned char>(str[i]))) {
1098
3.88k
        ++i;
1099
3.88k
    }
1100
251k
    return i;
1101
251k
}
1102
//! @endcond
1103
1104
// ---------------------------------------------------------------------------
1105
1106
//! @cond Doxygen_Suppress
1107
// As used in examples of OGC 12-063r5
1108
static const std::string startPrintedQuote("\xE2\x80\x9C");
1109
static const std::string endPrintedQuote("\xE2\x80\x9D");
1110
//! @endcond
1111
1112
WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart,
1113
72.8k
                                 int recLevel, size_t &indexEnd) {
1114
72.8k
    if (recLevel == 16) {
1115
6
        throw ParsingException("too many nesting levels");
1116
6
    }
1117
72.8k
    std::string value;
1118
72.8k
    size_t i = skipSpace(wkt, indexStart);
1119
72.8k
    if (i == wkt.size()) {
1120
0
        throw ParsingException("whitespace only string");
1121
0
    }
1122
72.8k
    std::string closingStringMarker;
1123
72.8k
    bool inString = false;
1124
1125
620k
    for (; i < wkt.size() &&
1126
620k
           (inString ||
1127
462k
            (wkt[i] != '[' && wkt[i] != '(' && wkt[i] != ',' && wkt[i] != ']' &&
1128
391k
             wkt[i] != ')' && !::isspace(static_cast<unsigned char>(wkt[i]))));
1129
547k
         ++i) {
1130
547k
        if (wkt[i] == '"') {
1131
19.9k
            if (!inString) {
1132
9.53k
                inString = true;
1133
9.53k
                closingStringMarker = "\"";
1134
10.3k
            } else if (closingStringMarker == "\"") {
1135
9.95k
                if (i + 1 < wkt.size() && wkt[i + 1] == '"') {
1136
450
                    i++;
1137
9.50k
                } else {
1138
9.50k
                    inString = false;
1139
9.50k
                    closingStringMarker.clear();
1140
9.50k
                }
1141
9.95k
            }
1142
527k
        } else if (i + 3 <= wkt.size() &&
1143
526k
                   wkt.substr(i, 3) == startPrintedQuote) {
1144
132
            if (!inString) {
1145
78
                inString = true;
1146
78
                closingStringMarker = endPrintedQuote;
1147
78
                value += '"';
1148
78
                i += 2;
1149
78
                continue;
1150
78
            }
1151
527k
        } else if (i + 3 <= wkt.size() &&
1152
526k
                   closingStringMarker == endPrintedQuote &&
1153
1.81k
                   wkt.substr(i, 3) == endPrintedQuote) {
1154
33
            inString = false;
1155
33
            closingStringMarker.clear();
1156
33
            value += '"';
1157
33
            i += 2;
1158
33
            continue;
1159
33
        }
1160
547k
        value += wkt[i];
1161
547k
    }
1162
72.8k
    i = skipSpace(wkt, i);
1163
72.8k
    if (i == wkt.size()) {
1164
313
        if (indexStart == 0) {
1165
0
            throw ParsingException("missing [");
1166
313
        } else {
1167
313
            throw ParsingException("missing , or ]");
1168
313
        }
1169
313
    }
1170
1171
72.5k
    auto node = NN_NO_CHECK(std::make_unique<WKTNode>(value));
1172
1173
72.5k
    if (indexStart > 0) {
1174
69.5k
        if (wkt[i] == ',') {
1175
25.8k
            indexEnd = i + 1;
1176
25.8k
            return node;
1177
25.8k
        }
1178
43.7k
        if (wkt[i] == ']' || wkt[i] == ')') {
1179
20.7k
            indexEnd = i;
1180
20.7k
            return node;
1181
20.7k
        }
1182
43.7k
    }
1183
25.9k
    if (wkt[i] != '[' && wkt[i] != '(') {
1184
27
        throw ParsingException("missing [");
1185
27
    }
1186
25.9k
    ++i; // skip [
1187
25.9k
    i = skipSpace(wkt, i);
1188
95.5k
    while (i < wkt.size() && wkt[i] != ']' && wkt[i] != ')') {
1189
69.6k
        size_t indexEndChild;
1190
69.6k
        node->addChild(createFrom(wkt, i, recLevel + 1, indexEndChild));
1191
69.6k
        assert(indexEndChild > i);
1192
69.6k
        i = indexEndChild;
1193
69.6k
        i = skipSpace(wkt, i);
1194
69.6k
        if (i < wkt.size() && wkt[i] == ',') {
1195
9.86k
            ++i;
1196
9.86k
            i = skipSpace(wkt, i);
1197
9.86k
        }
1198
69.6k
    }
1199
25.9k
    if (i == wkt.size() || (wkt[i] != ']' && wkt[i] != ')')) {
1200
55
        throw ParsingException("missing ]");
1201
55
    }
1202
25.8k
    indexEnd = i + 1;
1203
25.8k
    return node;
1204
25.9k
}
1205
// ---------------------------------------------------------------------------
1206
1207
/** \brief Instantiate a WKTNode hierarchy from a WKT string.
1208
 *
1209
 * @param wkt the WKT string to parse.
1210
 * @param indexStart the start index in the wkt string.
1211
 * @throw ParsingException if the string cannot be parsed.
1212
 */
1213
0
WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart) {
1214
0
    size_t indexEnd;
1215
0
    return createFrom(wkt, indexStart, 0, indexEnd);
1216
0
}
1217
1218
// ---------------------------------------------------------------------------
1219
1220
//! @cond Doxygen_Suppress
1221
0
static std::string escapeIfQuotedString(const std::string &str) {
1222
0
    if (str.size() > 2 && str[0] == '"' && str.back() == '"') {
1223
0
        std::string res("\"");
1224
0
        res += replaceAll(str.substr(1, str.size() - 2), "\"", "\"\"");
1225
0
        res += '"';
1226
0
        return res;
1227
0
    } else {
1228
0
        return str;
1229
0
    }
1230
0
}
1231
//! @endcond
1232
1233
// ---------------------------------------------------------------------------
1234
1235
/** \brief Return a WKT representation of the tree structure.
1236
 */
1237
0
std::string WKTNode::toString() const {
1238
0
    std::string str(escapeIfQuotedString(d->value_));
1239
0
    if (!d->children_.empty()) {
1240
0
        str += "[";
1241
0
        bool first = true;
1242
0
        for (auto &child : d->children_) {
1243
0
            if (!first) {
1244
0
                str += ',';
1245
0
            }
1246
0
            first = false;
1247
0
            str += child->toString();
1248
0
        }
1249
0
        str += "]";
1250
0
    }
1251
0
    return str;
1252
0
}
1253
1254
// ---------------------------------------------------------------------------
1255
1256
//! @cond Doxygen_Suppress
1257
struct WKTParser::Private {
1258
1259
    struct ci_less_struct {
1260
        bool operator()(const std::string &lhs,
1261
23.1k
                        const std::string &rhs) const noexcept {
1262
23.1k
            return ci_less(lhs, rhs);
1263
23.1k
        }
1264
    };
1265
1266
    bool strict_ = true;
1267
    bool unsetIdentifiersIfIncompatibleDef_ = true;
1268
    std::list<std::string> warningList_{};
1269
    std::list<std::string> grammarErrorList_{};
1270
    std::vector<double> toWGS84Parameters_{};
1271
    std::string datumPROJ4Grids_{};
1272
    bool esriStyle_ = false;
1273
    bool maybeEsriStyle_ = false;
1274
    DatabaseContextPtr dbContext_{};
1275
    crs::GeographicCRSPtr geogCRSOfCompoundCRS_{};
1276
1277
    static constexpr unsigned int MAX_PROPERTY_SIZE = 1024;
1278
    std::vector<std::unique_ptr<PropertyMap>> properties_{};
1279
1280
2.95k
    Private() = default;
1281
2.95k
    ~Private() = default;
1282
    Private(const Private &) = delete;
1283
    Private &operator=(const Private &) = delete;
1284
1285
    void emitRecoverableWarning(const std::string &warningMsg);
1286
    void emitGrammarError(const std::string &errorMsg);
1287
    void emitRecoverableMissingUNIT(const std::string &parentNodeName,
1288
                                    const UnitOfMeasure &fallbackUnit);
1289
1290
    BaseObjectNNPtr build(const WKTNodeNNPtr &node);
1291
1292
    IdentifierPtr buildId(const WKTNodeNNPtr &parentNode,
1293
                          const WKTNodeNNPtr &node, bool tolerant,
1294
                          bool removeInverseOf);
1295
1296
    PropertyMap &buildProperties(const WKTNodeNNPtr &node,
1297
                                 bool removeInverseOf = false,
1298
                                 bool hasName = true);
1299
1300
    ObjectDomainPtr buildObjectDomain(const WKTNodeNNPtr &node);
1301
1302
    static std::string stripQuotes(const WKTNodeNNPtr &node);
1303
1304
    static double asDouble(const WKTNodeNNPtr &node);
1305
1306
    UnitOfMeasure
1307
    buildUnit(const WKTNodeNNPtr &node,
1308
              UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN);
1309
1310
    UnitOfMeasure buildUnitInSubNode(
1311
        const WKTNodeNNPtr &node,
1312
        common::UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN);
1313
1314
    EllipsoidNNPtr buildEllipsoid(const WKTNodeNNPtr &node);
1315
1316
    PrimeMeridianNNPtr
1317
    buildPrimeMeridian(const WKTNodeNNPtr &node,
1318
                       const UnitOfMeasure &defaultAngularUnit);
1319
1320
    static optional<std::string> getAnchor(const WKTNodeNNPtr &node);
1321
1322
    static optional<common::Measure> getAnchorEpoch(const WKTNodeNNPtr &node);
1323
1324
    static void parseDynamic(const WKTNodeNNPtr &dynamicNode,
1325
                             double &frameReferenceEpoch,
1326
                             util::optional<std::string> &modelName);
1327
1328
    GeodeticReferenceFrameNNPtr
1329
    buildGeodeticReferenceFrame(const WKTNodeNNPtr &node,
1330
                                const PrimeMeridianNNPtr &primeMeridian,
1331
                                const WKTNodeNNPtr &dynamicNode);
1332
1333
    DatumEnsembleNNPtr buildDatumEnsemble(const WKTNodeNNPtr &node,
1334
                                          const PrimeMeridianPtr &primeMeridian,
1335
                                          bool expectEllipsoid);
1336
1337
    MeridianNNPtr buildMeridian(const WKTNodeNNPtr &node);
1338
    CoordinateSystemAxisNNPtr buildAxis(const WKTNodeNNPtr &node,
1339
                                        const UnitOfMeasure &unitIn,
1340
                                        const UnitOfMeasure::Type &unitType,
1341
                                        bool isGeocentric,
1342
                                        int expectedOrderNum);
1343
1344
    CoordinateSystemNNPtr buildCS(const WKTNodeNNPtr &node, /* maybe null */
1345
                                  const WKTNodeNNPtr &parentNode,
1346
                                  const UnitOfMeasure &defaultAngularUnit);
1347
1348
    GeodeticCRSNNPtr buildGeodeticCRS(const WKTNodeNNPtr &node,
1349
                                      bool forceGeocentricIfNoCs = false);
1350
1351
    CRSNNPtr buildDerivedGeodeticCRS(const WKTNodeNNPtr &node);
1352
1353
    static UnitOfMeasure
1354
    guessUnitForParameter(const std::string &paramName,
1355
                          const UnitOfMeasure &defaultLinearUnit,
1356
                          const UnitOfMeasure &defaultAngularUnit);
1357
1358
    void consumeParameters(const WKTNodeNNPtr &node, bool isAbridged,
1359
                           std::vector<OperationParameterNNPtr> &parameters,
1360
                           std::vector<ParameterValueNNPtr> &values,
1361
                           const UnitOfMeasure &defaultLinearUnit,
1362
                           const UnitOfMeasure &defaultAngularUnit);
1363
1364
    static std::string getExtensionProj4(const WKTNode::Private *nodeP);
1365
1366
    static void addExtensionProj4ToProp(const WKTNode::Private *nodeP,
1367
                                        PropertyMap &props);
1368
1369
    ConversionNNPtr buildConversion(const WKTNodeNNPtr &node,
1370
                                    const UnitOfMeasure &defaultLinearUnit,
1371
                                    const UnitOfMeasure &defaultAngularUnit);
1372
1373
    static bool hasWebMercPROJ4String(const WKTNodeNNPtr &projCRSNode,
1374
                                      const WKTNodeNNPtr &projectionNode);
1375
1376
    static std::string projectionGetParameter(const WKTNodeNNPtr &projCRSNode,
1377
                                              const char *paramName);
1378
1379
    ConversionNNPtr buildProjection(const GeodeticCRSNNPtr &baseGeodCRS,
1380
                                    const WKTNodeNNPtr &projCRSNode,
1381
                                    const WKTNodeNNPtr &projectionNode,
1382
                                    const UnitOfMeasure &defaultLinearUnit,
1383
                                    const UnitOfMeasure &defaultAngularUnit);
1384
1385
    ConversionNNPtr
1386
    buildProjectionStandard(const GeodeticCRSNNPtr &baseGeodCRS,
1387
                            const WKTNodeNNPtr &projCRSNode,
1388
                            const WKTNodeNNPtr &projectionNode,
1389
                            const UnitOfMeasure &defaultLinearUnit,
1390
                            const UnitOfMeasure &defaultAngularUnit);
1391
1392
    const ESRIMethodMapping *
1393
    getESRIMapping(const WKTNodeNNPtr &projCRSNode,
1394
                   const WKTNodeNNPtr &projectionNode,
1395
                   std::map<std::string, std::string, ci_less_struct>
1396
                       &mapParamNameToValue);
1397
1398
    static ConversionNNPtr
1399
    buildProjectionFromESRI(const GeodeticCRSNNPtr &baseGeodCRS,
1400
                            const WKTNodeNNPtr &projCRSNode,
1401
                            const WKTNodeNNPtr &projectionNode,
1402
                            const UnitOfMeasure &defaultLinearUnit,
1403
                            const UnitOfMeasure &defaultAngularUnit,
1404
                            const ESRIMethodMapping *esriMapping,
1405
                            std::map<std::string, std::string, ci_less_struct>
1406
                                &mapParamNameToValue);
1407
1408
    ConversionNNPtr
1409
    buildProjectionFromESRI(const GeodeticCRSNNPtr &baseGeodCRS,
1410
                            const WKTNodeNNPtr &projCRSNode,
1411
                            const WKTNodeNNPtr &projectionNode,
1412
                            const UnitOfMeasure &defaultLinearUnit,
1413
                            const UnitOfMeasure &defaultAngularUnit);
1414
1415
    ProjectedCRSNNPtr buildProjectedCRS(const WKTNodeNNPtr &node);
1416
1417
    VerticalReferenceFrameNNPtr
1418
    buildVerticalReferenceFrame(const WKTNodeNNPtr &node,
1419
                                const WKTNodeNNPtr &dynamicNode);
1420
1421
    TemporalDatumNNPtr buildTemporalDatum(const WKTNodeNNPtr &node);
1422
1423
    EngineeringDatumNNPtr buildEngineeringDatum(const WKTNodeNNPtr &node);
1424
1425
    ParametricDatumNNPtr buildParametricDatum(const WKTNodeNNPtr &node);
1426
1427
    CRSNNPtr buildVerticalCRS(const WKTNodeNNPtr &node);
1428
1429
    DerivedVerticalCRSNNPtr buildDerivedVerticalCRS(const WKTNodeNNPtr &node);
1430
1431
    CRSNNPtr buildCompoundCRS(const WKTNodeNNPtr &node);
1432
1433
    BoundCRSNNPtr buildBoundCRS(const WKTNodeNNPtr &node);
1434
1435
    TemporalCSNNPtr buildTemporalCS(const WKTNodeNNPtr &parentNode);
1436
1437
    TemporalCRSNNPtr buildTemporalCRS(const WKTNodeNNPtr &node);
1438
1439
    DerivedTemporalCRSNNPtr buildDerivedTemporalCRS(const WKTNodeNNPtr &node);
1440
1441
    EngineeringCRSNNPtr buildEngineeringCRS(const WKTNodeNNPtr &node);
1442
1443
    EngineeringCRSNNPtr
1444
    buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node);
1445
1446
    DerivedEngineeringCRSNNPtr
1447
    buildDerivedEngineeringCRS(const WKTNodeNNPtr &node);
1448
1449
    ParametricCSNNPtr buildParametricCS(const WKTNodeNNPtr &parentNode);
1450
1451
    ParametricCRSNNPtr buildParametricCRS(const WKTNodeNNPtr &node);
1452
1453
    DerivedParametricCRSNNPtr
1454
    buildDerivedParametricCRS(const WKTNodeNNPtr &node);
1455
1456
    DerivedProjectedCRSNNPtr buildDerivedProjectedCRS(const WKTNodeNNPtr &node);
1457
1458
    CRSPtr buildCRS(const WKTNodeNNPtr &node);
1459
1460
    TransformationNNPtr buildCoordinateOperation(const WKTNodeNNPtr &node);
1461
1462
    PointMotionOperationNNPtr
1463
    buildPointMotionOperation(const WKTNodeNNPtr &node);
1464
1465
    ConcatenatedOperationNNPtr
1466
    buildConcatenatedOperation(const WKTNodeNNPtr &node);
1467
1468
    CoordinateMetadataNNPtr buildCoordinateMetadata(const WKTNodeNNPtr &node);
1469
};
1470
//! @endcond
1471
1472
// ---------------------------------------------------------------------------
1473
1474
2.95k
WKTParser::WKTParser() : d(std::make_unique<Private>()) {}
1475
1476
// ---------------------------------------------------------------------------
1477
1478
//! @cond Doxygen_Suppress
1479
2.95k
WKTParser::~WKTParser() = default;
1480
//! @endcond
1481
1482
// ---------------------------------------------------------------------------
1483
1484
/** \brief Set whether parsing should be done in strict mode.
1485
 */
1486
2.95k
WKTParser &WKTParser::setStrict(bool strict) {
1487
2.95k
    d->strict_ = strict;
1488
2.95k
    return *this;
1489
2.95k
}
1490
1491
// ---------------------------------------------------------------------------
1492
1493
/** \brief Set whether object identifiers should be unset when there is
1494
 *         a contradiction between the definition from WKT and the one from
1495
 *         the database.
1496
 *
1497
 * At time of writing, this only applies to the base geographic CRS of a
1498
 * projected CRS, when comparing its coordinate system.
1499
 */
1500
0
WKTParser &WKTParser::setUnsetIdentifiersIfIncompatibleDef(bool unset) {
1501
0
    d->unsetIdentifiersIfIncompatibleDef_ = unset;
1502
0
    return *this;
1503
0
}
1504
1505
// ---------------------------------------------------------------------------
1506
1507
/** \brief Return the list of warnings found during parsing.
1508
 *
1509
 * \note The list might be non-empty only is setStrict(false) has been called.
1510
 */
1511
0
std::list<std::string> WKTParser::warningList() const {
1512
0
    return d->warningList_;
1513
0
}
1514
1515
// ---------------------------------------------------------------------------
1516
1517
/** \brief Return the list of grammar errors found during parsing.
1518
 *
1519
 * Grammar errors are non-compliance issues with respect to the WKT grammar.
1520
 *
1521
 * \note The list might be non-empty only is setStrict(false) has been called.
1522
 *
1523
 * @since PROJ 9.5
1524
 */
1525
0
std::list<std::string> WKTParser::grammarErrorList() const {
1526
0
    return d->grammarErrorList_;
1527
0
}
1528
1529
// ---------------------------------------------------------------------------
1530
1531
//! @cond Doxygen_Suppress
1532
2.72k
void WKTParser::Private::emitRecoverableWarning(const std::string &errorMsg) {
1533
2.72k
    if (strict_) {
1534
0
        throw ParsingException(errorMsg);
1535
2.72k
    } else {
1536
2.72k
        warningList_.push_back(errorMsg);
1537
2.72k
    }
1538
2.72k
}
1539
//! @endcond
1540
1541
// ---------------------------------------------------------------------------
1542
1543
//! @cond Doxygen_Suppress
1544
1.02k
void WKTParser::Private::emitGrammarError(const std::string &errorMsg) {
1545
1.02k
    if (strict_) {
1546
0
        throw ParsingException(errorMsg);
1547
1.02k
    } else {
1548
1.02k
        grammarErrorList_.push_back(errorMsg);
1549
1.02k
    }
1550
1.02k
}
1551
//! @endcond
1552
1553
// ---------------------------------------------------------------------------
1554
1555
11.7k
static double asDouble(const std::string &val) { return c_locale_stod(val); }
1556
1557
// ---------------------------------------------------------------------------
1558
1559
155
PROJ_NO_RETURN static void ThrowNotEnoughChildren(const std::string &nodeName) {
1560
155
    throw ParsingException(
1561
155
        concat("not enough children in ", nodeName, " node"));
1562
155
}
1563
1564
// ---------------------------------------------------------------------------
1565
1566
PROJ_NO_RETURN static void
1567
31
ThrowNotRequiredNumberOfChildren(const std::string &nodeName) {
1568
31
    throw ParsingException(
1569
31
        concat("not required number of children in ", nodeName, " node"));
1570
31
}
1571
1572
// ---------------------------------------------------------------------------
1573
1574
328
PROJ_NO_RETURN static void ThrowMissing(const std::string &nodeName) {
1575
328
    throw ParsingException(concat("missing ", nodeName, " node"));
1576
328
}
1577
1578
// ---------------------------------------------------------------------------
1579
1580
PROJ_NO_RETURN static void
1581
0
ThrowNotExpectedCSType(const std::string &expectedCSType) {
1582
0
    throw ParsingException(concat("CS node is not of type ", expectedCSType));
1583
0
}
1584
1585
// ---------------------------------------------------------------------------
1586
1587
static ParsingException buildRethrow(const char *funcName,
1588
77
                                     const std::exception &e) {
1589
77
    std::string res(funcName);
1590
77
    res += ": ";
1591
77
    res += e.what();
1592
77
    return ParsingException(res);
1593
77
}
1594
1595
// ---------------------------------------------------------------------------
1596
1597
//! @cond Doxygen_Suppress
1598
31.5k
std::string WKTParser::Private::stripQuotes(const WKTNodeNNPtr &node) {
1599
31.5k
    return ::stripQuotes(node->GP()->value());
1600
31.5k
}
1601
1602
// ---------------------------------------------------------------------------
1603
1604
9.18k
double WKTParser::Private::asDouble(const WKTNodeNNPtr &node) {
1605
9.18k
    return io::asDouble(node->GP()->value());
1606
9.18k
}
1607
1608
// ---------------------------------------------------------------------------
1609
1610
IdentifierPtr WKTParser::Private::buildId(const WKTNodeNNPtr &parentNode,
1611
                                          const WKTNodeNNPtr &node,
1612
2.49k
                                          bool tolerant, bool removeInverseOf) {
1613
2.49k
    const auto *nodeP = node->GP();
1614
2.49k
    const auto &nodeChildren = nodeP->children();
1615
2.49k
    if (nodeChildren.size() >= 2) {
1616
2.10k
        auto codeSpace = stripQuotes(nodeChildren[0]);
1617
2.10k
        if (removeInverseOf && starts_with(codeSpace, "INVERSE(") &&
1618
48
            codeSpace.back() == ')') {
1619
43
            codeSpace = codeSpace.substr(strlen("INVERSE("));
1620
43
            codeSpace.resize(codeSpace.size() - 1);
1621
43
        }
1622
1623
2.10k
        PropertyMap propertiesId;
1624
2.10k
        if (nodeChildren.size() >= 3 &&
1625
1.14k
            nodeChildren[2]->GP()->childrenSize() == 0) {
1626
943
            std::string version = stripQuotes(nodeChildren[2]);
1627
1628
            // IAU + 2015 -> IAU_2015
1629
943
            if (dbContext_) {
1630
617
                std::string codeSpaceOut;
1631
617
                if (dbContext_->getVersionedAuthority(codeSpace, version,
1632
617
                                                      codeSpaceOut)) {
1633
0
                    codeSpace = std::move(codeSpaceOut);
1634
0
                    version.clear();
1635
0
                }
1636
617
            }
1637
1638
943
            if (!version.empty()) {
1639
902
                propertiesId.set(Identifier::VERSION_KEY, version);
1640
902
            }
1641
943
        }
1642
1643
2.10k
        auto code = stripQuotes(nodeChildren[1]);
1644
1645
        // Prior to PROJ 9.5, when synthetizing an ID for a CONVERSION UTM Zone
1646
        // south, we generated a wrong value. Auto-fix that
1647
2.10k
        const auto &parentNodeKeyword(parentNode->GP()->value());
1648
2.10k
        if (parentNodeKeyword == WKTConstants::CONVERSION &&
1649
237
            codeSpace == Identifier::EPSG) {
1650
36
            const auto &parentNodeChildren = parentNode->GP()->children();
1651
36
            if (!parentNodeChildren.empty()) {
1652
36
                const auto parentNodeName(stripQuotes(parentNodeChildren[0]));
1653
36
                if (ci_starts_with(parentNodeName, "UTM Zone ") &&
1654
0
                    parentNodeName.find('S') != std::string::npos) {
1655
0
                    const int nZone =
1656
0
                        atoi(parentNodeName.c_str() + strlen("UTM Zone "));
1657
0
                    if (nZone >= 1 && nZone <= 60) {
1658
0
                        code = internal::toString(16100 + nZone);
1659
0
                    }
1660
0
                }
1661
36
            }
1662
36
        }
1663
1664
2.10k
        auto &citationNode = nodeP->lookForChild(WKTConstants::CITATION);
1665
2.10k
        auto &uriNode = nodeP->lookForChild(WKTConstants::URI);
1666
1667
2.10k
        propertiesId.set(Identifier::CODESPACE_KEY, codeSpace);
1668
2.10k
        bool authoritySet = false;
1669
2.10k
        /*if (!isNull(citationNode))*/ {
1670
2.10k
            const auto *citationNodeP = citationNode->GP();
1671
2.10k
            if (citationNodeP->childrenSize() == 1) {
1672
10
                authoritySet = true;
1673
10
                propertiesId.set(Identifier::AUTHORITY_KEY,
1674
10
                                 stripQuotes(citationNodeP->children()[0]));
1675
10
            }
1676
2.10k
        }
1677
2.10k
        if (!authoritySet) {
1678
2.09k
            propertiesId.set(Identifier::AUTHORITY_KEY, codeSpace);
1679
2.09k
        }
1680
2.10k
        /*if (!isNull(uriNode))*/ {
1681
2.10k
            const auto *uriNodeP = uriNode->GP();
1682
2.10k
            if (uriNodeP->childrenSize() == 1) {
1683
13
                propertiesId.set(Identifier::URI_KEY,
1684
13
                                 stripQuotes(uriNodeP->children()[0]));
1685
13
            }
1686
2.10k
        }
1687
2.10k
        return Identifier::create(code, propertiesId);
1688
2.10k
    } else if (strict_ || !tolerant) {
1689
4
        ThrowNotEnoughChildren(nodeP->value());
1690
390
    } else {
1691
390
        std::string msg("not enough children in ");
1692
390
        msg += nodeP->value();
1693
390
        msg += " node";
1694
390
        warningList_.emplace_back(std::move(msg));
1695
390
    }
1696
390
    return nullptr;
1697
2.49k
}
1698
1699
// ---------------------------------------------------------------------------
1700
1701
PropertyMap &WKTParser::Private::buildProperties(const WKTNodeNNPtr &node,
1702
                                                 bool removeInverseOf,
1703
12.8k
                                                 bool hasName) {
1704
1705
12.8k
    if (properties_.size() >= MAX_PROPERTY_SIZE) {
1706
0
        throw ParsingException("MAX_PROPERTY_SIZE reached");
1707
0
    }
1708
12.8k
    properties_.push_back(std::make_unique<PropertyMap>());
1709
12.8k
    auto properties = properties_.back().get();
1710
1711
12.8k
    std::string authNameFromAlias;
1712
12.8k
    std::string codeFromAlias;
1713
12.8k
    const auto *nodeP = node->GP();
1714
12.8k
    const auto &nodeChildren = nodeP->children();
1715
1716
12.8k
    auto identifiers = ArrayOfBaseObject::create();
1717
47.6k
    for (const auto &subNode : nodeChildren) {
1718
47.6k
        const auto &subNodeName(subNode->GP()->value());
1719
47.6k
        if (ci_equal(subNodeName, WKTConstants::ID) ||
1720
45.3k
            ci_equal(subNodeName, WKTConstants::AUTHORITY)) {
1721
2.46k
            auto id = buildId(node, subNode, true, removeInverseOf);
1722
2.46k
            if (id) {
1723
2.07k
                identifiers->add(NN_NO_CHECK(id));
1724
2.07k
            }
1725
2.46k
        }
1726
47.6k
    }
1727
1728
12.8k
    if (hasName && !nodeChildren.empty()) {
1729
12.0k
        const auto &nodeName(nodeP->value());
1730
12.0k
        auto name(stripQuotes(nodeChildren[0]));
1731
12.0k
        if (removeInverseOf && starts_with(name, "Inverse of ")) {
1732
102
            name = name.substr(strlen("Inverse of "));
1733
102
        }
1734
1735
12.0k
        if (ends_with(name, " (deprecated)")) {
1736
3
            name.resize(name.size() - strlen(" (deprecated)"));
1737
3
            properties->set(common::IdentifiedObject::DEPRECATED_KEY, true);
1738
3
        }
1739
1740
        // Oracle WKT can contain names like
1741
        // "Reseau Geodesique Francais 1993 (EPSG ID 6171)"
1742
        // for WKT attributes to the auth_name = "IGN - Paris"
1743
        // Strip that suffix from the name and assign a true EPSG code to the
1744
        // object
1745
12.0k
        if (identifiers->empty()) {
1746
11.4k
            const auto pos = name.find(" (EPSG ID ");
1747
11.4k
            if (pos != std::string::npos && name.back() == ')') {
1748
172
                const auto code =
1749
172
                    name.substr(pos + strlen(" (EPSG ID "),
1750
172
                                name.size() - 1 - pos - strlen(" (EPSG ID "));
1751
172
                name.resize(pos);
1752
1753
172
                PropertyMap propertiesId;
1754
172
                propertiesId.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
1755
172
                propertiesId.set(Identifier::AUTHORITY_KEY, Identifier::EPSG);
1756
172
                identifiers->add(Identifier::create(code, propertiesId));
1757
172
            }
1758
11.4k
        }
1759
1760
12.0k
        const char *tableNameForAlias = nullptr;
1761
12.0k
        if (ci_equal(nodeName, WKTConstants::GEOGCS)) {
1762
1.29k
            if (starts_with(name, "GCS_")) {
1763
317
                esriStyle_ = true;
1764
317
                if (name == "GCS_WGS_1984") {
1765
148
                    name = "WGS 84";
1766
169
                } else if (name == "GCS_unknown") {
1767
0
                    name = "unknown";
1768
169
                } else {
1769
169
                    tableNameForAlias = "geodetic_crs";
1770
169
                }
1771
317
            }
1772
10.7k
        } else if (esriStyle_ && ci_equal(nodeName, WKTConstants::SPHEROID)) {
1773
354
            if (name == "WGS_1984") {
1774
148
                name = "WGS 84";
1775
148
                authNameFromAlias = Identifier::EPSG;
1776
148
                codeFromAlias = "7030";
1777
206
            } else {
1778
206
                tableNameForAlias = "ellipsoid";
1779
206
            }
1780
354
        }
1781
1782
12.0k
        if (dbContext_ && tableNameForAlias) {
1783
357
            std::string outTableName;
1784
357
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
1785
357
                                                        std::string());
1786
357
            auto officialName = authFactory->getOfficialNameFromAlias(
1787
357
                name, tableNameForAlias, "ESRI", false, outTableName,
1788
357
                authNameFromAlias, codeFromAlias);
1789
357
            if (!officialName.empty()) {
1790
163
                name = std::move(officialName);
1791
1792
                // Clearing authority for geodetic_crs because of
1793
                // potential axis order mismatch.
1794
163
                if (strcmp(tableNameForAlias, "geodetic_crs") == 0) {
1795
129
                    authNameFromAlias.clear();
1796
129
                    codeFromAlias.clear();
1797
129
                }
1798
163
            }
1799
357
        }
1800
1801
12.0k
        properties->set(IdentifiedObject::NAME_KEY, name);
1802
12.0k
    }
1803
1804
12.8k
    if (identifiers->empty() && !authNameFromAlias.empty()) {
1805
182
        identifiers->add(Identifier::create(
1806
182
            codeFromAlias,
1807
182
            PropertyMap()
1808
182
                .set(Identifier::CODESPACE_KEY, authNameFromAlias)
1809
182
                .set(Identifier::AUTHORITY_KEY, authNameFromAlias)));
1810
182
    }
1811
12.8k
    if (!identifiers->empty()) {
1812
914
        properties->set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
1813
914
    }
1814
1815
12.8k
    auto &remarkNode = nodeP->lookForChild(WKTConstants::REMARK);
1816
12.8k
    if (!isNull(remarkNode)) {
1817
69
        const auto &remarkChildren = remarkNode->GP()->children();
1818
69
        if (remarkChildren.size() == 1) {
1819
64
            properties->set(IdentifiedObject::REMARKS_KEY,
1820
64
                            stripQuotes(remarkChildren[0]));
1821
64
        } else {
1822
5
            ThrowNotRequiredNumberOfChildren(remarkNode->GP()->value());
1823
5
        }
1824
69
    }
1825
1826
12.8k
    ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create();
1827
47.5k
    for (const auto &subNode : nodeP->children()) {
1828
47.5k
        const auto &subNodeName(subNode->GP()->value());
1829
47.5k
        if (ci_equal(subNodeName, WKTConstants::USAGE)) {
1830
101
            auto objectDomain = buildObjectDomain(subNode);
1831
101
            if (!objectDomain) {
1832
4
                throw ParsingException(
1833
4
                    concat("missing children in ", subNodeName, " node"));
1834
4
            }
1835
97
            array->add(NN_NO_CHECK(objectDomain));
1836
97
        }
1837
47.5k
    }
1838
12.8k
    if (!array->empty()) {
1839
32
        properties->set(ObjectUsage::OBJECT_DOMAIN_KEY, array);
1840
12.8k
    } else {
1841
12.8k
        auto objectDomain = buildObjectDomain(node);
1842
12.8k
        if (objectDomain) {
1843
337
            properties->set(ObjectUsage::OBJECT_DOMAIN_KEY,
1844
337
                            NN_NO_CHECK(objectDomain));
1845
337
        }
1846
12.8k
    }
1847
1848
12.8k
    auto &versionNode = nodeP->lookForChild(WKTConstants::VERSION);
1849
12.8k
    if (!isNull(versionNode)) {
1850
22
        const auto &versionChildren = versionNode->GP()->children();
1851
22
        if (versionChildren.size() == 1) {
1852
19
            properties->set(CoordinateOperation::OPERATION_VERSION_KEY,
1853
19
                            stripQuotes(versionChildren[0]));
1854
19
        } else {
1855
3
            ThrowNotRequiredNumberOfChildren(versionNode->GP()->value());
1856
3
        }
1857
22
    }
1858
1859
12.8k
    return *properties;
1860
12.8k
}
1861
1862
// ---------------------------------------------------------------------------
1863
1864
ObjectDomainPtr
1865
12.9k
WKTParser::Private::buildObjectDomain(const WKTNodeNNPtr &node) {
1866
1867
12.9k
    const auto *nodeP = node->GP();
1868
12.9k
    auto &scopeNode = nodeP->lookForChild(WKTConstants::SCOPE);
1869
12.9k
    auto &areaNode = nodeP->lookForChild(WKTConstants::AREA);
1870
12.9k
    auto &bboxNode = nodeP->lookForChild(WKTConstants::BBOX);
1871
12.9k
    auto &verticalExtentNode =
1872
12.9k
        nodeP->lookForChild(WKTConstants::VERTICALEXTENT);
1873
12.9k
    auto &temporalExtentNode = nodeP->lookForChild(WKTConstants::TIMEEXTENT);
1874
12.9k
    if (!isNull(scopeNode) || !isNull(areaNode) || !isNull(bboxNode) ||
1875
12.4k
        !isNull(verticalExtentNode) || !isNull(temporalExtentNode)) {
1876
471
        optional<std::string> scope;
1877
471
        const auto *scopeNodeP = scopeNode->GP();
1878
471
        const auto &scopeChildren = scopeNodeP->children();
1879
471
        if (scopeChildren.size() == 1) {
1880
0
            scope = stripQuotes(scopeChildren[0]);
1881
0
        }
1882
471
        ExtentPtr extent;
1883
471
        if (!isNull(areaNode) || !isNull(bboxNode)) {
1884
448
            util::optional<std::string> description;
1885
448
            std::vector<GeographicExtentNNPtr> geogExtent;
1886
448
            std::vector<VerticalExtentNNPtr> verticalExtent;
1887
448
            std::vector<TemporalExtentNNPtr> temporalExtent;
1888
448
            if (!isNull(areaNode)) {
1889
99
                const auto &areaChildren = areaNode->GP()->children();
1890
99
                if (areaChildren.size() == 1) {
1891
96
                    description = stripQuotes(areaChildren[0]);
1892
96
                } else {
1893
3
                    ThrowNotRequiredNumberOfChildren(areaNode->GP()->value());
1894
3
                }
1895
99
            }
1896
445
            if (!isNull(bboxNode)) {
1897
349
                const auto &bboxChildren = bboxNode->GP()->children();
1898
349
                if (bboxChildren.size() == 4) {
1899
336
                    double south, west, north, east;
1900
336
                    try {
1901
336
                        south = asDouble(bboxChildren[0]);
1902
336
                        west = asDouble(bboxChildren[1]);
1903
336
                        north = asDouble(bboxChildren[2]);
1904
336
                        east = asDouble(bboxChildren[3]);
1905
336
                    } catch (const std::exception &) {
1906
5
                        throw ParsingException(concat("not 4 double values in ",
1907
5
                                                      bboxNode->GP()->value(),
1908
5
                                                      " node"));
1909
5
                    }
1910
331
                    try {
1911
331
                        auto bbox = GeographicBoundingBox::create(west, south,
1912
331
                                                                  east, north);
1913
331
                        geogExtent.emplace_back(bbox);
1914
331
                    } catch (const std::exception &e) {
1915
4
                        throw ParsingException(concat("Invalid ",
1916
4
                                                      bboxNode->GP()->value(),
1917
4
                                                      " node: ") +
1918
4
                                               e.what());
1919
4
                    }
1920
331
                } else {
1921
13
                    ThrowNotRequiredNumberOfChildren(bboxNode->GP()->value());
1922
13
                }
1923
349
            }
1924
1925
423
            if (!isNull(verticalExtentNode)) {
1926
23
                const auto &verticalExtentChildren =
1927
23
                    verticalExtentNode->GP()->children();
1928
23
                const auto verticalExtentChildrenSize =
1929
23
                    verticalExtentChildren.size();
1930
23
                if (verticalExtentChildrenSize == 2 ||
1931
20
                    verticalExtentChildrenSize == 3) {
1932
20
                    double min;
1933
20
                    double max;
1934
20
                    try {
1935
20
                        min = asDouble(verticalExtentChildren[0]);
1936
20
                        max = asDouble(verticalExtentChildren[1]);
1937
20
                    } catch (const std::exception &) {
1938
5
                        throw ParsingException(
1939
5
                            concat("not 2 double values in ",
1940
5
                                   verticalExtentNode->GP()->value(), " node"));
1941
5
                    }
1942
15
                    UnitOfMeasure unit = UnitOfMeasure::METRE;
1943
15
                    if (verticalExtentChildrenSize == 3) {
1944
1
                        unit = buildUnit(verticalExtentChildren[2],
1945
1
                                         UnitOfMeasure::Type::LINEAR);
1946
1
                    }
1947
15
                    verticalExtent.emplace_back(VerticalExtent::create(
1948
15
                        min, max, util::nn_make_shared<UnitOfMeasure>(unit)));
1949
15
                } else {
1950
3
                    ThrowNotRequiredNumberOfChildren(
1951
3
                        verticalExtentNode->GP()->value());
1952
3
                }
1953
23
            }
1954
1955
415
            if (!isNull(temporalExtentNode)) {
1956
85
                const auto &temporalExtentChildren =
1957
85
                    temporalExtentNode->GP()->children();
1958
85
                if (temporalExtentChildren.size() == 2) {
1959
81
                    temporalExtent.emplace_back(TemporalExtent::create(
1960
81
                        stripQuotes(temporalExtentChildren[0]),
1961
81
                        stripQuotes(temporalExtentChildren[1])));
1962
81
                } else {
1963
4
                    ThrowNotRequiredNumberOfChildren(
1964
4
                        temporalExtentNode->GP()->value());
1965
4
                }
1966
85
            }
1967
411
            extent = Extent::create(description, geogExtent, verticalExtent,
1968
411
                                    temporalExtent)
1969
411
                         .as_nullable();
1970
411
        }
1971
434
        return ObjectDomain::create(scope, extent).as_nullable();
1972
471
    }
1973
1974
12.4k
    return nullptr;
1975
12.9k
}
1976
1977
// ---------------------------------------------------------------------------
1978
1979
UnitOfMeasure WKTParser::Private::buildUnit(const WKTNodeNNPtr &node,
1980
2.33k
                                            UnitOfMeasure::Type type) {
1981
2.33k
    const auto *nodeP = node->GP();
1982
2.33k
    const auto &children = nodeP->children();
1983
2.33k
    if ((type != UnitOfMeasure::Type::TIME && children.size() < 2) ||
1984
2.32k
        (type == UnitOfMeasure::Type::TIME && children.size() < 1)) {
1985
14
        ThrowNotEnoughChildren(nodeP->value());
1986
14
    }
1987
2.31k
    try {
1988
2.31k
        std::string unitName(stripQuotes(children[0]));
1989
2.31k
        PropertyMap properties(buildProperties(node));
1990
2.31k
        auto &idNode =
1991
2.31k
            nodeP->lookForChild(WKTConstants::ID, WKTConstants::AUTHORITY);
1992
2.31k
        if (!isNull(idNode) && idNode->GP()->childrenSize() < 2) {
1993
99
            emitRecoverableWarning("not enough children in " +
1994
99
                                   idNode->GP()->value() + " node");
1995
99
        }
1996
2.31k
        const bool hasValidIdNode =
1997
2.31k
            !isNull(idNode) && idNode->GP()->childrenSize() >= 2;
1998
1999
2.31k
        const auto &idNodeChildren(idNode->GP()->children());
2000
2.31k
        std::string codeSpace(hasValidIdNode ? stripQuotes(idNodeChildren[0])
2001
2.31k
                                             : std::string());
2002
2.31k
        std::string code(hasValidIdNode ? stripQuotes(idNodeChildren[1])
2003
2.31k
                                        : std::string());
2004
2005
2.31k
        bool queryDb = true;
2006
2.31k
        if (type == UnitOfMeasure::Type::UNKNOWN) {
2007
30
            if (ci_equal(unitName, "METER") || ci_equal(unitName, "METRE")) {
2008
0
                type = UnitOfMeasure::Type::LINEAR;
2009
0
                unitName = "metre";
2010
0
                if (codeSpace.empty()) {
2011
0
                    codeSpace = Identifier::EPSG;
2012
0
                    code = "9001";
2013
0
                    queryDb = false;
2014
0
                }
2015
30
            } else if (ci_equal(unitName, "DEGREE") ||
2016
30
                       ci_equal(unitName, "GRAD")) {
2017
0
                type = UnitOfMeasure::Type::ANGULAR;
2018
0
            }
2019
30
        }
2020
2021
2.31k
        if (esriStyle_ && dbContext_ && queryDb) {
2022
1.22k
            std::string outTableName;
2023
1.22k
            std::string authNameFromAlias;
2024
1.22k
            std::string codeFromAlias;
2025
1.22k
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2026
1.22k
                                                        std::string());
2027
1.22k
            auto officialName = authFactory->getOfficialNameFromAlias(
2028
1.22k
                unitName, "unit_of_measure", "ESRI", false, outTableName,
2029
1.22k
                authNameFromAlias, codeFromAlias);
2030
1.22k
            if (!officialName.empty()) {
2031
598
                unitName = std::move(officialName);
2032
598
                codeSpace = std::move(authNameFromAlias);
2033
598
                code = std::move(codeFromAlias);
2034
598
            }
2035
1.22k
        }
2036
2037
2.31k
        double convFactor = children.size() >= 2 ? asDouble(children[1]) : 0.0;
2038
2.31k
        constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37;
2039
2.31k
        constexpr double REL_ERROR = 1e-10;
2040
        // Fix common rounding errors
2041
2.31k
        if (std::fabs(convFactor - UnitOfMeasure::DEGREE.conversionToSI()) <
2042
2.31k
            REL_ERROR * convFactor) {
2043
640
            convFactor = UnitOfMeasure::DEGREE.conversionToSI();
2044
1.67k
        } else if (std::fabs(convFactor - US_FOOT_CONV_FACTOR) <
2045
1.67k
                   REL_ERROR * convFactor) {
2046
10
            convFactor = US_FOOT_CONV_FACTOR;
2047
10
        }
2048
2049
2.31k
        return UnitOfMeasure(unitName, convFactor, type, codeSpace, code);
2050
2.31k
    } catch (const std::exception &e) {
2051
16
        throw buildRethrow(__FUNCTION__, e);
2052
16
    }
2053
2.31k
}
2054
2055
// ---------------------------------------------------------------------------
2056
2057
// node here is a parent node, not a UNIT/LENGTHUNIT/ANGLEUNIT/TIMEUNIT/... node
2058
UnitOfMeasure WKTParser::Private::buildUnitInSubNode(const WKTNodeNNPtr &node,
2059
7.89k
                                                     UnitOfMeasure::Type type) {
2060
7.89k
    const auto *nodeP = node->GP();
2061
7.89k
    {
2062
7.89k
        auto &unitNode = nodeP->lookForChild(WKTConstants::LENGTHUNIT);
2063
7.89k
        if (!isNull(unitNode)) {
2064
3
            return buildUnit(unitNode, UnitOfMeasure::Type::LINEAR);
2065
3
        }
2066
7.89k
    }
2067
2068
7.89k
    {
2069
7.89k
        auto &unitNode = nodeP->lookForChild(WKTConstants::ANGLEUNIT);
2070
7.89k
        if (!isNull(unitNode)) {
2071
5
            return buildUnit(unitNode, UnitOfMeasure::Type::ANGULAR);
2072
5
        }
2073
7.89k
    }
2074
2075
7.88k
    {
2076
7.88k
        auto &unitNode = nodeP->lookForChild(WKTConstants::SCALEUNIT);
2077
7.88k
        if (!isNull(unitNode)) {
2078
1
            return buildUnit(unitNode, UnitOfMeasure::Type::SCALE);
2079
1
        }
2080
7.88k
    }
2081
2082
7.88k
    {
2083
7.88k
        auto &unitNode = nodeP->lookForChild(WKTConstants::TIMEUNIT);
2084
7.88k
        if (!isNull(unitNode)) {
2085
1
            return buildUnit(unitNode, UnitOfMeasure::Type::TIME);
2086
1
        }
2087
7.88k
    }
2088
7.88k
    {
2089
7.88k
        auto &unitNode = nodeP->lookForChild(WKTConstants::TEMPORALQUANTITY);
2090
7.88k
        if (!isNull(unitNode)) {
2091
6
            return buildUnit(unitNode, UnitOfMeasure::Type::TIME);
2092
6
        }
2093
7.88k
    }
2094
2095
7.88k
    {
2096
7.88k
        auto &unitNode = nodeP->lookForChild(WKTConstants::PARAMETRICUNIT);
2097
7.88k
        if (!isNull(unitNode)) {
2098
3
            return buildUnit(unitNode, UnitOfMeasure::Type::PARAMETRIC);
2099
3
        }
2100
7.88k
    }
2101
2102
7.87k
    {
2103
7.87k
        auto &unitNode = nodeP->lookForChild(WKTConstants::UNIT);
2104
7.87k
        if (!isNull(unitNode)) {
2105
2.31k
            return buildUnit(unitNode, type);
2106
2.31k
        }
2107
7.87k
    }
2108
2109
5.56k
    return UnitOfMeasure::NONE;
2110
7.87k
}
2111
2112
// ---------------------------------------------------------------------------
2113
2114
2.09k
EllipsoidNNPtr WKTParser::Private::buildEllipsoid(const WKTNodeNNPtr &node) {
2115
2.09k
    const auto *nodeP = node->GP();
2116
2.09k
    const auto &children = nodeP->children();
2117
2.09k
    if (children.size() < 3) {
2118
8
        ThrowNotEnoughChildren(nodeP->value());
2119
8
    }
2120
2.08k
    try {
2121
2.08k
        UnitOfMeasure unit =
2122
2.08k
            buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR);
2123
2.08k
        if (unit == UnitOfMeasure::NONE) {
2124
1.60k
            unit = UnitOfMeasure::METRE;
2125
1.60k
        }
2126
2.08k
        Length semiMajorAxis(asDouble(children[1]), unit);
2127
        // Some WKT in the wild use "inf". Cf SPHEROID["unnamed",6370997,"inf"]
2128
        // in https://zenodo.org/record/3878979#.Y_P4g4CZNH4,
2129
        // https://zenodo.org/record/5831940#.Y_P4i4CZNH5
2130
        // or https://grasswiki.osgeo.org/wiki/Marine_Science
2131
2.08k
        const auto &invFlatteningChild = children[2];
2132
2.08k
        if (invFlatteningChild->GP()->value() == "\"inf\"") {
2133
0
            emitRecoverableWarning("Inverse flattening = \"inf\" is not "
2134
0
                                   "conformant, but understood");
2135
0
        }
2136
2.08k
        Scale invFlattening(invFlatteningChild->GP()->value() == "\"inf\""
2137
2.08k
                                ? 0
2138
2.08k
                                : asDouble(invFlatteningChild));
2139
2.08k
        const auto ellpsProperties = buildProperties(node);
2140
2.08k
        std::string ellpsName;
2141
2.08k
        ellpsProperties.getStringValue(IdentifiedObject::NAME_KEY, ellpsName);
2142
2.08k
        const auto celestialBody(Ellipsoid::guessBodyName(
2143
2.08k
            dbContext_, semiMajorAxis.getSIValue(), ellpsName));
2144
2.08k
        if (invFlattening.getSIValue() == 0) {
2145
962
            return Ellipsoid::createSphere(ellpsProperties, semiMajorAxis,
2146
962
                                           celestialBody);
2147
1.12k
        } else {
2148
1.12k
            return Ellipsoid::createFlattenedSphere(
2149
1.12k
                ellpsProperties, semiMajorAxis, invFlattening, celestialBody);
2150
1.12k
        }
2151
2.08k
    } catch (const std::exception &e) {
2152
50
        throw buildRethrow(__FUNCTION__, e);
2153
50
    }
2154
2.08k
}
2155
2156
// ---------------------------------------------------------------------------
2157
2158
PrimeMeridianNNPtr WKTParser::Private::buildPrimeMeridian(
2159
346
    const WKTNodeNNPtr &node, const UnitOfMeasure &defaultAngularUnit) {
2160
346
    const auto *nodeP = node->GP();
2161
346
    const auto &children = nodeP->children();
2162
346
    if (children.size() < 2) {
2163
6
        ThrowNotEnoughChildren(nodeP->value());
2164
6
    }
2165
340
    auto name = stripQuotes(children[0]);
2166
340
    UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR);
2167
340
    if (unit == UnitOfMeasure::NONE) {
2168
340
        unit = defaultAngularUnit;
2169
340
        if (unit == UnitOfMeasure::NONE) {
2170
0
            unit = UnitOfMeasure::DEGREE;
2171
0
        }
2172
340
    }
2173
340
    try {
2174
340
        double angleValue = asDouble(children[1]);
2175
2176
        // Correct for GDAL WKT1 and WKT1-ESRI departure
2177
340
        if (name == "Paris" && std::fabs(angleValue - 2.33722917) < 1e-8 &&
2178
0
            unit._isEquivalentTo(UnitOfMeasure::GRAD,
2179
0
                                 util::IComparable::Criterion::EQUIVALENT)) {
2180
0
            angleValue = 2.5969213;
2181
340
        } else {
2182
340
            static const struct {
2183
340
                const char *name;
2184
340
                int deg;
2185
340
                int min;
2186
340
                double sec;
2187
340
            } primeMeridiansDMS[] = {
2188
340
                {"Lisbon", -9, 7, 54.862},  {"Bogota", -74, 4, 51.3},
2189
340
                {"Madrid", -3, 41, 14.55},  {"Rome", 12, 27, 8.4},
2190
340
                {"Bern", 7, 26, 22.5},      {"Jakarta", 106, 48, 27.79},
2191
340
                {"Ferro", -17, 40, 0},      {"Brussels", 4, 22, 4.71},
2192
340
                {"Stockholm", 18, 3, 29.8}, {"Athens", 23, 42, 58.815},
2193
340
                {"Oslo", 10, 43, 22.5},     {"Paris RGS", 2, 20, 13.95},
2194
340
                {"Paris_RGS", 2, 20, 13.95}};
2195
2196
            // Current epsg.org output may use the EPSG:9110 "sexagesimal DMS"
2197
            // unit and a DD.MMSSsss value, but this will likely be changed to
2198
            // use decimal degree.
2199
            // Or WKT1 may for example use the Paris RGS decimal degree value
2200
            // but with a GEOGCS with UNIT["Grad"]
2201
4.32k
            for (const auto &pmDef : primeMeridiansDMS) {
2202
4.32k
                if (name == pmDef.name) {
2203
3
                    double dmsAsDecimalValue =
2204
3
                        (pmDef.deg >= 0 ? 1 : -1) *
2205
3
                        (std::abs(pmDef.deg) + pmDef.min / 100. +
2206
3
                         pmDef.sec / 10000.);
2207
3
                    double dmsAsDecimalDegreeValue =
2208
3
                        (pmDef.deg >= 0 ? 1 : -1) *
2209
3
                        (std::abs(pmDef.deg) + pmDef.min / 60. +
2210
3
                         pmDef.sec / 3600.);
2211
3
                    if (std::fabs(angleValue - dmsAsDecimalValue) < 1e-8 ||
2212
3
                        std::fabs(angleValue - dmsAsDecimalDegreeValue) <
2213
3
                            1e-8) {
2214
0
                        angleValue = dmsAsDecimalDegreeValue;
2215
0
                        unit = UnitOfMeasure::DEGREE;
2216
0
                    }
2217
3
                    break;
2218
3
                }
2219
4.32k
            }
2220
340
        }
2221
2222
340
        auto &properties = buildProperties(node);
2223
340
        if (dbContext_ && esriStyle_) {
2224
302
            std::string outTableName;
2225
302
            std::string codeFromAlias;
2226
302
            std::string authNameFromAlias;
2227
302
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2228
302
                                                        std::string());
2229
302
            auto officialName = authFactory->getOfficialNameFromAlias(
2230
302
                name, "prime_meridian", "ESRI", false, outTableName,
2231
302
                authNameFromAlias, codeFromAlias);
2232
302
            if (!officialName.empty()) {
2233
0
                properties.set(IdentifiedObject::NAME_KEY, officialName);
2234
0
                if (!authNameFromAlias.empty()) {
2235
0
                    auto identifiers = ArrayOfBaseObject::create();
2236
0
                    identifiers->add(Identifier::create(
2237
0
                        codeFromAlias,
2238
0
                        PropertyMap()
2239
0
                            .set(Identifier::CODESPACE_KEY, authNameFromAlias)
2240
0
                            .set(Identifier::AUTHORITY_KEY,
2241
0
                                 authNameFromAlias)));
2242
0
                    properties.set(IdentifiedObject::IDENTIFIERS_KEY,
2243
0
                                   identifiers);
2244
0
                }
2245
0
            }
2246
302
        }
2247
2248
340
        Angle angle(angleValue, unit);
2249
340
        return PrimeMeridian::create(properties, angle);
2250
340
    } catch (const std::exception &e) {
2251
8
        throw buildRethrow(__FUNCTION__, e);
2252
8
    }
2253
340
}
2254
2255
// ---------------------------------------------------------------------------
2256
2257
1.98k
optional<std::string> WKTParser::Private::getAnchor(const WKTNodeNNPtr &node) {
2258
2259
1.98k
    auto &anchorNode = node->GP()->lookForChild(WKTConstants::ANCHOR);
2260
1.98k
    if (anchorNode->GP()->childrenSize() == 1) {
2261
0
        return optional<std::string>(
2262
0
            stripQuotes(anchorNode->GP()->children()[0]));
2263
0
    }
2264
1.98k
    return optional<std::string>();
2265
1.98k
}
2266
2267
// ---------------------------------------------------------------------------
2268
2269
optional<common::Measure>
2270
1.95k
WKTParser::Private::getAnchorEpoch(const WKTNodeNNPtr &node) {
2271
2272
1.95k
    auto &anchorEpochNode = node->GP()->lookForChild(WKTConstants::ANCHOREPOCH);
2273
1.95k
    if (anchorEpochNode->GP()->childrenSize() == 1) {
2274
0
        try {
2275
0
            double value = asDouble(anchorEpochNode->GP()->children()[0]);
2276
0
            return optional<common::Measure>(
2277
0
                common::Measure(value, common::UnitOfMeasure::YEAR));
2278
0
        } catch (const std::exception &e) {
2279
0
            throw buildRethrow(__FUNCTION__, e);
2280
0
        }
2281
0
    }
2282
1.95k
    return optional<common::Measure>();
2283
1.95k
}
2284
// ---------------------------------------------------------------------------
2285
2286
static const PrimeMeridianNNPtr &
2287
fixupPrimeMeridan(const EllipsoidNNPtr &ellipsoid,
2288
2.02k
                  const PrimeMeridianNNPtr &pm) {
2289
2.02k
    return (ellipsoid->celestialBody() != Ellipsoid::EARTH &&
2290
1.01k
            pm.get() == PrimeMeridian::GREENWICH.get())
2291
2.02k
               ? PrimeMeridian::REFERENCE_MERIDIAN
2292
2.02k
               : pm;
2293
2.02k
}
2294
2295
// ---------------------------------------------------------------------------
2296
2297
GeodeticReferenceFrameNNPtr WKTParser::Private::buildGeodeticReferenceFrame(
2298
    const WKTNodeNNPtr &node, const PrimeMeridianNNPtr &primeMeridian,
2299
1.26k
    const WKTNodeNNPtr &dynamicNode) {
2300
1.26k
    const auto *nodeP = node->GP();
2301
1.26k
    auto &ellipsoidNode =
2302
1.26k
        nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID);
2303
1.26k
    if (isNull(ellipsoidNode)) {
2304
82
        ThrowMissing(WKTConstants::ELLIPSOID);
2305
82
    }
2306
1.17k
    auto &properties = buildProperties(node);
2307
2308
    // do that before buildEllipsoid() so that esriStyle_ can be set
2309
1.17k
    auto name = stripQuotes(nodeP->children()[0]);
2310
2311
1.17k
    const auto identifyFromName = [&](const std::string &l_name) {
2312
152
        if (dbContext_) {
2313
141
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2314
141
                                                        std::string());
2315
141
            auto res = authFactory->createObjectsFromName(
2316
141
                l_name,
2317
141
                {AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, true,
2318
141
                1);
2319
141
            if (!res.empty()) {
2320
88
                bool foundDatumName = false;
2321
88
                const auto &refDatum = res.front();
2322
88
                if (metadata::Identifier::isEquivalentName(
2323
88
                        l_name.c_str(), refDatum->nameStr().c_str())) {
2324
18
                    foundDatumName = true;
2325
70
                } else if (refDatum->identifiers().size() == 1) {
2326
70
                    const auto &id = refDatum->identifiers()[0];
2327
70
                    const auto aliases =
2328
70
                        authFactory->databaseContext()->getAliases(
2329
70
                            *id->codeSpace(), id->code(), refDatum->nameStr(),
2330
70
                            "geodetic_datum", "not EPSG_OLD");
2331
90
                    for (const auto &alias : aliases) {
2332
90
                        if (metadata::Identifier::isEquivalentName(
2333
90
                                l_name.c_str(), alias.c_str())) {
2334
0
                            foundDatumName = true;
2335
0
                            break;
2336
0
                        }
2337
90
                    }
2338
70
                }
2339
88
                if (foundDatumName) {
2340
18
                    properties.set(IdentifiedObject::NAME_KEY,
2341
18
                                   refDatum->nameStr());
2342
18
                    if (!properties.get(Identifier::CODESPACE_KEY) &&
2343
18
                        refDatum->identifiers().size() == 1) {
2344
18
                        const auto &id = refDatum->identifiers()[0];
2345
18
                        auto identifiers = ArrayOfBaseObject::create();
2346
18
                        identifiers->add(Identifier::create(
2347
18
                            id->code(), PropertyMap()
2348
18
                                            .set(Identifier::CODESPACE_KEY,
2349
18
                                                 *id->codeSpace())
2350
18
                                            .set(Identifier::AUTHORITY_KEY,
2351
18
                                                 *id->codeSpace())));
2352
18
                        properties.set(IdentifiedObject::IDENTIFIERS_KEY,
2353
18
                                       identifiers);
2354
18
                    }
2355
18
                    return true;
2356
18
                }
2357
88
            } else {
2358
                // Get official name from database if AUTHORITY is present
2359
53
                auto &idNode = nodeP->lookForChild(WKTConstants::AUTHORITY);
2360
53
                if (!isNull(idNode)) {
2361
2
                    try {
2362
2
                        auto id = buildId(node, idNode, false, false);
2363
2
                        auto authFactory2 = AuthorityFactory::create(
2364
2
                            NN_NO_CHECK(dbContext_), *id->codeSpace());
2365
2
                        auto dbDatum =
2366
2
                            authFactory2->createGeodeticDatum(id->code());
2367
2
                        properties.set(IdentifiedObject::NAME_KEY,
2368
2
                                       dbDatum->nameStr());
2369
2
                        return true;
2370
2
                    } catch (const std::exception &) {
2371
2
                    }
2372
2
                }
2373
53
            }
2374
141
        }
2375
134
        return false;
2376
152
    };
2377
2378
    // Remap GDAL WGS_1984 to EPSG v9 "World Geodetic System 1984" official
2379
    // name.
2380
1.17k
    bool nameSet = false;
2381
1.17k
    if (name == "WGS_1984") {
2382
0
        nameSet = true;
2383
0
        properties.set(IdentifiedObject::NAME_KEY,
2384
0
                       GeodeticReferenceFrame::EPSG_6326->nameStr());
2385
0
    }
2386
    // Also remap EPSG v10 datum ensemble names to non-ensemble EPSG v9
2387
1.17k
    else if (internal::ends_with(name, " ensemble")) {
2388
0
        auto massagedName = DatumEnsemble::ensembleNameToNonEnsembleName(name);
2389
0
        if (!massagedName.empty()) {
2390
0
            nameSet = true;
2391
0
            properties.set(IdentifiedObject::NAME_KEY, massagedName);
2392
0
        }
2393
0
    }
2394
    // If we got hints this might be a ESRI WKT, then check in the DB to
2395
    // confirm
2396
1.17k
    std::string officialName;
2397
1.17k
    std::string authNameFromAlias;
2398
1.17k
    std::string codeFromAlias;
2399
1.17k
    if (!nameSet && maybeEsriStyle_ && dbContext_ &&
2400
918
        !(starts_with(name, "D_") || esriStyle_)) {
2401
609
        std::string outTableName;
2402
609
        auto authFactory =
2403
609
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
2404
609
        officialName = authFactory->getOfficialNameFromAlias(
2405
609
            name, "geodetic_datum", "ESRI", false, outTableName,
2406
609
            authNameFromAlias, codeFromAlias);
2407
609
        if (!officialName.empty()) {
2408
0
            maybeEsriStyle_ = false;
2409
0
            esriStyle_ = true;
2410
0
        }
2411
609
    }
2412
2413
1.17k
    if (!nameSet && (starts_with(name, "D_") || esriStyle_)) {
2414
356
        esriStyle_ = true;
2415
356
        const char *tableNameForAlias = nullptr;
2416
356
        if (name == "D_WGS_1984") {
2417
148
            name = "World Geodetic System 1984";
2418
148
            authNameFromAlias = Identifier::EPSG;
2419
148
            codeFromAlias = "6326";
2420
208
        } else if (name == "D_ETRS_1989") {
2421
0
            name = "European Terrestrial Reference System 1989";
2422
0
            authNameFromAlias = Identifier::EPSG;
2423
0
            codeFromAlias = "6258";
2424
208
        } else if (name == "D_unknown") {
2425
0
            name = "unknown";
2426
208
        } else if (name == "D_Unknown_based_on_WGS_84_ellipsoid") {
2427
0
            name = "Unknown based on WGS 84 ellipsoid";
2428
208
        } else {
2429
208
            tableNameForAlias = "geodetic_datum";
2430
208
        }
2431
2432
356
        bool setNameAndId = true;
2433
356
        if (dbContext_ && tableNameForAlias) {
2434
199
            if (officialName.empty()) {
2435
199
                std::string outTableName;
2436
199
                auto authFactory = AuthorityFactory::create(
2437
199
                    NN_NO_CHECK(dbContext_), std::string());
2438
199
                officialName = authFactory->getOfficialNameFromAlias(
2439
199
                    name, tableNameForAlias, "ESRI", false, outTableName,
2440
199
                    authNameFromAlias, codeFromAlias);
2441
199
            }
2442
199
            if (officialName.empty()) {
2443
76
                if (starts_with(name, "D_")) {
2444
                    // For the case of "D_GDA2020" where there is no D_GDA2020
2445
                    // ESRI alias, so just try without the D_ prefix.
2446
37
                    const auto nameWithoutDPrefix = name.substr(2);
2447
37
                    if (identifyFromName(nameWithoutDPrefix)) {
2448
0
                        setNameAndId =
2449
0
                            false; // already done in identifyFromName()
2450
0
                    }
2451
37
                }
2452
123
            } else {
2453
123
                if (primeMeridian->nameStr() !=
2454
123
                    PrimeMeridian::GREENWICH->nameStr()) {
2455
0
                    auto nameWithPM =
2456
0
                        officialName + " (" + primeMeridian->nameStr() + ")";
2457
0
                    if (dbContext_->isKnownName(nameWithPM, "geodetic_datum")) {
2458
0
                        officialName = std::move(nameWithPM);
2459
0
                    }
2460
0
                }
2461
123
                name = std::move(officialName);
2462
123
            }
2463
199
        }
2464
2465
356
        if (setNameAndId) {
2466
356
            properties.set(IdentifiedObject::NAME_KEY, name);
2467
356
            if (!authNameFromAlias.empty()) {
2468
271
                auto identifiers = ArrayOfBaseObject::create();
2469
271
                identifiers->add(Identifier::create(
2470
271
                    codeFromAlias,
2471
271
                    PropertyMap()
2472
271
                        .set(Identifier::CODESPACE_KEY, authNameFromAlias)
2473
271
                        .set(Identifier::AUTHORITY_KEY, authNameFromAlias)));
2474
271
                properties.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
2475
271
            }
2476
356
        }
2477
822
    } else if (!nameSet && name.find('_') != std::string::npos) {
2478
        // Likely coming from WKT1
2479
115
        identifyFromName(name);
2480
115
    }
2481
2482
1.17k
    auto ellipsoid = buildEllipsoid(ellipsoidNode);
2483
1.17k
    const auto &primeMeridianModified =
2484
1.17k
        fixupPrimeMeridan(ellipsoid, primeMeridian);
2485
2486
1.17k
    auto &TOWGS84Node = nodeP->lookForChild(WKTConstants::TOWGS84);
2487
1.17k
    if (!isNull(TOWGS84Node)) {
2488
2
        const auto &TOWGS84Children = TOWGS84Node->GP()->children();
2489
2
        const size_t TOWGS84Size = TOWGS84Children.size();
2490
2
        if (TOWGS84Size == 3 || TOWGS84Size == 7) {
2491
1
            try {
2492
2
                for (const auto &child : TOWGS84Children) {
2493
2
                    toWGS84Parameters_.push_back(asDouble(child));
2494
2
                }
2495
2496
1
                if (TOWGS84Size == 7 && dbContext_) {
2497
0
                    dbContext_->toWGS84AutocorrectWrongValues(
2498
0
                        toWGS84Parameters_[0], toWGS84Parameters_[1],
2499
0
                        toWGS84Parameters_[2], toWGS84Parameters_[3],
2500
0
                        toWGS84Parameters_[4], toWGS84Parameters_[5],
2501
0
                        toWGS84Parameters_[6]);
2502
0
                }
2503
2504
1
                for (size_t i = TOWGS84Size; i < 7; ++i) {
2505
0
                    toWGS84Parameters_.push_back(0.0);
2506
0
                }
2507
1
            } catch (const std::exception &) {
2508
1
                throw ParsingException("Invalid TOWGS84 node");
2509
1
            }
2510
1
        } else {
2511
1
            throw ParsingException("Invalid TOWGS84 node");
2512
1
        }
2513
2
    }
2514
2515
1.17k
    auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
2516
1.17k
    const auto &extensionChildren = extensionNode->GP()->children();
2517
1.17k
    if (extensionChildren.size() == 2) {
2518
4
        if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) {
2519
0
            datumPROJ4Grids_ = stripQuotes(extensionChildren[1]);
2520
0
        }
2521
4
    }
2522
2523
1.17k
    if (!isNull(dynamicNode)) {
2524
36
        double frameReferenceEpoch = 0.0;
2525
36
        util::optional<std::string> modelName;
2526
36
        parseDynamic(dynamicNode, frameReferenceEpoch, modelName);
2527
36
        return DynamicGeodeticReferenceFrame::create(
2528
36
            properties, ellipsoid, getAnchor(node), primeMeridianModified,
2529
36
            common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR),
2530
36
            modelName);
2531
36
    }
2532
2533
1.14k
    return GeodeticReferenceFrame::create(properties, ellipsoid,
2534
1.14k
                                          getAnchor(node), getAnchorEpoch(node),
2535
1.14k
                                          primeMeridianModified);
2536
1.17k
}
2537
2538
// ---------------------------------------------------------------------------
2539
2540
DatumEnsembleNNPtr
2541
WKTParser::Private::buildDatumEnsemble(const WKTNodeNNPtr &node,
2542
                                       const PrimeMeridianPtr &primeMeridian,
2543
230
                                       bool expectEllipsoid) {
2544
230
    const auto *nodeP = node->GP();
2545
230
    auto &ellipsoidNode =
2546
230
        nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID);
2547
230
    if (expectEllipsoid && isNull(ellipsoidNode)) {
2548
14
        ThrowMissing(WKTConstants::ELLIPSOID);
2549
14
    }
2550
2551
216
    auto properties = buildProperties(node);
2552
2553
216
    std::vector<DatumNNPtr> datums;
2554
3.76k
    for (const auto &subNode : nodeP->children()) {
2555
3.76k
        if (ci_equal(subNode->GP()->value(), WKTConstants::MEMBER)) {
2556
1.08k
            if (subNode->GP()->childrenSize() == 0) {
2557
8
                throw ParsingException("Invalid MEMBER node");
2558
8
            }
2559
1.07k
            if (expectEllipsoid) {
2560
914
                datums.emplace_back(GeodeticReferenceFrame::create(
2561
914
                    buildProperties(subNode), buildEllipsoid(ellipsoidNode),
2562
914
                    optional<std::string>(),
2563
914
                    primeMeridian ? NN_NO_CHECK(primeMeridian)
2564
914
                                  : PrimeMeridian::GREENWICH));
2565
914
            } else {
2566
159
                datums.emplace_back(
2567
159
                    VerticalReferenceFrame::create(buildProperties(subNode)));
2568
159
            }
2569
1.07k
        }
2570
3.76k
    }
2571
2572
208
    if (datums.empty() && !nodeP->children().empty()) {
2573
10
        auto name = stripQuotes(nodeP->children()[0]);
2574
10
        if (dbContext_) {
2575
7
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2576
7
                                                        std::string());
2577
7
            auto res = authFactory->createObjectsFromName(
2578
7
                name, {AuthorityFactory::ObjectType::DATUM_ENSEMBLE}, true, 1);
2579
7
            if (res.size() == 1) {
2580
1
                auto datumEnsemble =
2581
1
                    dynamic_cast<const DatumEnsemble *>(res.front().get());
2582
1
                if (datumEnsemble) {
2583
1
                    datums = datumEnsemble->datums();
2584
1
                }
2585
6
            } else {
2586
6
                throw ParsingException(
2587
6
                    "No entry for datum ensemble '" + name +
2588
6
                    "' in database, and no explicit member specified");
2589
6
            }
2590
7
        } else {
2591
3
            throw ParsingException("Datum ensemble '" + name +
2592
3
                                   "' has no explicit member specified and no "
2593
3
                                   "connection to database");
2594
3
        }
2595
10
    }
2596
2597
199
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::ENSEMBLEACCURACY);
2598
199
    auto &accuracyNodeChildren = accuracyNode->GP()->children();
2599
199
    if (accuracyNodeChildren.empty()) {
2600
135
        ThrowMissing(WKTConstants::ENSEMBLEACCURACY);
2601
135
    }
2602
64
    auto accuracy =
2603
64
        PositionalAccuracy::create(accuracyNodeChildren[0]->GP()->value());
2604
2605
64
    try {
2606
64
        return DatumEnsemble::create(properties, datums, accuracy);
2607
64
    } catch (const util::Exception &e) {
2608
3
        throw buildRethrow(__FUNCTION__, e);
2609
3
    }
2610
64
}
2611
2612
// ---------------------------------------------------------------------------
2613
2614
0
MeridianNNPtr WKTParser::Private::buildMeridian(const WKTNodeNNPtr &node) {
2615
0
    const auto *nodeP = node->GP();
2616
0
    const auto &children = nodeP->children();
2617
0
    if (children.size() < 2) {
2618
0
        ThrowNotEnoughChildren(nodeP->value());
2619
0
    }
2620
0
    UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR);
2621
0
    try {
2622
0
        double angleValue = asDouble(children[0]);
2623
0
        Angle angle(angleValue, unit);
2624
0
        return Meridian::create(angle);
2625
0
    } catch (const std::exception &e) {
2626
0
        throw buildRethrow(__FUNCTION__, e);
2627
0
    }
2628
0
}
2629
2630
// ---------------------------------------------------------------------------
2631
2632
9
PROJ_NO_RETURN static void ThrowParsingExceptionMissingUNIT() {
2633
9
    throw ParsingException("buildCS: missing UNIT");
2634
9
}
2635
2636
// ---------------------------------------------------------------------------
2637
2638
CoordinateSystemAxisNNPtr
2639
WKTParser::Private::buildAxis(const WKTNodeNNPtr &node,
2640
                              const UnitOfMeasure &unitIn,
2641
                              const UnitOfMeasure::Type &unitType,
2642
230
                              bool isGeocentric, int expectedOrderNum) {
2643
230
    const auto *nodeP = node->GP();
2644
230
    const auto &children = nodeP->children();
2645
230
    if (children.size() < 2) {
2646
10
        ThrowNotEnoughChildren(nodeP->value());
2647
10
    }
2648
2649
220
    auto &orderNode = nodeP->lookForChild(WKTConstants::ORDER);
2650
220
    if (!isNull(orderNode)) {
2651
10
        const auto &orderNodeChildren = orderNode->GP()->children();
2652
10
        if (orderNodeChildren.size() != 1) {
2653
0
            ThrowNotEnoughChildren(WKTConstants::ORDER);
2654
0
        }
2655
10
        const auto &order = orderNodeChildren[0]->GP()->value();
2656
10
        int orderNum;
2657
10
        try {
2658
10
            orderNum = std::stoi(order);
2659
10
        } catch (const std::exception &) {
2660
3
            throw ParsingException(
2661
3
                concat("buildAxis: invalid ORDER value: ", order));
2662
3
        }
2663
7
        if (orderNum != expectedOrderNum) {
2664
4
            throw ParsingException(
2665
4
                concat("buildAxis: did not get expected ORDER value: ", order));
2666
4
        }
2667
7
    }
2668
2669
    // The axis designation in WK2 can be: "name", "(abbrev)" or "name
2670
    // (abbrev)"
2671
213
    std::string axisDesignation(stripQuotes(children[0]));
2672
213
    size_t sepPos = axisDesignation.find(" (");
2673
213
    std::string axisName;
2674
213
    std::string abbreviation;
2675
213
    if (sepPos != std::string::npos && axisDesignation.back() == ')') {
2676
9
        axisName = CoordinateSystemAxis::normalizeAxisName(
2677
9
            axisDesignation.substr(0, sepPos));
2678
9
        abbreviation = axisDesignation.substr(sepPos + 2);
2679
9
        abbreviation.resize(abbreviation.size() - 1);
2680
204
    } else if (!axisDesignation.empty() && axisDesignation[0] == '(' &&
2681
35
               axisDesignation.back() == ')') {
2682
17
        abbreviation = axisDesignation.substr(1, axisDesignation.size() - 2);
2683
17
        if (abbreviation == AxisAbbreviation::E) {
2684
3
            axisName = AxisName::Easting;
2685
14
        } else if (abbreviation == AxisAbbreviation::N) {
2686
0
            axisName = AxisName::Northing;
2687
14
        } else if (abbreviation == AxisAbbreviation::lat) {
2688
0
            axisName = AxisName::Latitude;
2689
14
        } else if (abbreviation == AxisAbbreviation::lon) {
2690
0
            axisName = AxisName::Longitude;
2691
0
        }
2692
187
    } else {
2693
187
        axisName = CoordinateSystemAxis::normalizeAxisName(axisDesignation);
2694
187
        if (axisName == AxisName::Latitude) {
2695
0
            abbreviation = AxisAbbreviation::lat;
2696
187
        } else if (axisName == AxisName::Longitude) {
2697
0
            abbreviation = AxisAbbreviation::lon;
2698
187
        } else if (axisName == AxisName::Ellipsoidal_height) {
2699
0
            abbreviation = AxisAbbreviation::h;
2700
0
        }
2701
187
    }
2702
213
    const std::string &dirString = children[1]->GP()->value();
2703
213
    const AxisDirection *direction = AxisDirection::valueOf(dirString);
2704
2705
    // WKT2, geocentric CS: axis names are omitted
2706
213
    if (axisName.empty()) {
2707
16
        if (direction == &AxisDirection::GEOCENTRIC_X &&
2708
0
            abbreviation == AxisAbbreviation::X) {
2709
0
            axisName = AxisName::Geocentric_X;
2710
16
        } else if (direction == &AxisDirection::GEOCENTRIC_Y &&
2711
5
                   abbreviation == AxisAbbreviation::Y) {
2712
0
            axisName = AxisName::Geocentric_Y;
2713
16
        } else if (direction == &AxisDirection::GEOCENTRIC_Z &&
2714
0
                   abbreviation == AxisAbbreviation::Z) {
2715
0
            axisName = AxisName::Geocentric_Z;
2716
0
        }
2717
16
    }
2718
2719
    // WKT1
2720
213
    if (!direction && isGeocentric && axisName == AxisName::Geocentric_X) {
2721
21
        abbreviation = AxisAbbreviation::X;
2722
21
        direction = &AxisDirection::GEOCENTRIC_X;
2723
192
    } else if (!direction && isGeocentric &&
2724
18
               axisName == AxisName::Geocentric_Y) {
2725
0
        abbreviation = AxisAbbreviation::Y;
2726
0
        direction = &AxisDirection::GEOCENTRIC_Y;
2727
192
    } else if (isGeocentric && axisName == AxisName::Geocentric_Z &&
2728
6
               (dirString == AxisDirectionWKT1::NORTH.toString() ||
2729
6
                dirString == AxisDirectionWKT1::OTHER.toString())) {
2730
0
        abbreviation = AxisAbbreviation::Z;
2731
0
        direction = &AxisDirection::GEOCENTRIC_Z;
2732
192
    } else if (dirString == AxisDirectionWKT1::OTHER.toString()) {
2733
0
        direction = &AxisDirection::UNSPECIFIED;
2734
192
    } else if (dirString == "UNKNOWN") {
2735
        // Found in WKT1 of NSIDC's EASE-Grid Sea Ice Age datasets.
2736
        // Cf https://github.com/OSGeo/gdal/issues/7210
2737
0
        emitRecoverableWarning("UNKNOWN is not a valid direction name.");
2738
0
        direction = &AxisDirection::UNSPECIFIED;
2739
0
    }
2740
2741
    // Set common axis abbreviations for WKT1
2742
213
    if (abbreviation.empty()) {
2743
166
        if (axisName == AxisName::Easting)
2744
0
            abbreviation = AxisAbbreviation::E;
2745
166
        else if (axisName == AxisName::Northing)
2746
0
            abbreviation = AxisAbbreviation::N;
2747
166
    }
2748
2749
213
    if (!direction) {
2750
90
        throw ParsingException(
2751
90
            concat("unhandled axis direction: ", children[1]->GP()->value()));
2752
90
    }
2753
123
    UnitOfMeasure unit(buildUnitInSubNode(node));
2754
123
    if (unit == UnitOfMeasure::NONE) {
2755
        // If no unit in the AXIS node, use the one potentially coming from
2756
        // the CS.
2757
110
        unit = unitIn;
2758
110
        if (unit == UnitOfMeasure::NONE &&
2759
9
            unitType != UnitOfMeasure::Type::NONE &&
2760
9
            unitType != UnitOfMeasure::Type::TIME) {
2761
9
            ThrowParsingExceptionMissingUNIT();
2762
9
        }
2763
110
    }
2764
2765
114
    auto &meridianNode = nodeP->lookForChild(WKTConstants::MERIDIAN);
2766
2767
114
    util::optional<double> minVal;
2768
114
    auto &axisMinValueNode = nodeP->lookForChild(WKTConstants::AXISMINVALUE);
2769
114
    if (!isNull(axisMinValueNode)) {
2770
0
        const auto &axisMinValueNodeChildren =
2771
0
            axisMinValueNode->GP()->children();
2772
0
        if (axisMinValueNodeChildren.size() != 1) {
2773
0
            ThrowNotEnoughChildren(WKTConstants::AXISMINVALUE);
2774
0
        }
2775
0
        const auto &val = axisMinValueNodeChildren[0];
2776
0
        try {
2777
0
            minVal = asDouble(val);
2778
0
        } catch (const std::exception &) {
2779
0
            throw ParsingException(concat(
2780
0
                "buildAxis: invalid AXISMINVALUE value: ", val->GP()->value()));
2781
0
        }
2782
0
    }
2783
2784
114
    util::optional<double> maxVal;
2785
114
    auto &axisMaxValueNode = nodeP->lookForChild(WKTConstants::AXISMAXVALUE);
2786
114
    if (!isNull(axisMaxValueNode)) {
2787
0
        const auto &axisMaxValueNodeChildren =
2788
0
            axisMaxValueNode->GP()->children();
2789
0
        if (axisMaxValueNodeChildren.size() != 1) {
2790
0
            ThrowNotEnoughChildren(WKTConstants::AXISMAXVALUE);
2791
0
        }
2792
0
        const auto &val = axisMaxValueNodeChildren[0];
2793
0
        try {
2794
0
            maxVal = asDouble(val);
2795
0
        } catch (const std::exception &) {
2796
0
            throw ParsingException(concat(
2797
0
                "buildAxis: invalid AXISMAXVALUE value: ", val->GP()->value()));
2798
0
        }
2799
0
    }
2800
2801
114
    util::optional<RangeMeaning> rangeMeaning;
2802
114
    auto &rangeMeaningNode = nodeP->lookForChild(WKTConstants::RANGEMEANING);
2803
114
    if (!isNull(rangeMeaningNode)) {
2804
10
        const auto &rangeMeaningNodeChildren =
2805
10
            rangeMeaningNode->GP()->children();
2806
10
        if (rangeMeaningNodeChildren.size() != 1) {
2807
1
            ThrowNotEnoughChildren(WKTConstants::RANGEMEANING);
2808
1
        }
2809
9
        const std::string &val = rangeMeaningNodeChildren[0]->GP()->value();
2810
9
        const RangeMeaning *meaning = RangeMeaning::valueOf(val);
2811
9
        if (meaning == nullptr) {
2812
9
            throw ParsingException(
2813
9
                concat("buildAxis: invalid RANGEMEANING value: ", val));
2814
9
        }
2815
0
        rangeMeaning = util::optional<RangeMeaning>(*meaning);
2816
0
    }
2817
2818
104
    return CoordinateSystemAxis::create(
2819
104
        buildProperties(node).set(IdentifiedObject::NAME_KEY, axisName),
2820
104
        abbreviation, *direction, unit, minVal, maxVal, rangeMeaning,
2821
104
        !isNull(meridianNode) ? buildMeridian(meridianNode).as_nullable()
2822
104
                              : nullptr);
2823
114
}
2824
2825
// ---------------------------------------------------------------------------
2826
2827
static const PropertyMap emptyPropertyMap{};
2828
2829
// ---------------------------------------------------------------------------
2830
2831
3
PROJ_NO_RETURN static void ThrowParsingException(const std::string &msg) {
2832
3
    throw ParsingException(msg);
2833
3
}
2834
2835
// ---------------------------------------------------------------------------
2836
2837
static ParsingException
2838
12
buildParsingExceptionInvalidAxisCount(const std::string &csType) {
2839
12
    return ParsingException(
2840
12
        concat("buildCS: invalid CS axis count for ", csType));
2841
12
}
2842
2843
// ---------------------------------------------------------------------------
2844
2845
void WKTParser::Private::emitRecoverableMissingUNIT(
2846
1.57k
    const std::string &parentNodeName, const UnitOfMeasure &fallbackUnit) {
2847
1.57k
    std::string msg("buildCS: missing UNIT in ");
2848
1.57k
    msg += parentNodeName;
2849
1.57k
    if (!strict_ && fallbackUnit == UnitOfMeasure::METRE) {
2850
1.04k
        msg += ". Assuming metre";
2851
1.04k
    } else if (!strict_ && fallbackUnit == UnitOfMeasure::DEGREE) {
2852
521
        msg += ". Assuming degree";
2853
521
    }
2854
1.57k
    emitRecoverableWarning(msg);
2855
1.57k
}
2856
2857
// ---------------------------------------------------------------------------
2858
2859
CoordinateSystemNNPtr
2860
WKTParser::Private::buildCS(const WKTNodeNNPtr &node, /* maybe null */
2861
                            const WKTNodeNNPtr &parentNode,
2862
2.96k
                            const UnitOfMeasure &defaultAngularUnit) {
2863
2.96k
    bool isGeocentric = false;
2864
2.96k
    std::string csType;
2865
2.96k
    const int numberOfAxis =
2866
2.96k
        parentNode->countChildrenOfName(WKTConstants::AXIS);
2867
2.96k
    int axisCount = numberOfAxis;
2868
2.96k
    const auto &parentNodeName = parentNode->GP()->value();
2869
2.96k
    if (!isNull(node)) {
2870
20
        const auto *nodeP = node->GP();
2871
20
        const auto &children = nodeP->children();
2872
20
        if (children.size() < 2) {
2873
6
            ThrowNotEnoughChildren(nodeP->value());
2874
6
        }
2875
14
        csType = children[0]->GP()->value();
2876
14
        try {
2877
14
            axisCount = std::stoi(children[1]->GP()->value());
2878
14
        } catch (const std::exception &) {
2879
3
            ThrowParsingException(concat("buildCS: invalid CS axis count: ",
2880
3
                                         children[1]->GP()->value()));
2881
3
        }
2882
2.94k
    } else {
2883
2.94k
        const char *csTypeCStr = CartesianCS::WKT2_TYPE;
2884
2.94k
        if (ci_equal(parentNodeName, WKTConstants::GEOCCS)) {
2885
            // csTypeCStr = CartesianCS::WKT2_TYPE;
2886
80
            isGeocentric = true;
2887
80
            if (axisCount == 0) {
2888
42
                auto unit =
2889
42
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2890
42
                if (unit == UnitOfMeasure::NONE) {
2891
34
                    unit = UnitOfMeasure::METRE;
2892
34
                    emitRecoverableMissingUNIT(parentNodeName, unit);
2893
34
                }
2894
42
                return CartesianCS::createGeocentric(unit);
2895
42
            }
2896
2.86k
        } else if (ci_equal(parentNodeName, WKTConstants::GEOGCS)) {
2897
1.03k
            csTypeCStr = EllipsoidalCS::WKT2_TYPE;
2898
1.03k
            if (axisCount == 0) {
2899
                // Missing axis with GEOGCS ? Presumably Long/Lat order
2900
                // implied
2901
1.03k
                auto unit = buildUnitInSubNode(parentNode,
2902
1.03k
                                               UnitOfMeasure::Type::ANGULAR);
2903
1.03k
                if (unit == UnitOfMeasure::NONE) {
2904
521
                    unit = defaultAngularUnit;
2905
521
                    emitRecoverableMissingUNIT(parentNodeName, unit);
2906
521
                }
2907
2908
                // ESRI WKT for geographic 3D CRS
2909
1.03k
                auto &linUnitNode =
2910
1.03k
                    parentNode->GP()->lookForChild(WKTConstants::LINUNIT);
2911
1.03k
                if (!isNull(linUnitNode)) {
2912
0
                    return EllipsoidalCS::
2913
0
                        createLongitudeLatitudeEllipsoidalHeight(
2914
0
                            unit, buildUnit(linUnitNode,
2915
0
                                            UnitOfMeasure::Type::LINEAR));
2916
0
                }
2917
2918
                // WKT1 --> long/lat
2919
1.03k
                return EllipsoidalCS::createLongitudeLatitude(unit);
2920
1.03k
            }
2921
1.82k
        } else if (ci_equal(parentNodeName, WKTConstants::BASEGEODCRS) ||
2922
1.82k
                   ci_equal(parentNodeName, WKTConstants::BASEGEOGCRS)) {
2923
0
            csTypeCStr = EllipsoidalCS::WKT2_TYPE;
2924
0
            if (axisCount == 0) {
2925
0
                auto unit = buildUnitInSubNode(parentNode,
2926
0
                                               UnitOfMeasure::Type::ANGULAR);
2927
0
                if (unit == UnitOfMeasure::NONE) {
2928
0
                    unit = defaultAngularUnit;
2929
0
                }
2930
                // WKT2 --> presumably lat/long
2931
0
                return EllipsoidalCS::createLatitudeLongitude(unit);
2932
0
            }
2933
1.82k
        } else if (ci_equal(parentNodeName, WKTConstants::PROJCS) ||
2934
1.08k
                   ci_equal(parentNodeName, WKTConstants::BASEPROJCRS) ||
2935
1.08k
                   ci_equal(parentNodeName, WKTConstants::BASEENGCRS)) {
2936
742
            csTypeCStr = CartesianCS::WKT2_TYPE;
2937
742
            if (axisCount == 0) {
2938
739
                auto unit =
2939
739
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2940
739
                if (unit == UnitOfMeasure::NONE) {
2941
414
                    unit = UnitOfMeasure::METRE;
2942
414
                    if (ci_equal(parentNodeName, WKTConstants::PROJCS)) {
2943
414
                        emitRecoverableMissingUNIT(parentNodeName, unit);
2944
414
                    }
2945
414
                }
2946
739
                return CartesianCS::createEastingNorthing(unit);
2947
739
            }
2948
1.08k
        } else if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
2949
1.06k
                   ci_equal(parentNodeName, WKTConstants::VERTCS) ||
2950
704
                   ci_equal(parentNodeName, WKTConstants::BASEVERTCRS)) {
2951
704
            csTypeCStr = VerticalCS::WKT2_TYPE;
2952
2953
704
            bool downDirection = false;
2954
704
            if (ci_equal(parentNodeName, WKTConstants::VERTCS)) // ESRI
2955
684
            {
2956
3.18k
                for (const auto &childNode : parentNode->GP()->children()) {
2957
3.18k
                    const auto &childNodeChildren = childNode->GP()->children();
2958
3.18k
                    if (childNodeChildren.size() == 2 &&
2959
323
                        ci_equal(childNode->GP()->value(),
2960
323
                                 WKTConstants::PARAMETER) &&
2961
20
                        childNodeChildren[0]->GP()->value() ==
2962
20
                            "\"Direction\"") {
2963
0
                        const auto &paramValue =
2964
0
                            childNodeChildren[1]->GP()->value();
2965
0
                        try {
2966
0
                            double val = asDouble(childNodeChildren[1]);
2967
0
                            if (val == 1.0) {
2968
                                // ok
2969
0
                            } else if (val == -1.0) {
2970
0
                                downDirection = true;
2971
0
                            }
2972
0
                        } catch (const std::exception &) {
2973
0
                            throw ParsingException(
2974
0
                                concat("unhandled parameter value type : ",
2975
0
                                       paramValue));
2976
0
                        }
2977
0
                    }
2978
3.18k
                }
2979
684
            }
2980
2981
704
            if (axisCount == 0) {
2982
588
                auto unit =
2983
588
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2984
588
                if (unit == UnitOfMeasure::NONE) {
2985
485
                    unit = UnitOfMeasure::METRE;
2986
485
                    if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
2987
485
                        ci_equal(parentNodeName, WKTConstants::VERTCS)) {
2988
485
                        emitRecoverableMissingUNIT(parentNodeName, unit);
2989
485
                    }
2990
485
                }
2991
588
                if (downDirection) {
2992
0
                    return VerticalCS::create(
2993
0
                        util::PropertyMap(),
2994
0
                        CoordinateSystemAxis::create(
2995
0
                            util::PropertyMap().set(IdentifiedObject::NAME_KEY,
2996
0
                                                    "depth"),
2997
0
                            "D", AxisDirection::DOWN, unit));
2998
0
                }
2999
588
                return VerticalCS::createGravityRelatedHeight(unit);
3000
588
            }
3001
704
        } else if (ci_equal(parentNodeName, WKTConstants::LOCAL_CS)) {
3002
381
            if (axisCount == 0) {
3003
308
                auto unit =
3004
308
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
3005
308
                if (unit == UnitOfMeasure::NONE) {
3006
307
                    unit = UnitOfMeasure::METRE;
3007
307
                }
3008
308
                return CartesianCS::createEastingNorthing(unit);
3009
308
            } else if (axisCount == 1) {
3010
66
                csTypeCStr = VerticalCS::WKT2_TYPE;
3011
66
            } else if (axisCount == 2 || axisCount == 3) {
3012
5
                csTypeCStr = CartesianCS::WKT2_TYPE;
3013
5
            } else {
3014
2
                throw ParsingException(
3015
2
                    "buildCS: unexpected AXIS count for LOCAL_CS");
3016
2
            }
3017
381
        } else if (ci_equal(parentNodeName, WKTConstants::BASEPARAMCRS)) {
3018
0
            csTypeCStr = ParametricCS::WKT2_TYPE;
3019
0
            if (axisCount == 0) {
3020
0
                auto unit =
3021
0
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
3022
0
                if (unit == UnitOfMeasure::NONE) {
3023
0
                    unit = UnitOfMeasure("unknown", 1,
3024
0
                                         UnitOfMeasure::Type::PARAMETRIC);
3025
0
                }
3026
0
                return ParametricCS::create(
3027
0
                    emptyPropertyMap,
3028
0
                    CoordinateSystemAxis::create(
3029
0
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
3030
0
                                          "unknown parametric"),
3031
0
                        std::string(), AxisDirection::UNSPECIFIED, unit));
3032
0
            }
3033
0
        } else if (ci_equal(parentNodeName, WKTConstants::BASETIMECRS)) {
3034
0
            csTypeCStr = TemporalCS::WKT2_2015_TYPE;
3035
0
            if (axisCount == 0) {
3036
0
                auto unit =
3037
0
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::TIME);
3038
0
                if (unit == UnitOfMeasure::NONE) {
3039
0
                    unit =
3040
0
                        UnitOfMeasure("unknown", 1, UnitOfMeasure::Type::TIME);
3041
0
                }
3042
0
                return DateTimeTemporalCS::create(
3043
0
                    emptyPropertyMap,
3044
0
                    CoordinateSystemAxis::create(
3045
0
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
3046
0
                                          "unknown temporal"),
3047
0
                        std::string(), AxisDirection::FUTURE, unit));
3048
0
            }
3049
0
        } else {
3050
            // Shouldn't happen normally
3051
0
            throw ParsingException(
3052
0
                concat("buildCS: unexpected parent node: ", parentNodeName));
3053
0
        }
3054
231
        csType = csTypeCStr;
3055
231
    }
3056
3057
242
    if (axisCount != 1 && axisCount != 2 && axisCount != 3) {
3058
8
        throw buildParsingExceptionInvalidAxisCount(csType);
3059
8
    }
3060
234
    if (numberOfAxis != axisCount) {
3061
3
        throw ParsingException("buildCS: declared number of axis by CS node "
3062
3
                               "and number of AXIS are inconsistent");
3063
3
    }
3064
3065
231
    const auto unitType =
3066
231
        ci_equal(csType, EllipsoidalCS::WKT2_TYPE)
3067
231
            ? UnitOfMeasure::Type::ANGULAR
3068
231
        : ci_equal(csType, OrdinalCS::WKT2_TYPE) ? UnitOfMeasure::Type::NONE
3069
228
        : ci_equal(csType, ParametricCS::WKT2_TYPE)
3070
228
            ? UnitOfMeasure::Type::PARAMETRIC
3071
228
        : ci_equal(csType, CartesianCS::WKT2_TYPE) ||
3072
182
                ci_equal(csType, VerticalCS::WKT2_TYPE) ||
3073
0
                ci_equal(csType, AffineCS::WKT2_TYPE)
3074
228
            ? UnitOfMeasure::Type::LINEAR
3075
228
        : (ci_equal(csType, TemporalCS::WKT2_2015_TYPE) ||
3076
0
           ci_equal(csType, DateTimeTemporalCS::WKT2_2019_TYPE) ||
3077
0
           ci_equal(csType, TemporalCountCS::WKT2_2019_TYPE) ||
3078
0
           ci_equal(csType, TemporalMeasureCS::WKT2_2019_TYPE))
3079
0
            ? UnitOfMeasure::Type::TIME
3080
0
            : UnitOfMeasure::Type::UNKNOWN;
3081
231
    UnitOfMeasure unit = buildUnitInSubNode(parentNode, unitType);
3082
3083
231
    if (unit == UnitOfMeasure::NONE) {
3084
221
        if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
3085
220
            ci_equal(parentNodeName, WKTConstants::VERTCS)) {
3086
116
            unit = UnitOfMeasure::METRE;
3087
116
            emitRecoverableMissingUNIT(parentNodeName, unit);
3088
116
        }
3089
221
    }
3090
3091
231
    std::vector<CoordinateSystemAxisNNPtr> axisList;
3092
461
    for (int i = 0; i < axisCount; i++) {
3093
230
        axisList.emplace_back(
3094
230
            buildAxis(parentNode->GP()->lookForChild(WKTConstants::AXIS, i),
3095
230
                      unit, unitType, isGeocentric, i + 1));
3096
230
    }
3097
3098
231
    const PropertyMap &csMap = emptyPropertyMap;
3099
231
    if (ci_equal(csType, EllipsoidalCS::WKT2_TYPE)) {
3100
0
        if (axisCount == 2) {
3101
0
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1]);
3102
0
        } else if (axisCount == 3) {
3103
0
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1],
3104
0
                                         axisList[2]);
3105
0
        }
3106
231
    } else if (ci_equal(csType, CartesianCS::WKT2_TYPE)) {
3107
5
        if (axisCount == 2) {
3108
1
            if (axisList[0]->unit() != axisList[1]->unit()) {
3109
0
                emitRecoverableWarning(
3110
0
                    "All axis of a CartesianCS must have the same unit");
3111
0
            }
3112
1
            return CartesianCS::create(csMap, axisList[0], axisList[1],
3113
1
                                       /* enforceSameUnit = */ false);
3114
4
        } else if (axisCount == 3) {
3115
0
            if (axisList[0]->unit() != axisList[1]->unit() ||
3116
0
                axisList[0]->unit() != axisList[2]->unit()) {
3117
0
                emitRecoverableWarning(
3118
0
                    "All axis of a CartesianCS must have the same unit");
3119
0
            }
3120
0
            return CartesianCS::create(csMap, axisList[0], axisList[1],
3121
0
                                       axisList[2],
3122
0
                                       /* enforceSameUnit = */ false);
3123
0
        }
3124
226
    } else if (ci_equal(csType, AffineCS::WKT2_TYPE)) {
3125
0
        if (axisCount == 2) {
3126
0
            return AffineCS::create(csMap, axisList[0], axisList[1]);
3127
0
        } else if (axisCount == 3) {
3128
0
            return AffineCS::create(csMap, axisList[0], axisList[1],
3129
0
                                    axisList[2]);
3130
0
        }
3131
226
    } else if (ci_equal(csType, VerticalCS::WKT2_TYPE)) {
3132
95
        if (axisCount == 1) {
3133
95
            return VerticalCS::create(csMap, axisList[0]);
3134
95
        }
3135
131
    } else if (ci_equal(csType, SphericalCS::WKT2_TYPE)) {
3136
0
        if (axisCount == 2) {
3137
            // Extension to ISO19111 to support (planet)-ocentric CS with
3138
            // geocentric latitude
3139
0
            return SphericalCS::create(csMap, axisList[0], axisList[1]);
3140
0
        } else if (axisCount == 3) {
3141
0
            return SphericalCS::create(csMap, axisList[0], axisList[1],
3142
0
                                       axisList[2]);
3143
0
        }
3144
131
    } else if (ci_equal(csType, OrdinalCS::WKT2_TYPE)) { // WKT2-2019
3145
0
        return OrdinalCS::create(csMap, axisList);
3146
131
    } else if (ci_equal(csType, ParametricCS::WKT2_TYPE)) {
3147
0
        if (axisCount == 1) {
3148
0
            return ParametricCS::create(csMap, axisList[0]);
3149
0
        }
3150
131
    } else if (ci_equal(csType, TemporalCS::WKT2_2015_TYPE)) {
3151
0
        if (axisCount == 1) {
3152
0
            if (isNull(
3153
0
                    parentNode->GP()->lookForChild(WKTConstants::TIMEUNIT)) &&
3154
0
                isNull(parentNode->GP()->lookForChild(WKTConstants::UNIT))) {
3155
0
                return DateTimeTemporalCS::create(csMap, axisList[0]);
3156
0
            } else {
3157
                // Default to TemporalMeasureCS
3158
                // TemporalCount could also be possible
3159
0
                return TemporalMeasureCS::create(csMap, axisList[0]);
3160
0
            }
3161
0
        }
3162
131
    } else if (ci_equal(csType, DateTimeTemporalCS::WKT2_2019_TYPE)) {
3163
0
        if (axisCount == 1) {
3164
0
            return DateTimeTemporalCS::create(csMap, axisList[0]);
3165
0
        }
3166
131
    } else if (ci_equal(csType, TemporalCountCS::WKT2_2019_TYPE)) {
3167
0
        if (axisCount == 1) {
3168
0
            return TemporalCountCS::create(csMap, axisList[0]);
3169
0
        }
3170
131
    } else if (ci_equal(csType, TemporalMeasureCS::WKT2_2019_TYPE)) {
3171
0
        if (axisCount == 1) {
3172
0
            return TemporalMeasureCS::create(csMap, axisList[0]);
3173
0
        }
3174
131
    } else {
3175
131
        throw ParsingException(concat("unhandled CS type: ", csType));
3176
131
    }
3177
4
    throw buildParsingExceptionInvalidAxisCount(csType);
3178
231
}
3179
3180
// ---------------------------------------------------------------------------
3181
3182
std::string
3183
2.76k
WKTParser::Private::getExtensionProj4(const WKTNode::Private *nodeP) {
3184
2.76k
    auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
3185
2.76k
    const auto &extensionChildren = extensionNode->GP()->children();
3186
2.76k
    if (extensionChildren.size() == 2) {
3187
195
        if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
3188
187
            return stripQuotes(extensionChildren[1]);
3189
187
        }
3190
195
    }
3191
2.57k
    return std::string();
3192
2.76k
}
3193
3194
// ---------------------------------------------------------------------------
3195
3196
void WKTParser::Private::addExtensionProj4ToProp(const WKTNode::Private *nodeP,
3197
1.87k
                                                 PropertyMap &props) {
3198
1.87k
    const auto extensionProj4(getExtensionProj4(nodeP));
3199
1.87k
    if (!extensionProj4.empty()) {
3200
54
        props.set("EXTENSION_PROJ4", extensionProj4);
3201
54
    }
3202
1.87k
}
3203
3204
// ---------------------------------------------------------------------------
3205
3206
GeodeticCRSNNPtr
3207
WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node,
3208
1.39k
                                     bool forceGeocentricIfNoCs) {
3209
1.39k
    const auto *nodeP = node->GP();
3210
1.39k
    auto &datumNode = nodeP->lookForChild(
3211
1.39k
        WKTConstants::DATUM, WKTConstants::GEODETICDATUM, WKTConstants::TRF);
3212
1.39k
    auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE);
3213
1.39k
    if (isNull(datumNode) && isNull(ensembleNode)) {
3214
10
        throw ParsingException("Missing DATUM or ENSEMBLE node");
3215
10
    }
3216
3217
    // Do that now so that esriStyle_ can be set before buildPrimeMeridian()
3218
1.38k
    auto props = buildProperties(node);
3219
3220
1.38k
    auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC);
3221
3222
1.38k
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
3223
1.38k
    const auto &nodeName = nodeP->value();
3224
1.38k
    if (isNull(csNode) && !ci_equal(nodeName, WKTConstants::GEOGCS) &&
3225
86
        !ci_equal(nodeName, WKTConstants::GEOCCS) &&
3226
1
        !ci_equal(nodeName, WKTConstants::BASEGEODCRS) &&
3227
1
        !ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) {
3228
1
        ThrowMissing(WKTConstants::CS_);
3229
1
    }
3230
3231
1.38k
    auto &primeMeridianNode =
3232
1.38k
        nodeP->lookForChild(WKTConstants::PRIMEM, WKTConstants::PRIMEMERIDIAN);
3233
1.38k
    if (isNull(primeMeridianNode)) {
3234
        // PRIMEM is required in WKT1
3235
1.04k
        if (ci_equal(nodeName, WKTConstants::GEOGCS) ||
3236
1.04k
            ci_equal(nodeName, WKTConstants::GEOCCS)) {
3237
1.04k
            emitRecoverableWarning(nodeName + " should have a PRIMEM node");
3238
1.04k
        }
3239
1.04k
    }
3240
3241
1.38k
    auto angularUnit =
3242
1.38k
        buildUnitInSubNode(node, ci_equal(nodeName, WKTConstants::GEOGCS)
3243
1.38k
                                     ? UnitOfMeasure::Type::ANGULAR
3244
1.38k
                                     : UnitOfMeasure::Type::UNKNOWN);
3245
1.38k
    if (angularUnit.type() != UnitOfMeasure::Type::ANGULAR) {
3246
836
        angularUnit = UnitOfMeasure::NONE;
3247
836
    }
3248
3249
1.38k
    auto primeMeridian =
3250
1.38k
        !isNull(primeMeridianNode)
3251
1.38k
            ? buildPrimeMeridian(primeMeridianNode, angularUnit)
3252
1.38k
            : PrimeMeridian::GREENWICH;
3253
1.38k
    if (angularUnit == UnitOfMeasure::NONE) {
3254
834
        angularUnit = primeMeridian->longitude().unit();
3255
834
    }
3256
3257
1.38k
    addExtensionProj4ToProp(nodeP, props);
3258
3259
    // No explicit AXIS node ? (WKT1)
3260
1.38k
    if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) {
3261
1.31k
        props.set("IMPLICIT_CS", true);
3262
1.31k
    }
3263
3264
1.38k
    const std::string crsName = stripQuotes(nodeP->children()[0]);
3265
1.38k
    if (esriStyle_ && dbContext_) {
3266
350
        std::string outTableName;
3267
350
        std::string authNameFromAlias;
3268
350
        std::string codeFromAlias;
3269
350
        auto authFactory =
3270
350
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
3271
350
        auto officialName = authFactory->getOfficialNameFromAlias(
3272
350
            crsName, "geodetic_crs", "ESRI", false, outTableName,
3273
350
            authNameFromAlias, codeFromAlias);
3274
350
        if (!officialName.empty()) {
3275
277
            props.set(IdentifiedObject::NAME_KEY, officialName);
3276
277
        }
3277
350
    }
3278
3279
1.38k
    auto datum =
3280
1.38k
        !isNull(datumNode)
3281
1.38k
            ? buildGeodeticReferenceFrame(datumNode, primeMeridian, dynamicNode)
3282
1.16k
                  .as_nullable()
3283
1.38k
            : nullptr;
3284
1.38k
    auto datumEnsemble =
3285
1.38k
        !isNull(ensembleNode)
3286
1.38k
            ? buildDatumEnsemble(ensembleNode, primeMeridian, true)
3287
190
                  .as_nullable()
3288
1.38k
            : nullptr;
3289
1.38k
    auto cs = buildCS(csNode, node, angularUnit);
3290
3291
    // If there's no CS[] node, typically for a BASEGEODCRS of a projected CRS,
3292
    // in a few rare cases, this might be a Geocentric CRS, and thus a
3293
    // Cartesian CS, and not the ellipsoidalCS we assumed above. The only way
3294
    // to figure that is to resolve the CRS from its code...
3295
1.38k
    if (isNull(csNode) && dbContext_ &&
3296
977
        ci_equal(nodeName, WKTConstants::BASEGEODCRS)) {
3297
0
        const auto &nodeChildren = nodeP->children();
3298
0
        for (const auto &subNode : nodeChildren) {
3299
0
            const auto &subNodeName(subNode->GP()->value());
3300
0
            if (ci_equal(subNodeName, WKTConstants::ID) ||
3301
0
                ci_equal(subNodeName, WKTConstants::AUTHORITY)) {
3302
0
                auto id = buildId(node, subNode, true, false);
3303
0
                if (id) {
3304
0
                    try {
3305
0
                        auto authFactory = AuthorityFactory::create(
3306
0
                            NN_NO_CHECK(dbContext_), *id->codeSpace());
3307
0
                        auto dbCRS = authFactory->createGeodeticCRS(id->code());
3308
0
                        cs = dbCRS->coordinateSystem();
3309
0
                    } catch (const util::Exception &) {
3310
0
                    }
3311
0
                }
3312
0
            }
3313
0
        }
3314
0
    }
3315
1.38k
    if (forceGeocentricIfNoCs && isNull(csNode) &&
3316
0
        ci_equal(nodeName, WKTConstants::BASEGEODCRS)) {
3317
0
        cs = cs::CartesianCS::createGeocentric(UnitOfMeasure::METRE);
3318
0
    }
3319
3320
1.38k
    auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs);
3321
1.38k
    if (ellipsoidalCS) {
3322
1.03k
        if (ci_equal(nodeName, WKTConstants::GEOCCS)) {
3323
0
            throw ParsingException("ellipsoidal CS not expected in GEOCCS");
3324
0
        }
3325
1.03k
        try {
3326
1.03k
            auto crs = GeographicCRS::create(props, datum, datumEnsemble,
3327
1.03k
                                             NN_NO_CHECK(ellipsoidalCS));
3328
            // In case of missing CS node, or to check it, query the coordinate
3329
            // system from the DB if possible (typically for the baseCRS of a
3330
            // ProjectedCRS)
3331
1.03k
            if (!crs->identifiers().empty() && dbContext_) {
3332
7
                GeographicCRSPtr dbCRS;
3333
7
                try {
3334
7
                    const auto &id = crs->identifiers()[0];
3335
7
                    auto authFactory = AuthorityFactory::create(
3336
7
                        NN_NO_CHECK(dbContext_), *id->codeSpace());
3337
7
                    dbCRS = authFactory->createGeographicCRS(id->code())
3338
7
                                .as_nullable();
3339
7
                } catch (const util::Exception &) {
3340
7
                }
3341
7
                if (dbCRS &&
3342
0
                    (!isNull(csNode) ||
3343
0
                     node->countChildrenOfName(WKTConstants::AXIS) != 0) &&
3344
0
                    !ellipsoidalCS->_isEquivalentTo(
3345
0
                        dbCRS->coordinateSystem().get(),
3346
0
                        util::IComparable::Criterion::EQUIVALENT)) {
3347
0
                    if (unsetIdentifiersIfIncompatibleDef_) {
3348
0
                        emitRecoverableWarning(
3349
0
                            "Coordinate system of GeographicCRS in the WKT "
3350
0
                            "definition is different from the one of the "
3351
0
                            "authority. Unsetting the identifier to avoid "
3352
0
                            "confusion");
3353
0
                        props.unset(Identifier::CODESPACE_KEY);
3354
0
                        props.unset(Identifier::AUTHORITY_KEY);
3355
0
                        props.unset(IdentifiedObject::IDENTIFIERS_KEY);
3356
0
                    }
3357
0
                    crs = GeographicCRS::create(props, datum, datumEnsemble,
3358
0
                                                NN_NO_CHECK(ellipsoidalCS));
3359
7
                } else if (dbCRS) {
3360
0
                    auto csFromDB = dbCRS->coordinateSystem();
3361
0
                    auto csFromDBAltered = csFromDB;
3362
0
                    if (!isNull(nodeP->lookForChild(WKTConstants::UNIT))) {
3363
0
                        csFromDBAltered =
3364
0
                            csFromDB->alterAngularUnit(angularUnit);
3365
0
                        if (unsetIdentifiersIfIncompatibleDef_ &&
3366
0
                            !csFromDBAltered->_isEquivalentTo(
3367
0
                                csFromDB.get(),
3368
0
                                util::IComparable::Criterion::EQUIVALENT)) {
3369
0
                            emitRecoverableWarning(
3370
0
                                "Coordinate system of GeographicCRS in the WKT "
3371
0
                                "definition is different from the one of the "
3372
0
                                "authority. Unsetting the identifier to avoid "
3373
0
                                "confusion");
3374
0
                            props.unset(Identifier::CODESPACE_KEY);
3375
0
                            props.unset(Identifier::AUTHORITY_KEY);
3376
0
                            props.unset(IdentifiedObject::IDENTIFIERS_KEY);
3377
0
                        }
3378
0
                    }
3379
0
                    crs = GeographicCRS::create(props, datum, datumEnsemble,
3380
0
                                                csFromDBAltered);
3381
0
                }
3382
7
            }
3383
1.03k
            return crs;
3384
1.03k
        } catch (const util::Exception &e) {
3385
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
3386
0
                                   e.what());
3387
0
        }
3388
1.03k
    } else if (ci_equal(nodeName, WKTConstants::GEOGCRS) ||
3389
43
               ci_equal(nodeName, WKTConstants::GEOGRAPHICCRS) ||
3390
43
               ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) {
3391
        // This is a WKT2-2019 GeographicCRS. An ellipsoidal CS is expected
3392
0
        throw ParsingException(concat("ellipsoidal CS expected, but found ",
3393
0
                                      cs->getWKT2Type(true)));
3394
0
    }
3395
3396
349
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
3397
349
    if (cartesianCS) {
3398
43
        if (cartesianCS->axisList().size() != 3) {
3399
1
            throw ParsingException(
3400
1
                "Cartesian CS for a GeodeticCRS should have 3 axis");
3401
1
        }
3402
42
        try {
3403
42
            return GeodeticCRS::create(props, datum, datumEnsemble,
3404
42
                                       NN_NO_CHECK(cartesianCS));
3405
42
        } catch (const util::Exception &e) {
3406
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
3407
0
                                   e.what());
3408
0
        }
3409
42
    }
3410
3411
306
    auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
3412
306
    if (sphericalCS) {
3413
0
        try {
3414
0
            return GeodeticCRS::create(props, datum, datumEnsemble,
3415
0
                                       NN_NO_CHECK(sphericalCS));
3416
0
        } catch (const util::Exception &e) {
3417
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
3418
0
                                   e.what());
3419
0
        }
3420
0
    }
3421
3422
306
    throw ParsingException(
3423
306
        concat("unhandled CS type: ", cs->getWKT2Type(true)));
3424
306
}
3425
3426
// ---------------------------------------------------------------------------
3427
3428
0
CRSNNPtr WKTParser::Private::buildDerivedGeodeticCRS(const WKTNodeNNPtr &node) {
3429
0
    const auto *nodeP = node->GP();
3430
0
    auto &baseGeodCRSNode = nodeP->lookForChild(WKTConstants::BASEGEODCRS,
3431
0
                                                WKTConstants::BASEGEOGCRS);
3432
    // given the constraints enforced on calling code path
3433
0
    assert(!isNull(baseGeodCRSNode));
3434
3435
0
    auto &derivingConversionNode =
3436
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
3437
0
    if (isNull(derivingConversionNode)) {
3438
0
        ThrowMissing(WKTConstants::DERIVINGCONVERSION);
3439
0
    }
3440
0
    auto derivingConversion = buildConversion(
3441
0
        derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
3442
3443
0
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
3444
0
    if (isNull(csNode)) {
3445
0
        ThrowMissing(WKTConstants::CS_);
3446
0
    }
3447
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
3448
3449
0
    bool forceGeocentricIfNoCs = false;
3450
0
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
3451
0
    if (cartesianCS) {
3452
0
        if (cartesianCS->axisList().size() != 3) {
3453
0
            throw ParsingException(
3454
0
                "Cartesian CS for a GeodeticCRS should have 3 axis");
3455
0
        }
3456
0
        const int methodCode = derivingConversion->method()->getEPSGCode();
3457
0
        if ((methodCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
3458
0
             methodCode ==
3459
0
                 EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC ||
3460
0
             methodCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
3461
0
             methodCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
3462
0
             methodCode ==
3463
0
                 EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
3464
0
             methodCode ==
3465
0
                 EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC) &&
3466
0
            nodeP->lookForChild(WKTConstants::BASEGEODCRS) != nullptr) {
3467
0
            forceGeocentricIfNoCs = true;
3468
0
        }
3469
0
    }
3470
0
    auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode, forceGeocentricIfNoCs);
3471
3472
0
    auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs);
3473
0
    if (ellipsoidalCS) {
3474
3475
0
        if (ellipsoidalCS->axisList().size() == 3 &&
3476
0
            baseGeodCRS->coordinateSystem()->axisList().size() == 2) {
3477
0
            baseGeodCRS =
3478
0
                NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
3479
0
                    baseGeodCRS->promoteTo3D(std::string(), dbContext_)));
3480
0
        }
3481
3482
0
        return DerivedGeographicCRS::create(buildProperties(node), baseGeodCRS,
3483
0
                                            derivingConversion,
3484
0
                                            NN_NO_CHECK(ellipsoidalCS));
3485
0
    } else if (ci_equal(nodeP->value(), WKTConstants::GEOGCRS)) {
3486
        // This is a WKT2-2019 GeographicCRS. An ellipsoidal CS is expected
3487
0
        throw ParsingException(concat("ellipsoidal CS expected, but found ",
3488
0
                                      cs->getWKT2Type(true)));
3489
0
    }
3490
3491
0
    if (cartesianCS) {
3492
0
        return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS,
3493
0
                                          derivingConversion,
3494
0
                                          NN_NO_CHECK(cartesianCS));
3495
0
    }
3496
3497
0
    auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
3498
0
    if (sphericalCS) {
3499
0
        return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS,
3500
0
                                          derivingConversion,
3501
0
                                          NN_NO_CHECK(sphericalCS));
3502
0
    }
3503
3504
0
    throw ParsingException(
3505
0
        concat("unhandled CS type: ", cs->getWKT2Type(true)));
3506
0
}
3507
3508
// ---------------------------------------------------------------------------
3509
3510
UnitOfMeasure WKTParser::Private::guessUnitForParameter(
3511
    const std::string &paramName, const UnitOfMeasure &defaultLinearUnit,
3512
3.23k
    const UnitOfMeasure &defaultAngularUnit) {
3513
3.23k
    UnitOfMeasure unit;
3514
    // scale must be first because of 'Scale factor on pseudo standard parallel'
3515
3.23k
    if (ci_find(paramName, "scale") != std::string::npos ||
3516
3.15k
        ci_find(paramName, "scaling factor") != std::string::npos) {
3517
75
        unit = UnitOfMeasure::SCALE_UNITY;
3518
3.15k
    } else if (ci_find(paramName, "latitude") != std::string::npos ||
3519
2.88k
               ci_find(paramName, "longitude") != std::string::npos ||
3520
2.52k
               ci_find(paramName, "meridian") != std::string::npos ||
3521
2.51k
               ci_find(paramName, "parallel") != std::string::npos ||
3522
2.48k
               ci_find(paramName, "azimuth") != std::string::npos ||
3523
2.45k
               ci_find(paramName, "angle") != std::string::npos ||
3524
2.41k
               ci_find(paramName, "heading") != std::string::npos ||
3525
2.37k
               ci_find(paramName, "rotation") != std::string::npos) {
3526
850
        unit = defaultAngularUnit;
3527
2.30k
    } else if (ci_find(paramName, "easting") != std::string::npos ||
3528
1.98k
               ci_find(paramName, "northing") != std::string::npos ||
3529
1.61k
               ci_find(paramName, "height") != std::string::npos) {
3530
761
        unit = defaultLinearUnit;
3531
761
    }
3532
3.23k
    return unit;
3533
3.23k
}
3534
3535
// ---------------------------------------------------------------------------
3536
3537
static bool
3538
549
isEPSGCodeForInterpolationParameter(const OperationParameterNNPtr &parameter) {
3539
549
    const auto &name = parameter->nameStr();
3540
549
    const auto epsgCode = parameter->getEPSGCode();
3541
549
    return name == EPSG_NAME_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
3542
545
           epsgCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
3543
492
           name == EPSG_NAME_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS ||
3544
492
           epsgCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS;
3545
549
}
3546
3547
// ---------------------------------------------------------------------------
3548
3549
321
static bool isIntegerParameter(const OperationParameterNNPtr &parameter) {
3550
321
    return isEPSGCodeForInterpolationParameter(parameter);
3551
321
}
3552
3553
// ---------------------------------------------------------------------------
3554
3555
void WKTParser::Private::consumeParameters(
3556
    const WKTNodeNNPtr &node, bool isAbridged,
3557
    std::vector<OperationParameterNNPtr> &parameters,
3558
    std::vector<ParameterValueNNPtr> &values,
3559
    const UnitOfMeasure &defaultLinearUnit,
3560
356
    const UnitOfMeasure &defaultAngularUnit) {
3561
2.30k
    for (const auto &childNode : node->GP()->children()) {
3562
2.30k
        const auto &childNodeChildren = childNode->GP()->children();
3563
2.30k
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
3564
522
            if (childNodeChildren.size() < 2) {
3565
11
                ThrowNotEnoughChildren(childNode->GP()->value());
3566
11
            }
3567
511
            parameters.push_back(
3568
511
                OperationParameter::create(buildProperties(childNode)));
3569
511
            const auto &paramValue = childNodeChildren[1]->GP()->value();
3570
511
            if (!paramValue.empty() && paramValue[0] == '"') {
3571
174
                values.push_back(
3572
174
                    ParameterValue::create(stripQuotes(childNodeChildren[1])));
3573
337
            } else {
3574
337
                try {
3575
337
                    double val = asDouble(childNodeChildren[1]);
3576
337
                    auto unit = buildUnitInSubNode(childNode);
3577
337
                    if (unit == UnitOfMeasure::NONE) {
3578
321
                        const auto &paramName =
3579
321
                            childNodeChildren[0]->GP()->value();
3580
321
                        unit = guessUnitForParameter(
3581
321
                            paramName, defaultLinearUnit, defaultAngularUnit);
3582
321
                    }
3583
3584
337
                    if (isAbridged) {
3585
0
                        const auto &paramName = parameters.back()->nameStr();
3586
0
                        int paramEPSGCode = 0;
3587
0
                        const auto &paramIds = parameters.back()->identifiers();
3588
0
                        if (paramIds.size() == 1 &&
3589
0
                            ci_equal(*(paramIds[0]->codeSpace()),
3590
0
                                     Identifier::EPSG)) {
3591
0
                            paramEPSGCode = ::atoi(paramIds[0]->code().c_str());
3592
0
                        }
3593
0
                        const common::UnitOfMeasure *pUnit = nullptr;
3594
0
                        if (OperationParameterValue::convertFromAbridged(
3595
0
                                paramName, val, pUnit, paramEPSGCode)) {
3596
0
                            unit = *pUnit;
3597
0
                            parameters.back() = OperationParameter::create(
3598
0
                                buildProperties(childNode)
3599
0
                                    .set(Identifier::CODESPACE_KEY,
3600
0
                                         Identifier::EPSG)
3601
0
                                    .set(Identifier::CODE_KEY, paramEPSGCode));
3602
0
                        }
3603
0
                    }
3604
3605
337
                    if (isIntegerParameter(parameters.back())) {
3606
50
                        values.push_back(ParameterValue::create(
3607
50
                            std::stoi(childNodeChildren[1]->GP()->value())));
3608
287
                    } else {
3609
287
                        values.push_back(
3610
287
                            ParameterValue::create(Measure(val, unit)));
3611
287
                    }
3612
337
                } catch (const std::exception &) {
3613
21
                    throw ParsingException(concat(
3614
21
                        "unhandled parameter value type : ", paramValue));
3615
21
                }
3616
337
            }
3617
1.78k
        } else if (ci_equal(childNode->GP()->value(),
3618
1.78k
                            WKTConstants::PARAMETERFILE)) {
3619
55
            if (childNodeChildren.size() < 2) {
3620
3
                ThrowNotEnoughChildren(childNode->GP()->value());
3621
3
            }
3622
52
            parameters.push_back(
3623
52
                OperationParameter::create(buildProperties(childNode)));
3624
52
            values.push_back(ParameterValue::createFilename(
3625
52
                stripQuotes(childNodeChildren[1])));
3626
52
        }
3627
2.30k
    }
3628
356
}
3629
3630
// ---------------------------------------------------------------------------
3631
3632
static CRSPtr dealWithEPSGCodeForInterpolationCRSParameter(
3633
    DatabaseContextPtr &dbContext,
3634
    std::vector<OperationParameterNNPtr> &parameters,
3635
    std::vector<ParameterValueNNPtr> &values);
3636
3637
ConversionNNPtr
3638
WKTParser::Private::buildConversion(const WKTNodeNNPtr &node,
3639
                                    const UnitOfMeasure &defaultLinearUnit,
3640
365
                                    const UnitOfMeasure &defaultAngularUnit) {
3641
365
    auto &methodNode = node->GP()->lookForChild(WKTConstants::METHOD,
3642
365
                                                WKTConstants::PROJECTION);
3643
365
    if (isNull(methodNode)) {
3644
6
        ThrowMissing(WKTConstants::METHOD);
3645
6
    }
3646
359
    if (methodNode->GP()->childrenSize() == 0) {
3647
3
        ThrowNotEnoughChildren(WKTConstants::METHOD);
3648
3
    }
3649
3650
356
    std::vector<OperationParameterNNPtr> parameters;
3651
356
    std::vector<ParameterValueNNPtr> values;
3652
356
    consumeParameters(node, false, parameters, values, defaultLinearUnit,
3653
356
                      defaultAngularUnit);
3654
3655
356
    auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
3656
356
        dbContext_, parameters, values);
3657
3658
356
    auto &convProps = buildProperties(node);
3659
356
    auto &methodProps = buildProperties(methodNode);
3660
356
    std::string convName;
3661
356
    std::string methodName;
3662
356
    if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) &&
3663
318
        methodProps.getStringValue(IdentifiedObject::NAME_KEY, methodName) &&
3664
318
        starts_with(convName, "Inverse of ") &&
3665
65
        starts_with(methodName, "Inverse of ")) {
3666
3667
51
        auto &invConvProps = buildProperties(node, true);
3668
51
        auto &invMethodProps = buildProperties(methodNode, true);
3669
51
        auto conv = NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(
3670
51
            Conversion::create(invConvProps, invMethodProps, parameters, values)
3671
51
                ->inverse()));
3672
51
        if (interpolationCRS)
3673
0
            conv->setInterpolationCRS(interpolationCRS);
3674
51
        return conv;
3675
51
    }
3676
305
    auto conv = Conversion::create(convProps, methodProps, parameters, values);
3677
305
    if (interpolationCRS)
3678
0
        conv->setInterpolationCRS(interpolationCRS);
3679
305
    return conv;
3680
356
}
3681
3682
// ---------------------------------------------------------------------------
3683
3684
static CRSPtr dealWithEPSGCodeForInterpolationCRSParameter(
3685
    DatabaseContextPtr &dbContext,
3686
    std::vector<OperationParameterNNPtr> &parameters,
3687
321
    std::vector<ParameterValueNNPtr> &values) {
3688
    // Transform EPSG hacky PARAMETER["EPSG code for Interpolation CRS",
3689
    // crs_epsg_code] into proper interpolation CRS
3690
321
    if (dbContext != nullptr) {
3691
518
        for (size_t i = 0; i < parameters.size(); ++i) {
3692
228
            if (isEPSGCodeForInterpolationParameter(parameters[i])) {
3693
7
                const int code = values[i]->integerValue();
3694
7
                try {
3695
7
                    auto authFactory = AuthorityFactory::create(
3696
7
                        NN_NO_CHECK(dbContext), Identifier::EPSG);
3697
7
                    auto interpolationCRS =
3698
7
                        authFactory
3699
7
                            ->createGeographicCRS(internal::toString(code))
3700
7
                            .as_nullable();
3701
7
                    parameters.erase(parameters.begin() + i);
3702
7
                    values.erase(values.begin() + i);
3703
7
                    return interpolationCRS;
3704
7
                } catch (const util::Exception &) {
3705
7
                }
3706
7
            }
3707
228
        }
3708
290
    }
3709
321
    return nullptr;
3710
321
}
3711
3712
// ---------------------------------------------------------------------------
3713
3714
TransformationNNPtr
3715
11
WKTParser::Private::buildCoordinateOperation(const WKTNodeNNPtr &node) {
3716
11
    const auto *nodeP = node->GP();
3717
11
    auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD);
3718
11
    if (isNull(methodNode)) {
3719
5
        ThrowMissing(WKTConstants::METHOD);
3720
5
    }
3721
6
    if (methodNode->GP()->childrenSize() == 0) {
3722
2
        ThrowNotEnoughChildren(WKTConstants::METHOD);
3723
2
    }
3724
3725
4
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
3726
4
    if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) {
3727
4
        ThrowMissing(WKTConstants::SOURCECRS);
3728
4
    }
3729
0
    auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
3730
0
    if (!sourceCRS) {
3731
0
        throw ParsingException("Invalid content in SOURCECRS node");
3732
0
    }
3733
3734
0
    auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
3735
0
    if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) {
3736
0
        ThrowMissing(WKTConstants::TARGETCRS);
3737
0
    }
3738
0
    auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]);
3739
0
    if (!targetCRS) {
3740
0
        throw ParsingException("Invalid content in TARGETCRS node");
3741
0
    }
3742
3743
0
    auto &interpolationCRSNode =
3744
0
        nodeP->lookForChild(WKTConstants::INTERPOLATIONCRS);
3745
0
    CRSPtr interpolationCRS;
3746
0
    if (/*!isNull(interpolationCRSNode) && */ interpolationCRSNode->GP()
3747
0
            ->childrenSize() == 1) {
3748
0
        interpolationCRS = buildCRS(interpolationCRSNode->GP()->children()[0]);
3749
0
    }
3750
3751
0
    std::vector<OperationParameterNNPtr> parameters;
3752
0
    std::vector<ParameterValueNNPtr> values;
3753
0
    const auto &defaultLinearUnit = UnitOfMeasure::NONE;
3754
0
    const auto &defaultAngularUnit = UnitOfMeasure::NONE;
3755
0
    consumeParameters(node, false, parameters, values, defaultLinearUnit,
3756
0
                      defaultAngularUnit);
3757
3758
0
    if (interpolationCRS == nullptr)
3759
0
        interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
3760
0
            dbContext_, parameters, values);
3761
3762
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
3763
0
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY);
3764
0
    if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) {
3765
0
        accuracies.push_back(PositionalAccuracy::create(
3766
0
            stripQuotes(accuracyNode->GP()->children()[0])));
3767
0
    }
3768
3769
0
    return Transformation::create(buildProperties(node), NN_NO_CHECK(sourceCRS),
3770
0
                                  NN_NO_CHECK(targetCRS), interpolationCRS,
3771
0
                                  buildProperties(methodNode), parameters,
3772
0
                                  values, accuracies);
3773
0
}
3774
3775
// ---------------------------------------------------------------------------
3776
3777
PointMotionOperationNNPtr
3778
1
WKTParser::Private::buildPointMotionOperation(const WKTNodeNNPtr &node) {
3779
1
    const auto *nodeP = node->GP();
3780
1
    auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD);
3781
1
    if (isNull(methodNode)) {
3782
1
        ThrowMissing(WKTConstants::METHOD);
3783
1
    }
3784
0
    if (methodNode->GP()->childrenSize() == 0) {
3785
0
        ThrowNotEnoughChildren(WKTConstants::METHOD);
3786
0
    }
3787
3788
0
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
3789
0
    if (sourceCRSNode->GP()->childrenSize() != 1) {
3790
0
        ThrowMissing(WKTConstants::SOURCECRS);
3791
0
    }
3792
0
    auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
3793
0
    if (!sourceCRS) {
3794
0
        throw ParsingException("Invalid content in SOURCECRS node");
3795
0
    }
3796
3797
0
    std::vector<OperationParameterNNPtr> parameters;
3798
0
    std::vector<ParameterValueNNPtr> values;
3799
0
    const auto &defaultLinearUnit = UnitOfMeasure::NONE;
3800
0
    const auto &defaultAngularUnit = UnitOfMeasure::NONE;
3801
0
    consumeParameters(node, false, parameters, values, defaultLinearUnit,
3802
0
                      defaultAngularUnit);
3803
3804
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
3805
0
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY);
3806
0
    if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) {
3807
0
        accuracies.push_back(PositionalAccuracy::create(
3808
0
            stripQuotes(accuracyNode->GP()->children()[0])));
3809
0
    }
3810
3811
0
    return PointMotionOperation::create(
3812
0
        buildProperties(node), NN_NO_CHECK(sourceCRS),
3813
0
        buildProperties(methodNode), parameters, values, accuracies);
3814
0
}
3815
3816
// ---------------------------------------------------------------------------
3817
3818
ConcatenatedOperationNNPtr
3819
5
WKTParser::Private::buildConcatenatedOperation(const WKTNodeNNPtr &node) {
3820
3821
5
    const auto *nodeP = node->GP();
3822
5
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
3823
5
    if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) {
3824
5
        ThrowMissing(WKTConstants::SOURCECRS);
3825
5
    }
3826
0
    auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
3827
0
    if (!sourceCRS) {
3828
0
        throw ParsingException("Invalid content in SOURCECRS node");
3829
0
    }
3830
3831
0
    auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
3832
0
    if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) {
3833
0
        ThrowMissing(WKTConstants::TARGETCRS);
3834
0
    }
3835
0
    auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]);
3836
0
    if (!targetCRS) {
3837
0
        throw ParsingException("Invalid content in TARGETCRS node");
3838
0
    }
3839
3840
0
    std::vector<CoordinateOperationNNPtr> operations;
3841
0
    for (const auto &childNode : nodeP->children()) {
3842
0
        if (ci_equal(childNode->GP()->value(), WKTConstants::STEP)) {
3843
0
            if (childNode->GP()->childrenSize() != 1) {
3844
0
                throw ParsingException("Invalid content in STEP node");
3845
0
            }
3846
0
            auto op = nn_dynamic_pointer_cast<CoordinateOperation>(
3847
0
                build(childNode->GP()->children()[0]));
3848
0
            if (!op) {
3849
0
                throw ParsingException("Invalid content in STEP node");
3850
0
            }
3851
0
            operations.emplace_back(NN_NO_CHECK(op));
3852
0
        }
3853
0
    }
3854
3855
0
    ConcatenatedOperation::fixSteps(
3856
0
        NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), operations, dbContext_,
3857
0
        /* fixDirectionAllowed = */ true);
3858
3859
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
3860
0
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY);
3861
0
    if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) {
3862
0
        accuracies.push_back(PositionalAccuracy::create(
3863
0
            stripQuotes(accuracyNode->GP()->children()[0])));
3864
0
    }
3865
3866
0
    try {
3867
0
        return ConcatenatedOperation::create(buildProperties(node), operations,
3868
0
                                             accuracies);
3869
0
    } catch (const InvalidOperation &e) {
3870
0
        throw ParsingException(
3871
0
            std::string("Cannot build concatenated operation: ") + e.what());
3872
0
    }
3873
0
}
3874
3875
// ---------------------------------------------------------------------------
3876
3877
bool WKTParser::Private::hasWebMercPROJ4String(
3878
722
    const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode) {
3879
722
    if (projectionNode->GP()->childrenSize() == 0) {
3880
5
        ThrowNotEnoughChildren(WKTConstants::PROJECTION);
3881
5
    }
3882
717
    const std::string wkt1ProjectionName =
3883
717
        stripQuotes(projectionNode->GP()->children()[0]);
3884
3885
717
    auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION);
3886
3887
717
    if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
3888
717
                                               "Mercator_1SP") &&
3889
0
        projCRSNode->countChildrenOfName("center_latitude") == 0) {
3890
3891
        // Hack to detect the hacky way of encodign webmerc in GDAL WKT1
3892
        // with a EXTENSION["PROJ4", "+proj=merc +a=6378137 +b=6378137
3893
        // +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m
3894
        // +nadgrids=@null +wktext +no_defs"] node
3895
0
        if (extensionNode && extensionNode->GP()->childrenSize() == 2 &&
3896
0
            ci_equal(stripQuotes(extensionNode->GP()->children()[0]),
3897
0
                     "PROJ4")) {
3898
0
            std::string projString =
3899
0
                stripQuotes(extensionNode->GP()->children()[1]);
3900
0
            if (projString.find("+proj=merc") != std::string::npos &&
3901
0
                projString.find("+a=6378137") != std::string::npos &&
3902
0
                projString.find("+b=6378137") != std::string::npos &&
3903
0
                projString.find("+lon_0=0") != std::string::npos &&
3904
0
                projString.find("+x_0=0") != std::string::npos &&
3905
0
                projString.find("+y_0=0") != std::string::npos &&
3906
0
                projString.find("+nadgrids=@null") != std::string::npos &&
3907
0
                (projString.find("+lat_ts=") == std::string::npos ||
3908
0
                 projString.find("+lat_ts=0") != std::string::npos) &&
3909
0
                (projString.find("+k=") == std::string::npos ||
3910
0
                 projString.find("+k=1") != std::string::npos) &&
3911
0
                (projString.find("+units=") == std::string::npos ||
3912
0
                 projString.find("+units=m") != std::string::npos)) {
3913
0
                return true;
3914
0
            }
3915
0
        }
3916
0
    }
3917
717
    return false;
3918
717
}
3919
3920
// ---------------------------------------------------------------------------
3921
3922
static const MethodMapping *
3923
selectSphericalOrEllipsoidal(const MethodMapping *mapping,
3924
4.59k
                             const GeodeticCRSNNPtr &baseGeodCRS) {
3925
4.59k
    if (mapping->epsg_code ==
3926
4.59k
            EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL ||
3927
4.59k
        mapping->epsg_code == EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA) {
3928
132
        mapping = getMapping(
3929
132
            baseGeodCRS->ellipsoid()->isSphere()
3930
132
                ? EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL
3931
132
                : EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA);
3932
4.46k
    } else if (mapping->epsg_code ==
3933
4.46k
                   EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL ||
3934
4.46k
               mapping->epsg_code ==
3935
4.46k
                   EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) {
3936
219
        mapping = getMapping(
3937
219
            baseGeodCRS->ellipsoid()->isSphere()
3938
219
                ? EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL
3939
219
                : EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA);
3940
4.24k
    } else if (mapping->epsg_code ==
3941
4.24k
                   EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL ||
3942
4.24k
               mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL) {
3943
55
        mapping =
3944
55
            getMapping(baseGeodCRS->ellipsoid()->isSphere()
3945
55
                           ? EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL
3946
55
                           : EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL);
3947
55
    }
3948
4.59k
    return mapping;
3949
4.59k
}
3950
3951
// ---------------------------------------------------------------------------
3952
3953
const ESRIMethodMapping *WKTParser::Private::getESRIMapping(
3954
    const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode,
3955
960
    std::map<std::string, std::string, ci_less_struct> &mapParamNameToValue) {
3956
960
    const std::string esriProjectionName =
3957
960
        stripQuotes(projectionNode->GP()->children()[0]);
3958
3959
    // Lambert_Conformal_Conic or Krovak may map to different WKT2 methods
3960
    // depending
3961
    // on the parameters / their values
3962
960
    const auto esriMappings = getMappingsFromESRI(esriProjectionName);
3963
960
    if (esriMappings.empty()) {
3964
415
        return nullptr;
3965
415
    }
3966
3967
    // Build a map of present parameters
3968
4.73k
    for (const auto &childNode : projCRSNode->GP()->children()) {
3969
4.73k
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
3970
2.30k
            const auto &childNodeChildren = childNode->GP()->children();
3971
2.30k
            if (childNodeChildren.size() < 2) {
3972
9
                ThrowNotEnoughChildren(WKTConstants::PARAMETER);
3973
9
            }
3974
2.29k
            const std::string parameterName(stripQuotes(childNodeChildren[0]));
3975
2.29k
            const auto &paramValue = childNodeChildren[1]->GP()->value();
3976
2.29k
            mapParamNameToValue[parameterName] = paramValue;
3977
2.29k
        }
3978
4.73k
    }
3979
3980
    // Compare parameters present with the ones expected in the mapping
3981
536
    const ESRIMethodMapping *esriMapping = nullptr;
3982
536
    int bestMatchCount = -1;
3983
783
    for (const auto &mapping : esriMappings) {
3984
783
        int matchCount = 0;
3985
783
        int unmatchCount = 0;
3986
5.68k
        for (const auto *param = mapping->params; param->esri_name; ++param) {
3987
4.91k
            auto iter = mapParamNameToValue.find(param->esri_name);
3988
4.91k
            if (iter != mapParamNameToValue.end()) {
3989
1.39k
                if (param->wkt2_name == nullptr) {
3990
25
                    bool ok = true;
3991
25
                    try {
3992
25
                        if (io::asDouble(param->fixed_value) ==
3993
25
                            io::asDouble(iter->second)) {
3994
11
                            matchCount++;
3995
14
                        } else {
3996
14
                            ok = false;
3997
14
                        }
3998
25
                    } catch (const std::exception &) {
3999
2
                        ok = false;
4000
2
                    }
4001
25
                    if (!ok) {
4002
14
                        matchCount = -1;
4003
14
                        break;
4004
14
                    }
4005
1.36k
                } else {
4006
1.36k
                    matchCount++;
4007
1.36k
                }
4008
3.52k
            } else if (param->is_fixed_value) {
4009
128
                mapParamNameToValue[param->esri_name] = param->fixed_value;
4010
3.39k
            } else {
4011
3.39k
                unmatchCount++;
4012
3.39k
            }
4013
4.91k
        }
4014
783
        if (matchCount > bestMatchCount &&
4015
735
            !(maybeEsriStyle_ && unmatchCount >= matchCount)) {
4016
342
            esriMapping = mapping;
4017
342
            bestMatchCount = matchCount;
4018
342
        }
4019
783
    }
4020
4021
536
    return esriMapping;
4022
536
}
4023
4024
// ---------------------------------------------------------------------------
4025
4026
ConversionNNPtr WKTParser::Private::buildProjectionFromESRI(
4027
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
4028
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
4029
    const UnitOfMeasure &defaultAngularUnit,
4030
    const ESRIMethodMapping *esriMapping,
4031
339
    std::map<std::string, std::string, ci_less_struct> &mapParamNameToValue) {
4032
339
    std::map<std::string, const char *> mapWKT2NameToESRIName;
4033
1.88k
    for (const auto *param = esriMapping->params; param->esri_name; ++param) {
4034
1.54k
        if (param->wkt2_name) {
4035
1.47k
            mapWKT2NameToESRIName[param->wkt2_name] = param->esri_name;
4036
1.47k
        }
4037
1.54k
    }
4038
4039
339
    const std::string esriProjectionName =
4040
339
        stripQuotes(projectionNode->GP()->children()[0]);
4041
339
    const char *projectionMethodWkt2Name = esriMapping->wkt2_name;
4042
339
    if (ci_equal(esriProjectionName, "Krovak")) {
4043
18
        const std::string projCRSName =
4044
18
            stripQuotes(projCRSNode->GP()->children()[0]);
4045
18
        if (projCRSName.find("_East_North") != std::string::npos) {
4046
0
            projectionMethodWkt2Name = EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED;
4047
0
        }
4048
18
    }
4049
4050
339
    const auto *wkt2_mapping = getMapping(projectionMethodWkt2Name);
4051
339
    if (ci_equal(esriProjectionName, "Stereographic")) {
4052
20
        try {
4053
20
            const auto iterLatitudeOfOrigin =
4054
20
                mapParamNameToValue.find("Latitude_Of_Origin");
4055
20
            if (iterLatitudeOfOrigin != mapParamNameToValue.end() &&
4056
19
                std::fabs(io::asDouble(iterLatitudeOfOrigin->second)) == 90.0) {
4057
14
                wkt2_mapping =
4058
14
                    getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A);
4059
14
            }
4060
20
        } catch (const std::exception &) {
4061
0
        }
4062
20
    }
4063
339
    assert(wkt2_mapping);
4064
4065
339
    wkt2_mapping = selectSphericalOrEllipsoidal(wkt2_mapping, baseGeodCRS);
4066
4067
339
    PropertyMap propertiesMethod;
4068
339
    propertiesMethod.set(IdentifiedObject::NAME_KEY, wkt2_mapping->wkt2_name);
4069
339
    if (wkt2_mapping->epsg_code != 0) {
4070
160
        propertiesMethod.set(Identifier::CODE_KEY, wkt2_mapping->epsg_code);
4071
160
        propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
4072
160
    }
4073
4074
339
    std::vector<OperationParameterNNPtr> parameters;
4075
339
    std::vector<ParameterValueNNPtr> values;
4076
4077
339
    if (wkt2_mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL &&
4078
2
        ci_equal(esriProjectionName, "Plate_Carree")) {
4079
        // Add a fixed  Latitude of 1st parallel = 0 so as to have all
4080
        // parameters expected by Equidistant Cylindrical.
4081
2
        mapWKT2NameToESRIName[EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL] =
4082
2
            "Standard_Parallel_1";
4083
2
        mapParamNameToValue["Standard_Parallel_1"] = "0";
4084
337
    } else if ((wkt2_mapping->epsg_code ==
4085
337
                    EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A ||
4086
327
                wkt2_mapping->epsg_code ==
4087
327
                    EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) &&
4088
10
               !ci_equal(esriProjectionName,
4089
10
                         "Rectified_Skew_Orthomorphic_Natural_Origin") &&
4090
0
               !ci_equal(esriProjectionName,
4091
0
                         "Rectified_Skew_Orthomorphic_Center")) {
4092
        // ESRI WKT lacks the angle to skew grid
4093
        // Take it from the azimuth value
4094
0
        mapWKT2NameToESRIName
4095
0
            [EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID] = "Azimuth";
4096
0
    }
4097
4098
1.78k
    for (int i = 0; wkt2_mapping->params[i] != nullptr; i++) {
4099
1.45k
        const auto *paramMapping = wkt2_mapping->params[i];
4100
4101
1.45k
        auto iter = mapWKT2NameToESRIName.find(paramMapping->wkt2_name);
4102
1.45k
        if (iter == mapWKT2NameToESRIName.end()) {
4103
17
            continue;
4104
17
        }
4105
1.44k
        const auto &esriParamName = iter->second;
4106
1.44k
        auto iter2 = mapParamNameToValue.find(esriParamName);
4107
1.44k
        auto mapParamNameToValueEnd = mapParamNameToValue.end();
4108
1.44k
        if (iter2 == mapParamNameToValueEnd) {
4109
            // In case we don't find a direct match, try the aliases
4110
135
            for (iter2 = mapParamNameToValue.begin();
4111
455
                 iter2 != mapParamNameToValueEnd; ++iter2) {
4112
325
                if (areEquivalentParameters(iter2->first, esriParamName)) {
4113
5
                    break;
4114
5
                }
4115
325
            }
4116
135
            if (iter2 == mapParamNameToValueEnd) {
4117
130
                continue;
4118
130
            }
4119
135
        }
4120
4121
1.31k
        PropertyMap propertiesParameter;
4122
1.31k
        propertiesParameter.set(IdentifiedObject::NAME_KEY,
4123
1.31k
                                paramMapping->wkt2_name);
4124
1.31k
        if (paramMapping->epsg_code != 0) {
4125
1.24k
            propertiesParameter.set(Identifier::CODE_KEY,
4126
1.24k
                                    paramMapping->epsg_code);
4127
1.24k
            propertiesParameter.set(Identifier::CODESPACE_KEY,
4128
1.24k
                                    Identifier::EPSG);
4129
1.24k
        }
4130
1.31k
        parameters.push_back(OperationParameter::create(propertiesParameter));
4131
4132
1.31k
        try {
4133
1.31k
            double val = io::asDouble(iter2->second);
4134
1.31k
            auto unit = guessUnitForParameter(
4135
1.31k
                paramMapping->wkt2_name, defaultLinearUnit, defaultAngularUnit);
4136
1.31k
            values.push_back(ParameterValue::create(Measure(val, unit)));
4137
1.31k
        } catch (const std::exception &) {
4138
8
            throw ParsingException(
4139
8
                concat("unhandled parameter value type : ", iter2->second));
4140
8
        }
4141
1.31k
    }
4142
4143
331
    return Conversion::create(
4144
331
               PropertyMap().set(IdentifiedObject::NAME_KEY,
4145
331
                                 esriProjectionName == "Gauss_Kruger"
4146
331
                                     ? "unknown (Gauss Kruger)"
4147
331
                                     : "unknown"),
4148
331
               propertiesMethod, parameters, values)
4149
331
        ->identify();
4150
339
}
4151
4152
// ---------------------------------------------------------------------------
4153
4154
ConversionNNPtr WKTParser::Private::buildProjectionFromESRI(
4155
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
4156
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
4157
689
    const UnitOfMeasure &defaultAngularUnit) {
4158
4159
689
    std::map<std::string, std::string, ci_less_struct> mapParamNameToValue;
4160
689
    const auto esriMapping =
4161
689
        getESRIMapping(projCRSNode, projectionNode, mapParamNameToValue);
4162
689
    if (esriMapping == nullptr) {
4163
355
        return buildProjectionStandard(baseGeodCRS, projCRSNode, projectionNode,
4164
355
                                       defaultLinearUnit, defaultAngularUnit);
4165
355
    }
4166
4167
334
    return buildProjectionFromESRI(baseGeodCRS, projCRSNode, projectionNode,
4168
334
                                   defaultLinearUnit, defaultAngularUnit,
4169
334
                                   esriMapping, mapParamNameToValue);
4170
689
}
4171
4172
// ---------------------------------------------------------------------------
4173
4174
ConversionNNPtr WKTParser::Private::buildProjection(
4175
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
4176
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
4177
717
    const UnitOfMeasure &defaultAngularUnit) {
4178
717
    if (projectionNode->GP()->childrenSize() == 0) {
4179
0
        ThrowNotEnoughChildren(WKTConstants::PROJECTION);
4180
0
    }
4181
717
    if (esriStyle_ || maybeEsriStyle_) {
4182
689
        return buildProjectionFromESRI(baseGeodCRS, projCRSNode, projectionNode,
4183
689
                                       defaultLinearUnit, defaultAngularUnit);
4184
689
    }
4185
28
    return buildProjectionStandard(baseGeodCRS, projCRSNode, projectionNode,
4186
28
                                   defaultLinearUnit, defaultAngularUnit);
4187
717
}
4188
4189
// ---------------------------------------------------------------------------
4190
4191
std::string
4192
WKTParser::Private::projectionGetParameter(const WKTNodeNNPtr &projCRSNode,
4193
0
                                           const char *paramName) {
4194
0
    for (const auto &childNode : projCRSNode->GP()->children()) {
4195
0
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
4196
0
            const auto &childNodeChildren = childNode->GP()->children();
4197
0
            if (childNodeChildren.size() == 2 &&
4198
0
                metadata::Identifier::isEquivalentName(
4199
0
                    stripQuotes(childNodeChildren[0]).c_str(), paramName)) {
4200
0
                return childNodeChildren[1]->GP()->value();
4201
0
            }
4202
0
        }
4203
0
    }
4204
0
    return std::string();
4205
0
}
4206
4207
// ---------------------------------------------------------------------------
4208
4209
ConversionNNPtr WKTParser::Private::buildProjectionStandard(
4210
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
4211
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
4212
383
    const UnitOfMeasure &defaultAngularUnit) {
4213
383
    std::string wkt1ProjectionName =
4214
383
        stripQuotes(projectionNode->GP()->children()[0]);
4215
4216
383
    std::vector<OperationParameterNNPtr> parameters;
4217
383
    std::vector<ParameterValueNNPtr> values;
4218
383
    bool tryToIdentifyWKT1Method = true;
4219
4220
383
    auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION);
4221
383
    const auto &extensionChildren = extensionNode->GP()->children();
4222
4223
383
    bool gdal_3026_hack = false;
4224
383
    if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
4225
383
                                               "Mercator_1SP") &&
4226
0
        projectionGetParameter(projCRSNode, "center_latitude").empty()) {
4227
4228
        // Hack for https://trac.osgeo.org/gdal/ticket/3026
4229
0
        std::string lat0(
4230
0
            projectionGetParameter(projCRSNode, "latitude_of_origin"));
4231
0
        if (!lat0.empty() && lat0 != "0" && lat0 != "0.0") {
4232
0
            wkt1ProjectionName = "Mercator_2SP";
4233
0
            gdal_3026_hack = true;
4234
0
        } else {
4235
            // The latitude of origin, which should always be zero, is
4236
            // missing
4237
            // in GDAL WKT1, but provisionned in the EPSG Mercator_1SP
4238
            // definition,
4239
            // so add it manually.
4240
0
            PropertyMap propertiesParameter;
4241
0
            propertiesParameter.set(IdentifiedObject::NAME_KEY,
4242
0
                                    "Latitude of natural origin");
4243
0
            propertiesParameter.set(Identifier::CODE_KEY, 8801);
4244
0
            propertiesParameter.set(Identifier::CODESPACE_KEY,
4245
0
                                    Identifier::EPSG);
4246
0
            parameters.push_back(
4247
0
                OperationParameter::create(propertiesParameter));
4248
0
            values.push_back(
4249
0
                ParameterValue::create(Measure(0, UnitOfMeasure::DEGREE)));
4250
0
        }
4251
4252
383
    } else if (metadata::Identifier::isEquivalentName(
4253
383
                   wkt1ProjectionName.c_str(), "Polar_Stereographic")) {
4254
83
        std::map<std::string, Measure> mapParameters;
4255
2.01k
        for (const auto &childNode : projCRSNode->GP()->children()) {
4256
2.01k
            const auto &childNodeChildren = childNode->GP()->children();
4257
2.01k
            if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) &&
4258
717
                childNodeChildren.size() == 2) {
4259
648
                const std::string wkt1ParameterName(
4260
648
                    stripQuotes(childNodeChildren[0]));
4261
648
                try {
4262
648
                    double val = asDouble(childNodeChildren[1]);
4263
648
                    auto unit = guessUnitForParameter(wkt1ParameterName,
4264
648
                                                      defaultLinearUnit,
4265
648
                                                      defaultAngularUnit);
4266
648
                    mapParameters.insert(std::pair<std::string, Measure>(
4267
648
                        tolower(wkt1ParameterName), Measure(val, unit)));
4268
648
                } catch (const std::exception &) {
4269
46
                }
4270
648
            }
4271
2.01k
        }
4272
4273
83
        Measure latitudeOfOrigin = mapParameters["latitude_of_origin"];
4274
83
        Measure centralMeridian = mapParameters["central_meridian"];
4275
83
        Measure scaleFactorFromMap = mapParameters["scale_factor"];
4276
83
        Measure scaleFactor((scaleFactorFromMap.unit() == UnitOfMeasure::NONE)
4277
83
                                ? Measure(1.0, UnitOfMeasure::SCALE_UNITY)
4278
83
                                : scaleFactorFromMap);
4279
83
        Measure falseEasting = mapParameters["false_easting"];
4280
83
        Measure falseNorthing = mapParameters["false_northing"];
4281
83
        if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
4282
0
            scaleFactor.getSIValue() == 1.0) {
4283
0
            return Conversion::createPolarStereographicVariantB(
4284
0
                PropertyMap().set(IdentifiedObject::NAME_KEY, "unknown"),
4285
0
                Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()),
4286
0
                Angle(centralMeridian.value(), centralMeridian.unit()),
4287
0
                Length(falseEasting.value(), falseEasting.unit()),
4288
0
                Length(falseNorthing.value(), falseNorthing.unit()));
4289
0
        }
4290
4291
83
        if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
4292
0
            std::fabs(std::fabs(latitudeOfOrigin.convertToUnit(
4293
0
                          UnitOfMeasure::DEGREE)) -
4294
0
                      90.0) < 1e-10) {
4295
0
            return Conversion::createPolarStereographicVariantA(
4296
0
                PropertyMap().set(IdentifiedObject::NAME_KEY, "unknown"),
4297
0
                Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()),
4298
0
                Angle(centralMeridian.value(), centralMeridian.unit()),
4299
0
                Scale(scaleFactor.value(), scaleFactor.unit()),
4300
0
                Length(falseEasting.value(), falseEasting.unit()),
4301
0
                Length(falseNorthing.value(), falseNorthing.unit()));
4302
0
        }
4303
4304
83
        tryToIdentifyWKT1Method = false;
4305
        // Import GDAL PROJ4 extension nodes
4306
300
    } else if (extensionChildren.size() == 2 &&
4307
57
               ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
4308
54
        std::string projString = stripQuotes(extensionChildren[1]);
4309
54
        if (starts_with(projString, "+proj=")) {
4310
47
            if (projString.find(" +type=crs") == std::string::npos) {
4311
44
                projString += " +type=crs";
4312
44
            }
4313
47
            try {
4314
47
                auto projObj =
4315
47
                    PROJStringParser().createFromPROJString(projString);
4316
47
                auto projObjCrs =
4317
47
                    nn_dynamic_pointer_cast<ProjectedCRS>(projObj);
4318
47
                if (projObjCrs) {
4319
0
                    return projObjCrs->derivingConversion();
4320
0
                }
4321
47
            } catch (const io::ParsingException &) {
4322
42
            }
4323
47
        }
4324
54
    }
4325
4326
383
    std::string projectionName(std::move(wkt1ProjectionName));
4327
383
    const MethodMapping *mapping =
4328
383
        tryToIdentifyWKT1Method ? getMappingFromWKT1(projectionName) : nullptr;
4329
4330
383
    if (!mapping) {
4331
        // Sometimes non-WKT1:ESRI looking WKT can actually use WKT1:ESRI
4332
        // projection definitions
4333
271
        std::map<std::string, std::string, ci_less_struct> mapParamNameToValue;
4334
271
        const auto esriMapping =
4335
271
            getESRIMapping(projCRSNode, projectionNode, mapParamNameToValue);
4336
271
        if (esriMapping != nullptr) {
4337
11
            return buildProjectionFromESRI(
4338
11
                baseGeodCRS, projCRSNode, projectionNode, defaultLinearUnit,
4339
11
                defaultAngularUnit, esriMapping, mapParamNameToValue);
4340
11
        }
4341
271
    }
4342
4343
372
    if (mapping) {
4344
112
        mapping = selectSphericalOrEllipsoidal(mapping, baseGeodCRS);
4345
260
    } else if (metadata::Identifier::isEquivalentName(
4346
260
                   projectionName.c_str(), "Lambert Conformal Conic")) {
4347
        // Lambert Conformal Conic or Lambert_Conformal_Conic are respectively
4348
        // used by Oracle WKT and Trimble for either LCC 1SP or 2SP, so we
4349
        // have to look at parameters to figure out the variant.
4350
21
        bool found2ndStdParallel = false;
4351
21
        bool foundScaleFactor = false;
4352
156
        for (const auto &childNode : projCRSNode->GP()->children()) {
4353
156
            if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
4354
33
                const auto &childNodeChildren = childNode->GP()->children();
4355
33
                if (childNodeChildren.size() < 2) {
4356
3
                    ThrowNotEnoughChildren(WKTConstants::PARAMETER);
4357
3
                }
4358
30
                const std::string wkt1ParameterName(
4359
30
                    stripQuotes(childNodeChildren[0]));
4360
30
                if (metadata::Identifier::isEquivalentName(
4361
30
                        wkt1ParameterName.c_str(), WKT1_STANDARD_PARALLEL_2)) {
4362
4
                    found2ndStdParallel = true;
4363
26
                } else if (metadata::Identifier::isEquivalentName(
4364
26
                               wkt1ParameterName.c_str(), WKT1_SCALE_FACTOR)) {
4365
0
                    foundScaleFactor = true;
4366
0
                }
4367
30
            }
4368
156
        }
4369
18
        if (found2ndStdParallel && !foundScaleFactor) {
4370
4
            mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
4371
14
        } else if (!found2ndStdParallel && foundScaleFactor) {
4372
0
            mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
4373
14
        } else if (found2ndStdParallel && foundScaleFactor) {
4374
            // Not sure if that happens
4375
0
            mapping = getMapping(
4376
0
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN);
4377
0
        }
4378
18
    }
4379
4380
    // For Krovak, we need to look at axis to decide between the Krovak and
4381
    // Krovak East-North Oriented methods
4382
369
    if (ci_equal(projectionName, "Krovak") &&
4383
90
        projCRSNode->countChildrenOfName(WKTConstants::AXIS) == 2 &&
4384
0
        &buildAxis(projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 0),
4385
0
                   defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false, 1)
4386
0
                ->direction() == &AxisDirection::SOUTH &&
4387
0
        &buildAxis(projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 1),
4388
0
                   defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false, 2)
4389
0
                ->direction() == &AxisDirection::WEST) {
4390
0
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
4391
0
    }
4392
4393
369
    PropertyMap propertiesMethod;
4394
369
    if (mapping) {
4395
116
        projectionName = mapping->wkt2_name;
4396
116
        if (mapping->epsg_code != 0) {
4397
110
            propertiesMethod.set(Identifier::CODE_KEY, mapping->epsg_code);
4398
110
            propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
4399
110
        }
4400
116
    }
4401
369
    propertiesMethod.set(IdentifiedObject::NAME_KEY, projectionName);
4402
4403
369
    std::vector<bool> foundParameters;
4404
369
    if (mapping) {
4405
116
        size_t countParams = 0;
4406
886
        while (mapping->params[countParams] != nullptr) {
4407
770
            ++countParams;
4408
770
        }
4409
116
        foundParameters.resize(countParams);
4410
116
    }
4411
4412
3.23k
    for (const auto &childNode : projCRSNode->GP()->children()) {
4413
3.23k
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
4414
1.19k
            const auto &childNodeChildren = childNode->GP()->children();
4415
1.19k
            if (childNodeChildren.size() < 2) {
4416
54
                ThrowNotEnoughChildren(WKTConstants::PARAMETER);
4417
54
            }
4418
1.14k
            const auto &paramValue = childNodeChildren[1]->GP()->value();
4419
4420
1.14k
            PropertyMap propertiesParameter;
4421
1.14k
            const std::string wkt1ParameterName(
4422
1.14k
                stripQuotes(childNodeChildren[0]));
4423
1.14k
            std::string parameterName(wkt1ParameterName);
4424
1.14k
            if (gdal_3026_hack) {
4425
0
                if (ci_equal(parameterName, "latitude_of_origin")) {
4426
0
                    parameterName = "standard_parallel_1";
4427
0
                } else if (ci_equal(parameterName, "scale_factor") &&
4428
0
                           paramValue == "1") {
4429
0
                    continue;
4430
0
                }
4431
0
            }
4432
1.14k
            auto *paramMapping =
4433
1.14k
                mapping ? getMappingFromWKT1(mapping, parameterName) : nullptr;
4434
1.14k
            if (mapping &&
4435
513
                mapping->epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
4436
0
                ci_equal(parameterName, "latitude_of_origin")) {
4437
                // Some illegal formulations of Mercator_2SP have a unexpected
4438
                // latitude_of_origin parameter. We accept it on import, but
4439
                // do not accept it when exporting to PROJ string, unless it is
4440
                // zero.
4441
                // No need to try to update foundParameters[] as this is a
4442
                // unexpected one.
4443
0
                parameterName = EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN;
4444
0
                propertiesParameter.set(
4445
0
                    Identifier::CODE_KEY,
4446
0
                    EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
4447
0
                propertiesParameter.set(Identifier::CODESPACE_KEY,
4448
0
                                        Identifier::EPSG);
4449
1.14k
            } else if (mapping && paramMapping) {
4450
13
                for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
4451
13
                    if (mapping->params[idx] == paramMapping) {
4452
7
                        foundParameters[idx] = true;
4453
7
                        break;
4454
7
                    }
4455
13
                }
4456
7
                parameterName = paramMapping->wkt2_name;
4457
7
                if (paramMapping->epsg_code != 0) {
4458
7
                    propertiesParameter.set(Identifier::CODE_KEY,
4459
7
                                            paramMapping->epsg_code);
4460
7
                    propertiesParameter.set(Identifier::CODESPACE_KEY,
4461
7
                                            Identifier::EPSG);
4462
7
                }
4463
7
            }
4464
1.14k
            propertiesParameter.set(IdentifiedObject::NAME_KEY, parameterName);
4465
1.14k
            parameters.push_back(
4466
1.14k
                OperationParameter::create(propertiesParameter));
4467
1.14k
            try {
4468
1.14k
                double val = io::asDouble(paramValue);
4469
1.14k
                auto unit = guessUnitForParameter(
4470
1.14k
                    wkt1ParameterName, defaultLinearUnit, defaultAngularUnit);
4471
1.14k
                values.push_back(ParameterValue::create(Measure(val, unit)));
4472
1.14k
            } catch (const std::exception &) {
4473
135
                throw ParsingException(
4474
135
                    concat("unhandled parameter value type : ", paramValue));
4475
135
            }
4476
1.14k
        }
4477
3.23k
    }
4478
4479
    // Add back important parameters that should normally be present, but
4480
    // are sometimes missing. Currently we only deal with Scale factor at
4481
    // natural origin. This is to avoid a default value of 0 to slip in later.
4482
    // But such WKT should be considered invalid.
4483
180
    if (mapping) {
4484
246
        for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
4485
212
            if (!foundParameters[idx] &&
4486
212
                mapping->params[idx]->epsg_code ==
4487
212
                    EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) {
4488
4489
11
                emitRecoverableWarning(
4490
11
                    "The WKT string lacks a value "
4491
11
                    "for " EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN
4492
11
                    ". Default it to 1.");
4493
4494
11
                PropertyMap propertiesParameter;
4495
11
                propertiesParameter.set(
4496
11
                    Identifier::CODE_KEY,
4497
11
                    EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
4498
11
                propertiesParameter.set(Identifier::CODESPACE_KEY,
4499
11
                                        Identifier::EPSG);
4500
11
                propertiesParameter.set(
4501
11
                    IdentifiedObject::NAME_KEY,
4502
11
                    EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
4503
11
                parameters.push_back(
4504
11
                    OperationParameter::create(propertiesParameter));
4505
11
                values.push_back(ParameterValue::create(
4506
11
                    Measure(1.0, UnitOfMeasure::SCALE_UNITY)));
4507
11
            }
4508
212
        }
4509
34
    }
4510
4511
180
    if (mapping && (mapping->epsg_code ==
4512
34
                        EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A ||
4513
27
                    mapping->epsg_code ==
4514
27
                        EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B)) {
4515
        // Special case when importing some GDAL WKT of Hotine Oblique Mercator
4516
        // that have a Azimuth parameter but lacks the Rectified Grid Angle.
4517
        // We have code in the exportToPROJString() to deal with that situation,
4518
        // but also adds the rectified grid angle from the azimuth on import.
4519
7
        bool foundAngleRecifiedToSkewGrid = false;
4520
7
        bool foundAzimuth = false;
4521
56
        for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
4522
49
            if (foundParameters[idx] &&
4523
0
                mapping->params[idx]->epsg_code ==
4524
0
                    EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) {
4525
0
                foundAngleRecifiedToSkewGrid = true;
4526
49
            } else if (foundParameters[idx] &&
4527
0
                       mapping->params[idx]->epsg_code ==
4528
0
                           EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE) {
4529
0
                foundAzimuth = true;
4530
0
            }
4531
49
        }
4532
7
        if (!foundAngleRecifiedToSkewGrid && foundAzimuth) {
4533
0
            for (size_t idx = 0; idx < parameters.size(); ++idx) {
4534
0
                if (parameters[idx]->getEPSGCode() ==
4535
0
                    EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE) {
4536
0
                    PropertyMap propertiesParameter;
4537
0
                    propertiesParameter.set(
4538
0
                        Identifier::CODE_KEY,
4539
0
                        EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID);
4540
0
                    propertiesParameter.set(Identifier::CODESPACE_KEY,
4541
0
                                            Identifier::EPSG);
4542
0
                    propertiesParameter.set(
4543
0
                        IdentifiedObject::NAME_KEY,
4544
0
                        EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID);
4545
0
                    parameters.push_back(
4546
0
                        OperationParameter::create(propertiesParameter));
4547
0
                    values.push_back(values[idx]);
4548
0
                }
4549
0
            }
4550
0
        }
4551
7
    }
4552
4553
180
    return Conversion::create(
4554
180
               PropertyMap().set(IdentifiedObject::NAME_KEY, "unknown"),
4555
180
               propertiesMethod, parameters, values)
4556
180
        ->identify();
4557
369
}
4558
4559
// ---------------------------------------------------------------------------
4560
4561
static ProjectedCRSNNPtr createPseudoMercator(const PropertyMap &props,
4562
0
                                              const cs::CartesianCSNNPtr &cs) {
4563
0
    auto conversion = Conversion::createPopularVisualisationPseudoMercator(
4564
0
        PropertyMap().set(IdentifiedObject::NAME_KEY, "unknown"), Angle(0),
4565
0
        Angle(0), Length(0), Length(0));
4566
0
    return ProjectedCRS::create(props, GeographicCRS::EPSG_4326, conversion,
4567
0
                                cs);
4568
0
}
4569
4570
// ---------------------------------------------------------------------------
4571
4572
ProjectedCRSNNPtr
4573
830
WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) {
4574
4575
830
    const auto *nodeP = node->GP();
4576
830
    auto &conversionNode = nodeP->lookForChild(WKTConstants::CONVERSION);
4577
830
    auto &projectionNode = nodeP->lookForChild(WKTConstants::PROJECTION);
4578
830
    if (isNull(conversionNode) && isNull(projectionNode)) {
4579
54
        ThrowMissing(WKTConstants::CONVERSION);
4580
54
    }
4581
4582
776
    auto &baseGeodCRSNode =
4583
776
        nodeP->lookForChild(WKTConstants::BASEGEODCRS,
4584
776
                            WKTConstants::BASEGEOGCRS, WKTConstants::GEOGCS);
4585
776
    if (isNull(baseGeodCRSNode)) {
4586
5
        throw ParsingException(
4587
5
            "Missing BASEGEODCRS / BASEGEOGCRS / GEOGCS node");
4588
5
    }
4589
771
    auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode);
4590
4591
771
    auto props = buildProperties(node);
4592
4593
771
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
4594
771
    const auto &nodeValue = nodeP->value();
4595
771
    if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::PROJCS) &&
4596
0
        !ci_equal(nodeValue, WKTConstants::BASEPROJCRS)) {
4597
0
        ThrowMissing(WKTConstants::CS_);
4598
0
    }
4599
4600
771
    std::string projCRSName = stripQuotes(nodeP->children()[0]);
4601
4602
771
    auto cs = [this, &projCRSName, &nodeP, &csNode, &node, &nodeValue,
4603
771
               &conversionNode]() -> CoordinateSystemNNPtr {
4604
742
        if (isNull(csNode) && ci_equal(nodeValue, WKTConstants::BASEPROJCRS) &&
4605
0
            !isNull(conversionNode)) {
4606
            // A BASEPROJCRS (as of WKT2 18-010r11) normally lacks an explicit
4607
            // CS[] which cause issues to properly instantiate it. So we first
4608
            // start by trying to identify the BASEPROJCRS by its id or name.
4609
            // And fallback to exploring the conversion parameters to infer the
4610
            // CS AXIS unit from the linear parameter unit... Not fully bullet
4611
            // proof.
4612
0
            if (dbContext_) {
4613
                // Get official name from database if ID is present
4614
0
                auto &idNode = nodeP->lookForChild(WKTConstants::ID);
4615
0
                if (!isNull(idNode)) {
4616
0
                    try {
4617
0
                        auto id = buildId(node, idNode, false, false);
4618
0
                        auto authFactory = AuthorityFactory::create(
4619
0
                            NN_NO_CHECK(dbContext_), *id->codeSpace());
4620
0
                        auto projCRS =
4621
0
                            authFactory->createProjectedCRS(id->code());
4622
0
                        return projCRS->coordinateSystem();
4623
0
                    } catch (const std::exception &) {
4624
0
                    }
4625
0
                }
4626
4627
0
                auto authFactory = AuthorityFactory::create(
4628
0
                    NN_NO_CHECK(dbContext_), std::string());
4629
0
                auto res = authFactory->createObjectsFromName(
4630
0
                    projCRSName, {AuthorityFactory::ObjectType::PROJECTED_CRS},
4631
0
                    false, 2);
4632
0
                if (res.size() == 1) {
4633
0
                    auto projCRS =
4634
0
                        dynamic_cast<const ProjectedCRS *>(res.front().get());
4635
0
                    if (projCRS) {
4636
0
                        return projCRS->coordinateSystem();
4637
0
                    }
4638
0
                }
4639
0
            }
4640
4641
0
            auto conv = buildConversion(conversionNode, UnitOfMeasure::METRE,
4642
0
                                        UnitOfMeasure::DEGREE);
4643
0
            UnitOfMeasure linearUOM = UnitOfMeasure::NONE;
4644
0
            for (const auto &genOpParamvalue : conv->parameterValues()) {
4645
0
                auto opParamvalue =
4646
0
                    dynamic_cast<const operation::OperationParameterValue *>(
4647
0
                        genOpParamvalue.get());
4648
0
                if (opParamvalue) {
4649
0
                    const auto &parameterValue = opParamvalue->parameterValue();
4650
0
                    if (parameterValue->type() ==
4651
0
                        operation::ParameterValue::Type::MEASURE) {
4652
0
                        const auto &measure = parameterValue->value();
4653
0
                        const auto &unit = measure.unit();
4654
0
                        if (unit.type() == UnitOfMeasure::Type::LINEAR) {
4655
0
                            if (linearUOM == UnitOfMeasure::NONE) {
4656
0
                                linearUOM = unit;
4657
0
                            } else if (linearUOM != unit) {
4658
0
                                linearUOM = UnitOfMeasure::NONE;
4659
0
                                break;
4660
0
                            }
4661
0
                        }
4662
0
                    }
4663
0
                }
4664
0
            }
4665
0
            if (linearUOM != UnitOfMeasure::NONE) {
4666
0
                return CartesianCS::createEastingNorthing(linearUOM);
4667
0
            }
4668
0
        }
4669
742
        return buildCS(csNode, node, UnitOfMeasure::NONE);
4670
742
    }();
4671
771
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
4672
4673
771
    if (dbContext_ && ((!esriStyle_ && projCRSName == "ETRF2000-PL / CS92" &&
4674
0
                        baseGeodCRS->nameStr() == "ETRF2000-PL") ||
4675
658
                       (esriStyle_ && projCRSName == "ETRF2000-PL_CS92" &&
4676
0
                        (baseGeodCRS->nameStr() == "GCS_ETRF2000-PL" ||
4677
0
                         baseGeodCRS->nameStr() == "ETRF2000-PL")))) {
4678
        // Oddity: "ETRF2000-PL / CS92" (EPSG:2180) has switched back to
4679
        // "ETRS89 / PL-1992"
4680
0
        auto authFactoryEPSG =
4681
0
            io::AuthorityFactory::create(NN_NO_CHECK(dbContext_), "EPSG");
4682
0
        auto newProjCRS = authFactoryEPSG->createProjectedCRS("2180");
4683
0
        props.set(IdentifiedObject::NAME_KEY, newProjCRS->nameStr());
4684
0
        baseGeodCRS = newProjCRS->baseCRS();
4685
0
    }
4686
    // In EPSG v12.025, Norway projected systems based on ETRS89 (EPSG:4258)
4687
    // have switched to use ETRS89-NOR [EUREF89] (EPSG:10875).
4688
    // Similarly for other ETRS89-like datums in later releases
4689
771
    else if (dbContext_ &&
4690
658
             (((starts_with(projCRSName, "ETRS89 / ") ||
4691
658
                (esriStyle_ && starts_with(projCRSName, "ETRS_1989_"))) &&
4692
0
               baseGeodCRS->nameStr() == "ETRS89") ||
4693
658
              starts_with(projCRSName, "ETRF2000-PL /")) &&
4694
0
             util::isOfExactType<GeographicCRS>(*(baseGeodCRS.get())) &&
4695
0
             baseGeodCRS->coordinateSystem()->axisList().size() == 2) {
4696
0
        auto authFactoryEPSG =
4697
0
            io::AuthorityFactory::create(NN_NO_CHECK(dbContext_), "EPSG");
4698
0
        const auto objCandidates = authFactoryEPSG->createObjectsFromNameEx(
4699
0
            projCRSName, {io::AuthorityFactory::ObjectType::PROJECTED_CRS},
4700
0
            false, // approximateMatch
4701
0
            0,     // limit
4702
0
            true   // useAliases
4703
0
        );
4704
0
        for (const auto &[obj, name] : objCandidates) {
4705
0
            if (name == projCRSName) {
4706
0
                auto candidateProj =
4707
0
                    dynamic_cast<const crs::ProjectedCRS *>(obj.get());
4708
0
                if (candidateProj &&
4709
0
                    candidateProj->baseCRS()->nameStr() !=
4710
0
                        baseGeodCRS->nameStr() &&
4711
0
                    candidateProj->baseCRS()->_isEquivalentTo(
4712
0
                        baseGeodCRS.get(),
4713
0
                        util::IComparable::Criterion::
4714
0
                            EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS,
4715
0
                        dbContext_)) {
4716
0
                    props.set(IdentifiedObject::NAME_KEY,
4717
0
                              candidateProj->nameStr());
4718
0
                    baseGeodCRS = candidateProj->baseCRS();
4719
0
                    break;
4720
0
                }
4721
0
            }
4722
0
        }
4723
0
    }
4724
4725
771
    if (esriStyle_ && dbContext_) {
4726
337
        if (cartesianCS) {
4727
337
            std::string outTableName;
4728
337
            std::string authNameFromAlias;
4729
337
            std::string codeFromAlias;
4730
337
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
4731
337
                                                        std::string());
4732
337
            auto officialName = authFactory->getOfficialNameFromAlias(
4733
337
                projCRSName, "projected_crs", "ESRI", false, outTableName,
4734
337
                authNameFromAlias, codeFromAlias);
4735
337
            if (!officialName.empty()) {
4736
                // Special case for https://github.com/OSGeo/PROJ/issues/2086
4737
                // The name of the CRS to identify is
4738
                // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501
4739
                // whereas it should be
4740
                // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501_Feet
4741
0
                constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37;
4742
0
                if (projCRSName.find("_FIPS_") != std::string::npos &&
4743
0
                    projCRSName.find("_Feet") == std::string::npos &&
4744
0
                    std::fabs(
4745
0
                        cartesianCS->axisList()[0]->unit().conversionToSI() -
4746
0
                        US_FOOT_CONV_FACTOR) < 1e-10 * US_FOOT_CONV_FACTOR) {
4747
0
                    auto officialNameFromFeet =
4748
0
                        authFactory->getOfficialNameFromAlias(
4749
0
                            projCRSName + "_Feet", "projected_crs", "ESRI",
4750
0
                            false, outTableName, authNameFromAlias,
4751
0
                            codeFromAlias);
4752
0
                    if (!officialNameFromFeet.empty()) {
4753
0
                        officialName = std::move(officialNameFromFeet);
4754
0
                    }
4755
0
                }
4756
4757
0
                projCRSName = officialName;
4758
0
                props.set(IdentifiedObject::NAME_KEY, officialName);
4759
0
            }
4760
337
        }
4761
337
    }
4762
4763
771
    if (isNull(conversionNode) && hasWebMercPROJ4String(node, projectionNode) &&
4764
0
        cartesianCS) {
4765
0
        toWGS84Parameters_.clear();
4766
0
        return createPseudoMercator(props, NN_NO_CHECK(cartesianCS));
4767
0
    }
4768
4769
    // WGS_84_Pseudo_Mercator: Particular case for corrupted ESRI WKT generated
4770
    // by older GDAL versions
4771
    // https://trac.osgeo.org/gdal/changeset/30732
4772
    // WGS_1984_Web_Mercator: deprecated ESRI:102113
4773
771
    if (cartesianCS && (metadata::Identifier::isEquivalentName(
4774
730
                            projCRSName.c_str(), "WGS_84_Pseudo_Mercator") ||
4775
730
                        metadata::Identifier::isEquivalentName(
4776
730
                            projCRSName.c_str(), "WGS_1984_Web_Mercator"))) {
4777
0
        toWGS84Parameters_.clear();
4778
0
        return createPseudoMercator(props, NN_NO_CHECK(cartesianCS));
4779
0
    }
4780
4781
    // For WKT2, if there is no explicit parameter unit, use metre for linear
4782
    // units and degree for angular units
4783
771
    const UnitOfMeasure linearUnit(
4784
771
        !isNull(conversionNode)
4785
771
            ? UnitOfMeasure::METRE
4786
771
            : buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR));
4787
771
    const auto &angularUnit =
4788
771
        !isNull(conversionNode)
4789
771
            ? UnitOfMeasure::DEGREE
4790
771
            : baseGeodCRS->coordinateSystem()->axisList()[0]->unit();
4791
4792
771
    auto conversion =
4793
771
        !isNull(conversionNode)
4794
771
            ? buildConversion(conversionNode, linearUnit, angularUnit)
4795
771
            : buildProjection(baseGeodCRS, node, projectionNode, linearUnit,
4796
758
                              angularUnit);
4797
4798
    // No explicit AXIS node ? (WKT1)
4799
771
    if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) {
4800
521
        props.set("IMPLICIT_CS", true);
4801
521
    }
4802
4803
771
    if (isNull(csNode) && node->countChildrenOfName(WKTConstants::AXIS) == 0) {
4804
4805
521
        const auto methodCode = conversion->method()->getEPSGCode();
4806
        // Krovak south oriented ?
4807
521
        if (methodCode == EPSG_CODE_METHOD_KROVAK) {
4808
13
            cartesianCS =
4809
13
                CartesianCS::create(
4810
13
                    PropertyMap(),
4811
13
                    CoordinateSystemAxis::create(
4812
13
                        util::PropertyMap().set(IdentifiedObject::NAME_KEY,
4813
13
                                                AxisName::Southing),
4814
13
                        emptyString, AxisDirection::SOUTH, linearUnit),
4815
13
                    CoordinateSystemAxis::create(
4816
13
                        util::PropertyMap().set(IdentifiedObject::NAME_KEY,
4817
13
                                                AxisName::Westing),
4818
13
                        emptyString, AxisDirection::WEST, linearUnit))
4819
13
                    .as_nullable();
4820
508
        } else if (methodCode ==
4821
508
                       EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A ||
4822
494
                   methodCode ==
4823
494
                       EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) {
4824
            // It is likely that the ESRI definition of EPSG:32661 (UPS North) &
4825
            // EPSG:32761 (UPS South) uses the easting-northing order, instead
4826
            // of the EPSG northing-easting order.
4827
            // Same for WKT1_GDAL
4828
19
            const double lat0 = conversion->parameterValueNumeric(
4829
19
                EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
4830
19
                common::UnitOfMeasure::DEGREE);
4831
19
            if (std::fabs(lat0 - 90) < 1e-10) {
4832
10
                cartesianCS =
4833
10
                    CartesianCS::createNorthPoleEastingSouthNorthingSouth(
4834
10
                        linearUnit)
4835
10
                        .as_nullable();
4836
10
            } else if (std::fabs(lat0 - -90) < 1e-10) {
4837
9
                cartesianCS =
4838
9
                    CartesianCS::createSouthPoleEastingNorthNorthingNorth(
4839
9
                        linearUnit)
4840
9
                        .as_nullable();
4841
9
            }
4842
489
        } else if (methodCode ==
4843
489
                   EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
4844
3
            const double lat_ts = conversion->parameterValueNumeric(
4845
3
                EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL,
4846
3
                common::UnitOfMeasure::DEGREE);
4847
3
            if (lat_ts > 0) {
4848
3
                cartesianCS =
4849
3
                    CartesianCS::createNorthPoleEastingSouthNorthingSouth(
4850
3
                        linearUnit)
4851
3
                        .as_nullable();
4852
3
            } else if (lat_ts < 0) {
4853
0
                cartesianCS =
4854
0
                    CartesianCS::createSouthPoleEastingNorthNorthingNorth(
4855
0
                        linearUnit)
4856
0
                        .as_nullable();
4857
0
            }
4858
486
        } else if (methodCode ==
4859
486
                   EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
4860
0
            cartesianCS =
4861
0
                CartesianCS::createWestingSouthing(linearUnit).as_nullable();
4862
0
        }
4863
521
    }
4864
771
    if (!cartesianCS) {
4865
0
        ThrowNotExpectedCSType(CartesianCS::WKT2_TYPE);
4866
0
    }
4867
4868
771
    if (cartesianCS->axisList().size() == 3 &&
4869
0
        baseGeodCRS->coordinateSystem()->axisList().size() == 2) {
4870
0
        baseGeodCRS = NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
4871
0
            baseGeodCRS->promoteTo3D(std::string(), dbContext_)));
4872
0
    }
4873
4874
771
    addExtensionProj4ToProp(nodeP, props);
4875
4876
771
    return ProjectedCRS::create(props, baseGeodCRS, conversion,
4877
771
                                NN_NO_CHECK(cartesianCS));
4878
771
}
4879
4880
// ---------------------------------------------------------------------------
4881
4882
void WKTParser::Private::parseDynamic(const WKTNodeNNPtr &dynamicNode,
4883
                                      double &frameReferenceEpoch,
4884
36
                                      util::optional<std::string> &modelName) {
4885
36
    auto &frameEpochNode = dynamicNode->lookForChild(WKTConstants::FRAMEEPOCH);
4886
36
    const auto &frameEpochChildren = frameEpochNode->GP()->children();
4887
36
    if (frameEpochChildren.empty()) {
4888
6
        ThrowMissing(WKTConstants::FRAMEEPOCH);
4889
6
    }
4890
30
    try {
4891
30
        frameReferenceEpoch = asDouble(frameEpochChildren[0]);
4892
30
    } catch (const std::exception &) {
4893
2
        throw ParsingException("Invalid FRAMEEPOCH node");
4894
2
    }
4895
28
    auto &modelNode = dynamicNode->GP()->lookForChild(
4896
28
        WKTConstants::MODEL, WKTConstants::VELOCITYGRID);
4897
28
    const auto &modelChildren = modelNode->GP()->children();
4898
28
    if (modelChildren.size() == 1) {
4899
0
        modelName = stripQuotes(modelChildren[0]);
4900
0
    }
4901
28
}
4902
4903
// ---------------------------------------------------------------------------
4904
4905
VerticalReferenceFrameNNPtr WKTParser::Private::buildVerticalReferenceFrame(
4906
852
    const WKTNodeNNPtr &node, const WKTNodeNNPtr &dynamicNode) {
4907
4908
852
    if (!isNull(dynamicNode)) {
4909
0
        double frameReferenceEpoch = 0.0;
4910
0
        util::optional<std::string> modelName;
4911
0
        parseDynamic(dynamicNode, frameReferenceEpoch, modelName);
4912
0
        return DynamicVerticalReferenceFrame::create(
4913
0
            buildProperties(node), getAnchor(node),
4914
0
            optional<RealizationMethod>(),
4915
0
            common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR),
4916
0
            modelName);
4917
0
    }
4918
4919
    // WKT1 VERT_DATUM has a datum type after the datum name
4920
852
    const auto *nodeP = node->GP();
4921
852
    const std::string &name(nodeP->value());
4922
852
    auto &props = buildProperties(node);
4923
852
    const auto &children = nodeP->children();
4924
4925
852
    if (esriStyle_ && dbContext_ && !children.empty()) {
4926
4
        std::string outTableName;
4927
4
        std::string authNameFromAlias;
4928
4
        std::string codeFromAlias;
4929
4
        auto authFactory =
4930
4
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
4931
4
        const std::string datumName = stripQuotes(children[0]);
4932
4
        auto officialName = authFactory->getOfficialNameFromAlias(
4933
4
            datumName, "vertical_datum", "ESRI", false, outTableName,
4934
4
            authNameFromAlias, codeFromAlias);
4935
4
        if (!officialName.empty()) {
4936
0
            props.set(IdentifiedObject::NAME_KEY, officialName);
4937
0
        }
4938
4
    }
4939
4940
852
    if (ci_equal(name, WKTConstants::VERT_DATUM)) {
4941
30
        if (children.size() >= 2) {
4942
18
            props.set("VERT_DATUM_TYPE", children[1]->GP()->value());
4943
18
        }
4944
30
    }
4945
4946
852
    return VerticalReferenceFrame::create(props, getAnchor(node),
4947
852
                                          getAnchorEpoch(node));
4948
852
}
4949
4950
// ---------------------------------------------------------------------------
4951
4952
TemporalDatumNNPtr
4953
21
WKTParser::Private::buildTemporalDatum(const WKTNodeNNPtr &node) {
4954
21
    const auto *nodeP = node->GP();
4955
21
    auto &calendarNode = nodeP->lookForChild(WKTConstants::CALENDAR);
4956
21
    std::string calendar = TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN;
4957
21
    const auto &calendarChildren = calendarNode->GP()->children();
4958
21
    if (calendarChildren.size() == 1) {
4959
0
        calendar = stripQuotes(calendarChildren[0]);
4960
0
    }
4961
4962
21
    auto &timeOriginNode = nodeP->lookForChild(WKTConstants::TIMEORIGIN);
4963
21
    std::string originStr;
4964
21
    const auto &timeOriginNodeChildren = timeOriginNode->GP()->children();
4965
21
    if (timeOriginNodeChildren.size() == 1) {
4966
3
        originStr = stripQuotes(timeOriginNodeChildren[0]);
4967
3
    }
4968
21
    auto origin = DateTime::create(originStr);
4969
21
    return TemporalDatum::create(buildProperties(node), origin, calendar);
4970
21
}
4971
4972
// ---------------------------------------------------------------------------
4973
4974
EngineeringDatumNNPtr
4975
4
WKTParser::Private::buildEngineeringDatum(const WKTNodeNNPtr &node) {
4976
4
    return EngineeringDatum::create(buildProperties(node), getAnchor(node));
4977
4
}
4978
4979
// ---------------------------------------------------------------------------
4980
4981
ParametricDatumNNPtr
4982
4
WKTParser::Private::buildParametricDatum(const WKTNodeNNPtr &node) {
4983
4
    return ParametricDatum::create(buildProperties(node), getAnchor(node));
4984
4
}
4985
4986
// ---------------------------------------------------------------------------
4987
4988
static CRSNNPtr
4989
createBoundCRSSourceTransformationCRS(const crs::CRSPtr &sourceCRS,
4990
13
                                      const crs::CRSPtr &targetCRS) {
4991
13
    CRSPtr sourceTransformationCRS;
4992
13
    if (dynamic_cast<GeographicCRS *>(targetCRS.get())) {
4993
13
        GeographicCRSPtr sourceGeographicCRS =
4994
13
            sourceCRS->extractGeographicCRS();
4995
13
        sourceTransformationCRS = sourceGeographicCRS;
4996
13
        if (sourceGeographicCRS) {
4997
0
            const auto &sourceDatum = sourceGeographicCRS->datum();
4998
0
            if (sourceDatum != nullptr && sourceGeographicCRS->primeMeridian()
4999
0
                                                  ->longitude()
5000
0
                                                  .getSIValue() != 0.0) {
5001
0
                sourceTransformationCRS =
5002
0
                    GeographicCRS::create(
5003
0
                        util::PropertyMap().set(
5004
0
                            common::IdentifiedObject::NAME_KEY,
5005
0
                            sourceGeographicCRS->nameStr() +
5006
0
                                " (with Greenwich prime meridian)"),
5007
0
                        datum::GeodeticReferenceFrame::create(
5008
0
                            util::PropertyMap().set(
5009
0
                                common::IdentifiedObject::NAME_KEY,
5010
0
                                sourceDatum->nameStr() +
5011
0
                                    " (with Greenwich prime meridian)"),
5012
0
                            sourceDatum->ellipsoid(),
5013
0
                            util::optional<std::string>(),
5014
0
                            datum::PrimeMeridian::GREENWICH),
5015
0
                        sourceGeographicCRS->coordinateSystem())
5016
0
                        .as_nullable();
5017
0
            }
5018
13
        } else {
5019
13
            auto vertSourceCRS =
5020
13
                std::dynamic_pointer_cast<VerticalCRS>(sourceCRS);
5021
13
            if (!vertSourceCRS) {
5022
0
                throw ParsingException(
5023
0
                    "Cannot find GeographicCRS or VerticalCRS in sourceCRS");
5024
0
            }
5025
13
            const auto &axis = vertSourceCRS->coordinateSystem()->axisList()[0];
5026
13
            if (axis->unit() == common::UnitOfMeasure::METRE &&
5027
7
                &(axis->direction()) == &AxisDirection::UP) {
5028
7
                sourceTransformationCRS = sourceCRS;
5029
7
            } else {
5030
6
                std::string sourceTransformationCRSName(
5031
6
                    vertSourceCRS->nameStr());
5032
6
                if (ends_with(sourceTransformationCRSName, " (ftUS)")) {
5033
0
                    sourceTransformationCRSName.resize(
5034
0
                        sourceTransformationCRSName.size() - strlen(" (ftUS)"));
5035
0
                }
5036
6
                if (ends_with(sourceTransformationCRSName, " depth")) {
5037
0
                    sourceTransformationCRSName.resize(
5038
0
                        sourceTransformationCRSName.size() - strlen(" depth"));
5039
0
                }
5040
6
                if (!ends_with(sourceTransformationCRSName, " height")) {
5041
6
                    sourceTransformationCRSName += " height";
5042
6
                }
5043
6
                sourceTransformationCRS =
5044
6
                    VerticalCRS::create(
5045
6
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
5046
6
                                          sourceTransformationCRSName),
5047
6
                        vertSourceCRS->datum(), vertSourceCRS->datumEnsemble(),
5048
6
                        VerticalCS::createGravityRelatedHeight(
5049
6
                            common::UnitOfMeasure::METRE))
5050
6
                        .as_nullable();
5051
6
            }
5052
13
        }
5053
13
    } else {
5054
0
        sourceTransformationCRS = sourceCRS;
5055
0
    }
5056
13
    return NN_NO_CHECK(sourceTransformationCRS);
5057
13
}
5058
5059
// ---------------------------------------------------------------------------
5060
5061
762
CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) {
5062
762
    const auto *nodeP = node->GP();
5063
762
    const auto &nodeValue = nodeP->value();
5064
762
    auto &vdatumNode =
5065
762
        nodeP->lookForChild(WKTConstants::VDATUM, WKTConstants::VERT_DATUM,
5066
762
                            WKTConstants::VERTICALDATUM, WKTConstants::VRF);
5067
762
    auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE);
5068
    // like in ESRI  VERTCS["WGS_1984",DATUM["D_WGS_1984",
5069
    //               SPHEROID["WGS_1984",6378137.0,298.257223563]],
5070
    //               PARAMETER["Vertical_Shift",0.0],
5071
    //               PARAMETER["Direction",1.0],UNIT["Meter",1.0]
5072
762
    auto &geogDatumNode = ci_equal(nodeValue, WKTConstants::VERTCS)
5073
762
                              ? nodeP->lookForChild(WKTConstants::DATUM)
5074
762
                              : null_node;
5075
762
    if (isNull(vdatumNode) && isNull(geogDatumNode) && isNull(ensembleNode)) {
5076
22
        throw ParsingException("Missing VDATUM or ENSEMBLE node");
5077
22
    }
5078
5079
3.34k
    for (const auto &childNode : nodeP->children()) {
5080
3.34k
        const auto &childNodeChildren = childNode->GP()->children();
5081
3.34k
        if (childNodeChildren.size() == 2 &&
5082
352
            ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) &&
5083
22
            childNodeChildren[0]->GP()->value() == "\"Vertical_Shift\"") {
5084
0
            esriStyle_ = true;
5085
0
            break;
5086
0
        }
5087
3.34k
    }
5088
5089
740
    auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC);
5090
740
    auto vdatum =
5091
740
        !isNull(geogDatumNode)
5092
740
            ? VerticalReferenceFrame::create(
5093
15
                  PropertyMap()
5094
15
                      .set(IdentifiedObject::NAME_KEY,
5095
15
                           buildGeodeticReferenceFrame(geogDatumNode,
5096
15
                                                       PrimeMeridian::GREENWICH,
5097
15
                                                       null_node)
5098
15
                               ->nameStr())
5099
15
                      .set("VERT_DATUM_TYPE", "2002"))
5100
15
                  .as_nullable()
5101
740
        : !isNull(vdatumNode)
5102
725
            ? buildVerticalReferenceFrame(vdatumNode, dynamicNode).as_nullable()
5103
725
            : nullptr;
5104
740
    auto datumEnsemble =
5105
740
        !isNull(ensembleNode)
5106
740
            ? buildDatumEnsemble(ensembleNode, nullptr, false).as_nullable()
5107
740
            : nullptr;
5108
5109
740
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5110
740
    if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::VERT_CS) &&
5111
685
        !ci_equal(nodeValue, WKTConstants::VERTCS) &&
5112
1
        !ci_equal(nodeValue, WKTConstants::BASEVERTCRS)) {
5113
1
        ThrowMissing(WKTConstants::CS_);
5114
1
    }
5115
739
    auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(
5116
739
        buildCS(csNode, node, UnitOfMeasure::NONE));
5117
739
    if (!verticalCS) {
5118
0
        ThrowNotExpectedCSType(VerticalCS::WKT2_TYPE);
5119
0
    }
5120
5121
739
    if (vdatum && vdatum->getWKT1DatumType() == "2002" &&
5122
13
        &(verticalCS->axisList()[0]->direction()) == &(AxisDirection::UP)) {
5123
13
        verticalCS =
5124
13
            VerticalCS::create(
5125
13
                util::PropertyMap(),
5126
13
                CoordinateSystemAxis::create(
5127
13
                    util::PropertyMap().set(IdentifiedObject::NAME_KEY,
5128
13
                                            "ellipsoidal height"),
5129
13
                    "h", AxisDirection::UP, verticalCS->axisList()[0]->unit()))
5130
13
                .as_nullable();
5131
13
    }
5132
5133
739
    auto &props = buildProperties(node);
5134
5135
739
    if (esriStyle_ && dbContext_) {
5136
11
        std::string outTableName;
5137
11
        std::string authNameFromAlias;
5138
11
        std::string codeFromAlias;
5139
11
        auto authFactory =
5140
11
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
5141
11
        const std::string vertCRSName = stripQuotes(nodeP->children()[0]);
5142
11
        auto officialName = authFactory->getOfficialNameFromAlias(
5143
11
            vertCRSName, "vertical_crs", "ESRI", false, outTableName,
5144
11
            authNameFromAlias, codeFromAlias);
5145
11
        if (!officialName.empty()) {
5146
0
            props.set(IdentifiedObject::NAME_KEY, officialName);
5147
0
        }
5148
11
    }
5149
5150
    // Deal with Lidar WKT1 VertCRS that embeds geoid model in CRS name,
5151
    // following conventions from
5152
    // https://pubs.usgs.gov/tm/11b4/pdf/tm11-B4.pdf
5153
    // page 9
5154
739
    if (ci_equal(nodeValue, WKTConstants::VERT_CS) ||
5155
672
        ci_equal(nodeValue, WKTConstants::VERTCS)) {
5156
672
        std::string name;
5157
672
        if (props.getStringValue(IdentifiedObject::NAME_KEY, name)) {
5158
672
            std::string navd88GeoidName;
5159
672
            for (const char *prefix :
5160
672
                 {"NAVD88 - ", "NAVD88 via ", "NAVD88 height - ",
5161
2.50k
                  "NAVD88 height (ftUS) - "}) {
5162
2.50k
                if (starts_with(name, prefix)) {
5163
119
                    navd88GeoidName = name.substr(strlen(prefix));
5164
119
                    auto pos = navd88GeoidName.find_first_of(" (");
5165
119
                    if (pos != std::string::npos) {
5166
77
                        navd88GeoidName.resize(pos);
5167
77
                    }
5168
119
                    break;
5169
119
                }
5170
2.50k
            }
5171
5172
            // Deal with vertical CRS names like "Geoid 2012A"
5173
672
            if (navd88GeoidName.empty() && ci_starts_with(name, "Geoid") &&
5174
2
                geogCRSOfCompoundCRS_ &&
5175
0
                starts_with(geogCRSOfCompoundCRS_->nameStr(), "NAD83")) {
5176
                // Remove spaces
5177
0
                navd88GeoidName = replaceAll(name, " ", "");
5178
                // Morph "Geoid 20XX[Y]" to "GeoidXX[Y]"
5179
0
                if (navd88GeoidName.size() >= 9 &&
5180
0
                    ci_starts_with(navd88GeoidName, "Geoid20") &&
5181
0
                    navd88GeoidName[7] >= '0' && navd88GeoidName[7] <= '9' &&
5182
0
                    navd88GeoidName[8] >= '0' && navd88GeoidName[8] <= '9') {
5183
0
                    navd88GeoidName = "Geoid" + navd88GeoidName.substr(7);
5184
0
                }
5185
0
            }
5186
5187
672
            if (!navd88GeoidName.empty()) {
5188
119
                const auto &axis = verticalCS->axisList()[0];
5189
119
                const auto &dir = axis->direction();
5190
119
                if (dir == cs::AxisDirection::UP) {
5191
108
                    if (axis->unit() == common::UnitOfMeasure::METRE) {
5192
81
                        props.set(IdentifiedObject::NAME_KEY, "NAVD88 height");
5193
81
                        props.set(Identifier::CODE_KEY, 5703);
5194
81
                        props.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
5195
81
                    } else if (axis->unit().name() == "US survey foot") {
5196
0
                        props.set(IdentifiedObject::NAME_KEY,
5197
0
                                  "NAVD88 height (ftUS)");
5198
0
                        props.set(Identifier::CODE_KEY, 6360);
5199
0
                        props.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
5200
0
                    }
5201
108
                }
5202
119
                PropertyMap propsModel;
5203
119
                propsModel.set(IdentifiedObject::NAME_KEY,
5204
119
                               toupper(navd88GeoidName));
5205
119
                PropertyMap propsDatum;
5206
119
                propsDatum.set(IdentifiedObject::NAME_KEY,
5207
119
                               "North American Vertical Datum 1988");
5208
119
                propsDatum.set(Identifier::CODE_KEY, 5103);
5209
119
                propsDatum.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
5210
119
                vdatum =
5211
119
                    VerticalReferenceFrame::create(propsDatum).as_nullable();
5212
119
                const auto dummyCRS =
5213
119
                    VerticalCRS::create(PropertyMap(), vdatum, datumEnsemble,
5214
119
                                        NN_NO_CHECK(verticalCS));
5215
119
                const auto model(Transformation::create(
5216
119
                    propsModel, dummyCRS, dummyCRS, nullptr,
5217
119
                    OperationMethod::create(
5218
119
                        PropertyMap(), std::vector<OperationParameterNNPtr>()),
5219
119
                    {}, {}));
5220
119
                props.set("GEOID_MODEL", model);
5221
119
            }
5222
672
        }
5223
672
    }
5224
5225
739
    auto &geoidModelNode = nodeP->lookForChild(WKTConstants::GEOIDMODEL);
5226
739
    if (!isNull(geoidModelNode)) {
5227
67
        ArrayOfBaseObjectNNPtr arrayModels = ArrayOfBaseObject::create();
5228
550
        for (const auto &childNode : nodeP->children()) {
5229
550
            const auto &childNodeChildren = childNode->GP()->children();
5230
550
            if (childNodeChildren.size() >= 1 &&
5231
237
                ci_equal(childNode->GP()->value(), WKTConstants::GEOIDMODEL)) {
5232
133
                auto &propsModel = buildProperties(childNode);
5233
133
                const auto dummyCRS =
5234
133
                    VerticalCRS::create(PropertyMap(), vdatum, datumEnsemble,
5235
133
                                        NN_NO_CHECK(verticalCS));
5236
133
                const auto model(Transformation::create(
5237
133
                    propsModel, dummyCRS, dummyCRS, nullptr,
5238
133
                    OperationMethod::create(
5239
133
                        PropertyMap(), std::vector<OperationParameterNNPtr>()),
5240
133
                    {}, {}));
5241
133
                arrayModels->add(model);
5242
133
            }
5243
550
        }
5244
67
        props.set("GEOID_MODEL", arrayModels);
5245
67
    }
5246
5247
739
    auto crs = nn_static_pointer_cast<CRS>(VerticalCRS::create(
5248
739
        props, vdatum, datumEnsemble, NN_NO_CHECK(verticalCS)));
5249
5250
739
    if (!isNull(vdatumNode)) {
5251
650
        auto &extensionNode = vdatumNode->lookForChild(WKTConstants::EXTENSION);
5252
650
        const auto &extensionChildren = extensionNode->GP()->children();
5253
650
        if (extensionChildren.size() == 2) {
5254
14
            if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) {
5255
13
                const auto gridName(stripQuotes(extensionChildren[1]));
5256
                // This is the expansion of EPSG:5703 by old GDAL versions.
5257
                // See
5258
                // 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
5259
                // It is unlikely that the user really explicitly wants this.
5260
13
                if (gridName != "g2003conus.gtx,g2003alaska.gtx,"
5261
13
                                "g2003h01.gtx,g2003p01.gtx" &&
5262
13
                    gridName != "g2012a_conus.gtx,g2012a_alaska.gtx,"
5263
13
                                "g2012a_guam.gtx,g2012a_hawaii.gtx,"
5264
13
                                "g2012a_puertorico.gtx,g2012a_samoa.gtx") {
5265
13
                    auto geogCRS =
5266
13
                        geogCRSOfCompoundCRS_ &&
5267
0
                                geogCRSOfCompoundCRS_->primeMeridian()
5268
0
                                        ->longitude()
5269
0
                                        .getSIValue() == 0 &&
5270
0
                                geogCRSOfCompoundCRS_->coordinateSystem()
5271
0
                                        ->axisList()[0]
5272
0
                                        ->unit() == UnitOfMeasure::DEGREE
5273
13
                            ? geogCRSOfCompoundCRS_->promoteTo3D(std::string(),
5274
0
                                                                 dbContext_)
5275
13
                            : GeographicCRS::EPSG_4979;
5276
5277
13
                    auto sourceTransformationCRS =
5278
13
                        createBoundCRSSourceTransformationCRS(
5279
13
                            crs.as_nullable(), geogCRS.as_nullable());
5280
13
                    auto transformation = Transformation::
5281
13
                        createGravityRelatedHeightToGeographic3D(
5282
13
                            PropertyMap().set(
5283
13
                                IdentifiedObject::NAME_KEY,
5284
13
                                sourceTransformationCRS->nameStr() + " to " +
5285
13
                                    geogCRS->nameStr() + " ellipsoidal height"),
5286
13
                            sourceTransformationCRS, geogCRS, nullptr, gridName,
5287
13
                            std::vector<PositionalAccuracyNNPtr>());
5288
13
                    return nn_static_pointer_cast<CRS>(
5289
13
                        BoundCRS::create(crs, geogCRS, transformation));
5290
13
                }
5291
13
            }
5292
14
        }
5293
650
    }
5294
5295
726
    return crs;
5296
739
}
5297
5298
// ---------------------------------------------------------------------------
5299
5300
DerivedVerticalCRSNNPtr
5301
3
WKTParser::Private::buildDerivedVerticalCRS(const WKTNodeNNPtr &node) {
5302
3
    const auto *nodeP = node->GP();
5303
3
    auto &baseVertCRSNode = nodeP->lookForChild(WKTConstants::BASEVERTCRS);
5304
    // given the constraints enforced on calling code path
5305
3
    assert(!isNull(baseVertCRSNode));
5306
5307
3
    auto baseVertCRS_tmp = buildVerticalCRS(baseVertCRSNode);
5308
3
    auto baseVertCRS = NN_NO_CHECK(baseVertCRS_tmp->extractVerticalCRS());
5309
5310
3
    auto &derivingConversionNode =
5311
3
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5312
3
    if (isNull(derivingConversionNode)) {
5313
0
        ThrowMissing(WKTConstants::DERIVINGCONVERSION);
5314
0
    }
5315
3
    auto derivingConversion = buildConversion(
5316
3
        derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
5317
5318
3
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5319
3
    if (isNull(csNode)) {
5320
0
        ThrowMissing(WKTConstants::CS_);
5321
0
    }
5322
3
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5323
5324
3
    auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(cs);
5325
3
    if (!verticalCS) {
5326
0
        throw ParsingException(
5327
0
            concat("vertical CS expected, but found ", cs->getWKT2Type(true)));
5328
0
    }
5329
5330
3
    return DerivedVerticalCRS::create(buildProperties(node), baseVertCRS,
5331
3
                                      derivingConversion,
5332
3
                                      NN_NO_CHECK(verticalCS));
5333
3
}
5334
5335
// ---------------------------------------------------------------------------
5336
5337
295
CRSNNPtr WKTParser::Private::buildCompoundCRS(const WKTNodeNNPtr &node) {
5338
295
    std::vector<CRSNNPtr> components;
5339
295
    bool bFirstNode = true;
5340
2.10k
    for (const auto &child : node->GP()->children()) {
5341
2.10k
        auto crs = buildCRS(child);
5342
2.10k
        if (crs) {
5343
817
            if (bFirstNode) {
5344
262
                geogCRSOfCompoundCRS_ = crs->extractGeographicCRS();
5345
262
                bFirstNode = false;
5346
262
            }
5347
817
            components.push_back(NN_NO_CHECK(crs));
5348
817
        }
5349
2.10k
    }
5350
5351
295
    if (ci_equal(node->GP()->value(), WKTConstants::COMPD_CS)) {
5352
195
        return CompoundCRS::createLax(buildProperties(node), components,
5353
195
                                      dbContext_);
5354
195
    } else {
5355
100
        return CompoundCRS::create(buildProperties(node), components);
5356
100
    }
5357
295
}
5358
5359
// ---------------------------------------------------------------------------
5360
5361
static TransformationNNPtr buildTransformationForBoundCRS(
5362
    DatabaseContextPtr &dbContext,
5363
    const util::PropertyMap &abridgedNodeProperties,
5364
    const util::PropertyMap &methodNodeProperties, const CRSNNPtr &sourceCRS,
5365
    const CRSNNPtr &targetCRS, std::vector<OperationParameterNNPtr> &parameters,
5366
0
    std::vector<ParameterValueNNPtr> &values) {
5367
5368
0
    auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
5369
0
        dbContext, parameters, values);
5370
5371
0
    const auto sourceTransformationCRS(
5372
0
        createBoundCRSSourceTransformationCRS(sourceCRS, targetCRS));
5373
0
    auto transformation = Transformation::create(
5374
0
        abridgedNodeProperties, sourceTransformationCRS, targetCRS,
5375
0
        interpolationCRS, methodNodeProperties, parameters, values,
5376
0
        std::vector<PositionalAccuracyNNPtr>());
5377
5378
    // If the transformation is a "Geographic3D to GravityRelatedHeight" one,
5379
    // then the sourceCRS is expected to be a GeographicCRS and the target a
5380
    // VerticalCRS. Due to how things work in a BoundCRS, we have the opposite,
5381
    // so use our "GravityRelatedHeight to Geographic3D" method instead.
5382
0
    if (Transformation::isGeographic3DToGravityRelatedHeight(
5383
0
            transformation->method(), true) &&
5384
0
        dynamic_cast<VerticalCRS *>(sourceTransformationCRS.get()) &&
5385
0
        dynamic_cast<GeographicCRS *>(targetCRS.get())) {
5386
0
        auto fileParameter = transformation->parameterValue(
5387
0
            EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
5388
0
            EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
5389
0
        if (fileParameter &&
5390
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
5391
0
            const auto &filename = fileParameter->valueFile();
5392
5393
0
            transformation =
5394
0
                Transformation::createGravityRelatedHeightToGeographic3D(
5395
0
                    abridgedNodeProperties, sourceTransformationCRS, targetCRS,
5396
0
                    interpolationCRS, filename,
5397
0
                    std::vector<PositionalAccuracyNNPtr>());
5398
0
        }
5399
0
    }
5400
0
    return transformation;
5401
0
}
5402
5403
// ---------------------------------------------------------------------------
5404
5405
15
BoundCRSNNPtr WKTParser::Private::buildBoundCRS(const WKTNodeNNPtr &node) {
5406
15
    const auto *nodeP = node->GP();
5407
15
    auto &abridgedNode =
5408
15
        nodeP->lookForChild(WKTConstants::ABRIDGEDTRANSFORMATION);
5409
15
    if (isNull(abridgedNode)) {
5410
6
        ThrowNotEnoughChildren(WKTConstants::ABRIDGEDTRANSFORMATION);
5411
6
    }
5412
5413
9
    auto &methodNode = abridgedNode->GP()->lookForChild(WKTConstants::METHOD);
5414
9
    if (isNull(methodNode)) {
5415
9
        ThrowMissing(WKTConstants::METHOD);
5416
9
    }
5417
0
    if (methodNode->GP()->childrenSize() == 0) {
5418
0
        ThrowNotEnoughChildren(WKTConstants::METHOD);
5419
0
    }
5420
5421
0
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
5422
0
    const auto &sourceCRSNodeChildren = sourceCRSNode->GP()->children();
5423
0
    if (sourceCRSNodeChildren.size() != 1) {
5424
0
        ThrowNotEnoughChildren(WKTConstants::SOURCECRS);
5425
0
    }
5426
0
    auto sourceCRS = buildCRS(sourceCRSNodeChildren[0]);
5427
0
    if (!sourceCRS) {
5428
0
        throw ParsingException("Invalid content in SOURCECRS node");
5429
0
    }
5430
5431
0
    auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
5432
0
    const auto &targetCRSNodeChildren = targetCRSNode->GP()->children();
5433
0
    if (targetCRSNodeChildren.size() != 1) {
5434
0
        ThrowNotEnoughChildren(WKTConstants::TARGETCRS);
5435
0
    }
5436
0
    auto targetCRS = buildCRS(targetCRSNodeChildren[0]);
5437
0
    if (!targetCRS) {
5438
0
        throw ParsingException("Invalid content in TARGETCRS node");
5439
0
    }
5440
5441
0
    std::vector<OperationParameterNNPtr> parameters;
5442
0
    std::vector<ParameterValueNNPtr> values;
5443
0
    const auto &defaultLinearUnit = UnitOfMeasure::NONE;
5444
0
    const auto &defaultAngularUnit = UnitOfMeasure::NONE;
5445
0
    consumeParameters(abridgedNode, true, parameters, values, defaultLinearUnit,
5446
0
                      defaultAngularUnit);
5447
5448
0
    const auto nnSourceCRS = NN_NO_CHECK(sourceCRS);
5449
0
    const auto nnTargetCRS = NN_NO_CHECK(targetCRS);
5450
0
    const auto transformation = buildTransformationForBoundCRS(
5451
0
        dbContext_, buildProperties(abridgedNode), buildProperties(methodNode),
5452
0
        nnSourceCRS, nnTargetCRS, parameters, values);
5453
5454
0
    return BoundCRS::create(buildProperties(node, false, false),
5455
0
                            NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS),
5456
0
                            transformation);
5457
0
}
5458
5459
// ---------------------------------------------------------------------------
5460
5461
TemporalCSNNPtr
5462
8
WKTParser::Private::buildTemporalCS(const WKTNodeNNPtr &parentNode) {
5463
5464
8
    auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS_);
5465
8
    if (isNull(csNode) &&
5466
5
        !ci_equal(parentNode->GP()->value(), WKTConstants::BASETIMECRS)) {
5467
5
        ThrowMissing(WKTConstants::CS_);
5468
5
    }
5469
3
    auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE);
5470
3
    auto temporalCS = nn_dynamic_pointer_cast<TemporalCS>(cs);
5471
3
    if (!temporalCS) {
5472
0
        ThrowNotExpectedCSType(TemporalCS::WKT2_2015_TYPE);
5473
0
    }
5474
3
    return NN_NO_CHECK(temporalCS);
5475
3
}
5476
5477
// ---------------------------------------------------------------------------
5478
5479
TemporalCRSNNPtr
5480
14
WKTParser::Private::buildTemporalCRS(const WKTNodeNNPtr &node) {
5481
14
    auto &datumNode =
5482
14
        node->GP()->lookForChild(WKTConstants::TDATUM, WKTConstants::TIMEDATUM);
5483
14
    if (isNull(datumNode)) {
5484
6
        throw ParsingException("Missing TDATUM / TIMEDATUM node");
5485
6
    }
5486
5487
8
    return TemporalCRS::create(buildProperties(node),
5488
8
                               buildTemporalDatum(datumNode),
5489
8
                               buildTemporalCS(node));
5490
14
}
5491
5492
// ---------------------------------------------------------------------------
5493
5494
DerivedTemporalCRSNNPtr
5495
2
WKTParser::Private::buildDerivedTemporalCRS(const WKTNodeNNPtr &node) {
5496
2
    const auto *nodeP = node->GP();
5497
2
    auto &baseCRSNode = nodeP->lookForChild(WKTConstants::BASETIMECRS);
5498
    // given the constraints enforced on calling code path
5499
2
    assert(!isNull(baseCRSNode));
5500
5501
2
    auto &derivingConversionNode =
5502
2
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5503
2
    if (isNull(derivingConversionNode)) {
5504
2
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5505
2
    }
5506
5507
0
    return DerivedTemporalCRS::create(
5508
0
        buildProperties(node), buildTemporalCRS(baseCRSNode),
5509
0
        buildConversion(derivingConversionNode, UnitOfMeasure::NONE,
5510
0
                        UnitOfMeasure::NONE),
5511
0
        buildTemporalCS(node));
5512
2
}
5513
5514
// ---------------------------------------------------------------------------
5515
5516
EngineeringCRSNNPtr
5517
3
WKTParser::Private::buildEngineeringCRS(const WKTNodeNNPtr &node) {
5518
3
    const auto *nodeP = node->GP();
5519
3
    auto &datumNode = nodeP->lookForChild(WKTConstants::EDATUM,
5520
3
                                          WKTConstants::ENGINEERINGDATUM);
5521
3
    if (isNull(datumNode)) {
5522
3
        throw ParsingException("Missing EDATUM / ENGINEERINGDATUM node");
5523
3
    }
5524
5525
0
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5526
0
    if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::BASEENGCRS)) {
5527
0
        ThrowMissing(WKTConstants::CS_);
5528
0
    }
5529
5530
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5531
0
    return EngineeringCRS::create(buildProperties(node),
5532
0
                                  buildEngineeringDatum(datumNode), cs);
5533
0
}
5534
5535
// ---------------------------------------------------------------------------
5536
5537
EngineeringCRSNNPtr
5538
381
WKTParser::Private::buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node) {
5539
381
    auto &datumNode = node->GP()->lookForChild(WKTConstants::LOCAL_DATUM);
5540
381
    auto cs = buildCS(null_node, node, UnitOfMeasure::NONE);
5541
381
    auto datum = EngineeringDatum::create(
5542
381
        !isNull(datumNode)
5543
381
            ? buildProperties(datumNode)
5544
381
            :
5545
            // In theory OGC 01-009 mandates LOCAL_DATUM, but GDAL
5546
            // has a tradition of emitting just LOCAL_CS["foo"]
5547
381
            []() {
5548
307
                PropertyMap map;
5549
307
                map.set(IdentifiedObject::NAME_KEY, UNKNOWN_ENGINEERING_DATUM);
5550
307
                return map;
5551
307
            }());
5552
381
    return EngineeringCRS::create(buildProperties(node), datum, cs);
5553
381
}
5554
5555
// ---------------------------------------------------------------------------
5556
5557
DerivedEngineeringCRSNNPtr
5558
0
WKTParser::Private::buildDerivedEngineeringCRS(const WKTNodeNNPtr &node) {
5559
0
    const auto *nodeP = node->GP();
5560
0
    auto &baseEngCRSNode = nodeP->lookForChild(WKTConstants::BASEENGCRS);
5561
    // given the constraints enforced on calling code path
5562
0
    assert(!isNull(baseEngCRSNode));
5563
5564
0
    auto baseEngCRS = buildEngineeringCRS(baseEngCRSNode);
5565
5566
0
    auto &derivingConversionNode =
5567
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5568
0
    if (isNull(derivingConversionNode)) {
5569
0
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5570
0
    }
5571
0
    auto derivingConversion = buildConversion(
5572
0
        derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
5573
5574
0
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5575
0
    if (isNull(csNode)) {
5576
0
        ThrowMissing(WKTConstants::CS_);
5577
0
    }
5578
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5579
5580
0
    return DerivedEngineeringCRS::create(buildProperties(node), baseEngCRS,
5581
0
                                         derivingConversion, cs);
5582
0
}
5583
5584
// ---------------------------------------------------------------------------
5585
5586
ParametricCSNNPtr
5587
0
WKTParser::Private::buildParametricCS(const WKTNodeNNPtr &parentNode) {
5588
5589
0
    auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS_);
5590
0
    if (isNull(csNode) &&
5591
0
        !ci_equal(parentNode->GP()->value(), WKTConstants::BASEPARAMCRS)) {
5592
0
        ThrowMissing(WKTConstants::CS_);
5593
0
    }
5594
0
    auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE);
5595
0
    auto parametricCS = nn_dynamic_pointer_cast<ParametricCS>(cs);
5596
0
    if (!parametricCS) {
5597
0
        ThrowNotExpectedCSType(ParametricCS::WKT2_TYPE);
5598
0
    }
5599
0
    return NN_NO_CHECK(parametricCS);
5600
0
}
5601
5602
// ---------------------------------------------------------------------------
5603
5604
ParametricCRSNNPtr
5605
7
WKTParser::Private::buildParametricCRS(const WKTNodeNNPtr &node) {
5606
7
    auto &datumNode = node->GP()->lookForChild(WKTConstants::PDATUM,
5607
7
                                               WKTConstants::PARAMETRICDATUM);
5608
7
    if (isNull(datumNode)) {
5609
7
        throw ParsingException("Missing PDATUM / PARAMETRICDATUM node");
5610
7
    }
5611
5612
0
    return ParametricCRS::create(buildProperties(node),
5613
0
                                 buildParametricDatum(datumNode),
5614
0
                                 buildParametricCS(node));
5615
7
}
5616
5617
// ---------------------------------------------------------------------------
5618
5619
DerivedParametricCRSNNPtr
5620
6
WKTParser::Private::buildDerivedParametricCRS(const WKTNodeNNPtr &node) {
5621
6
    const auto *nodeP = node->GP();
5622
6
    auto &baseParamCRSNode = nodeP->lookForChild(WKTConstants::BASEPARAMCRS);
5623
    // given the constraints enforced on calling code path
5624
6
    assert(!isNull(baseParamCRSNode));
5625
5626
6
    auto &derivingConversionNode =
5627
6
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5628
6
    if (isNull(derivingConversionNode)) {
5629
6
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5630
6
    }
5631
5632
0
    return DerivedParametricCRS::create(
5633
0
        buildProperties(node), buildParametricCRS(baseParamCRSNode),
5634
0
        buildConversion(derivingConversionNode, UnitOfMeasure::NONE,
5635
0
                        UnitOfMeasure::NONE),
5636
0
        buildParametricCS(node));
5637
6
}
5638
5639
// ---------------------------------------------------------------------------
5640
5641
DerivedProjectedCRSNNPtr
5642
2
WKTParser::Private::buildDerivedProjectedCRS(const WKTNodeNNPtr &node) {
5643
2
    const auto *nodeP = node->GP();
5644
2
    auto &baseProjCRSNode = nodeP->lookForChild(WKTConstants::BASEPROJCRS);
5645
2
    if (isNull(baseProjCRSNode)) {
5646
2
        ThrowNotEnoughChildren(WKTConstants::BASEPROJCRS);
5647
2
    }
5648
0
    auto baseProjCRS = buildProjectedCRS(baseProjCRSNode);
5649
5650
0
    auto &conversionNode =
5651
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5652
0
    if (isNull(conversionNode)) {
5653
0
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5654
0
    }
5655
5656
0
    auto linearUnit = buildUnitInSubNode(node);
5657
0
    const auto &angularUnit =
5658
0
        baseProjCRS->baseCRS()->coordinateSystem()->axisList()[0]->unit();
5659
5660
0
    auto conversion = buildConversion(conversionNode, linearUnit, angularUnit);
5661
5662
0
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5663
0
    if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::PROJCS)) {
5664
0
        ThrowMissing(WKTConstants::CS_);
5665
0
    }
5666
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5667
5668
0
    if (cs->axisList().size() == 3 &&
5669
0
        baseProjCRS->coordinateSystem()->axisList().size() == 2) {
5670
0
        baseProjCRS = NN_NO_CHECK(util::nn_dynamic_pointer_cast<ProjectedCRS>(
5671
0
            baseProjCRS->promoteTo3D(std::string(), dbContext_)));
5672
0
    }
5673
5674
0
    return DerivedProjectedCRS::create(buildProperties(node), baseProjCRS,
5675
0
                                       conversion, cs);
5676
0
}
5677
5678
// ---------------------------------------------------------------------------
5679
5680
CoordinateMetadataNNPtr
5681
4
WKTParser::Private::buildCoordinateMetadata(const WKTNodeNNPtr &node) {
5682
4
    const auto *nodeP = node->GP();
5683
5684
4
    const auto &l_children = nodeP->children();
5685
4
    if (l_children.empty()) {
5686
0
        ThrowNotEnoughChildren(WKTConstants::COORDINATEMETADATA);
5687
0
    }
5688
5689
4
    auto crs = buildCRS(l_children[0]);
5690
4
    if (!crs) {
5691
1
        throw ParsingException("Invalid content in CRS node");
5692
1
    }
5693
5694
3
    auto &epochNode = nodeP->lookForChild(WKTConstants::EPOCH);
5695
3
    if (!isNull(epochNode)) {
5696
0
        const auto &epochChildren = epochNode->GP()->children();
5697
0
        if (epochChildren.empty()) {
5698
0
            ThrowMissing(WKTConstants::EPOCH);
5699
0
        }
5700
0
        double coordinateEpoch;
5701
0
        try {
5702
0
            coordinateEpoch = asDouble(epochChildren[0]);
5703
0
        } catch (const std::exception &) {
5704
0
            throw ParsingException("Invalid EPOCH node");
5705
0
        }
5706
0
        return CoordinateMetadata::create(NN_NO_CHECK(crs), coordinateEpoch,
5707
0
                                          dbContext_);
5708
0
    }
5709
5710
3
    return CoordinateMetadata::create(NN_NO_CHECK(crs));
5711
3
}
5712
5713
// ---------------------------------------------------------------------------
5714
5715
4.63k
static bool isGeodeticCRS(const std::string &name) {
5716
4.63k
    return ci_equal(name, WKTConstants::GEODCRS) ||       // WKT2
5717
4.63k
           ci_equal(name, WKTConstants::GEODETICCRS) ||   // WKT2
5718
4.63k
           ci_equal(name, WKTConstants::GEOGCRS) ||       // WKT2 2019
5719
4.63k
           ci_equal(name, WKTConstants::GEOGRAPHICCRS) || // WKT2 2019
5720
4.63k
           ci_equal(name, WKTConstants::GEOGCS) ||        // WKT1
5721
4.10k
           ci_equal(name, WKTConstants::GEOCCS);          // WKT1
5722
4.63k
}
5723
5724
// ---------------------------------------------------------------------------
5725
5726
4.63k
CRSPtr WKTParser::Private::buildCRS(const WKTNodeNNPtr &node) {
5727
4.63k
    const auto *nodeP = node->GP();
5728
4.63k
    const std::string &name(nodeP->value());
5729
5730
4.63k
    const auto applyHorizontalBoundCRSParams = [&](const CRSNNPtr &crs) {
5731
905
        if (!toWGS84Parameters_.empty()) {
5732
0
            auto ret = BoundCRS::createFromTOWGS84(crs, toWGS84Parameters_);
5733
0
            toWGS84Parameters_.clear();
5734
0
            return util::nn_static_pointer_cast<CRS>(ret);
5735
905
        } else if (!datumPROJ4Grids_.empty()) {
5736
0
            auto ret = BoundCRS::createFromNadgrids(crs, datumPROJ4Grids_);
5737
0
            datumPROJ4Grids_.clear();
5738
0
            return util::nn_static_pointer_cast<CRS>(ret);
5739
0
        }
5740
905
        return crs;
5741
905
    };
5742
5743
4.63k
    if (isGeodeticCRS(name)) {
5744
622
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEGEOGCRS,
5745
622
                                        WKTConstants::BASEGEODCRS))) {
5746
0
            return util::nn_static_pointer_cast<CRS>(
5747
0
                applyHorizontalBoundCRSParams(buildDerivedGeodeticCRS(node)));
5748
622
        } else {
5749
622
            return util::nn_static_pointer_cast<CRS>(
5750
622
                applyHorizontalBoundCRSParams(buildGeodeticCRS(node)));
5751
622
        }
5752
622
    }
5753
5754
4.01k
    if (ci_equal(name, WKTConstants::PROJCS) ||
5755
3.12k
        ci_equal(name, WKTConstants::PROJCRS) ||
5756
3.12k
        ci_equal(name, WKTConstants::PROJECTEDCRS)) {
5757
        // Get the EXTENSION "PROJ4" node before attempting to call
5758
        // buildProjectedCRS() since formulations of WKT1_GDAL from GDAL 2.x
5759
        // with the netCDF driver and the lack the required UNIT[] node
5760
886
        std::string projString = getExtensionProj4(nodeP);
5761
886
        if (!projString.empty() &&
5762
133
            (starts_with(projString, "+proj=ob_tran +o_proj=longlat") ||
5763
70
             starts_with(projString, "+proj=ob_tran +o_proj=lonlat") ||
5764
70
             starts_with(projString, "+proj=ob_tran +o_proj=latlong") ||
5765
101
             starts_with(projString, "+proj=ob_tran +o_proj=latlon"))) {
5766
            // Those are not a projected CRS, but a DerivedGeographic one...
5767
101
            if (projString.find(" +type=crs") == std::string::npos) {
5768
100
                projString += " +type=crs";
5769
100
            }
5770
101
            try {
5771
101
                auto projObj =
5772
101
                    PROJStringParser().createFromPROJString(projString);
5773
101
                auto crs = nn_dynamic_pointer_cast<CRS>(projObj);
5774
101
                if (crs) {
5775
54
                    return util::nn_static_pointer_cast<CRS>(
5776
54
                        applyHorizontalBoundCRSParams(NN_NO_CHECK(crs)));
5777
54
                }
5778
101
            } catch (const io::ParsingException &) {
5779
39
            }
5780
101
        }
5781
830
        return util::nn_static_pointer_cast<CRS>(
5782
830
            applyHorizontalBoundCRSParams(buildProjectedCRS(node)));
5783
886
    }
5784
5785
3.12k
    if (ci_equal(name, WKTConstants::VERT_CS) ||
5786
3.10k
        ci_equal(name, WKTConstants::VERTCS) ||
5787
2.50k
        ci_equal(name, WKTConstants::VERTCRS) ||
5788
2.50k
        ci_equal(name, WKTConstants::VERTICALCRS)) {
5789
620
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEVERTCRS))) {
5790
3
            return util::nn_static_pointer_cast<CRS>(
5791
3
                buildDerivedVerticalCRS(node));
5792
617
        } else {
5793
617
            return util::nn_static_pointer_cast<CRS>(buildVerticalCRS(node));
5794
617
        }
5795
620
    }
5796
5797
2.50k
    if (ci_equal(name, WKTConstants::COMPD_CS) ||
5798
2.21k
        ci_equal(name, WKTConstants::COMPOUNDCRS)) {
5799
295
        return util::nn_static_pointer_cast<CRS>(buildCompoundCRS(node));
5800
295
    }
5801
5802
2.21k
    if (ci_equal(name, WKTConstants::BOUNDCRS)) {
5803
15
        return util::nn_static_pointer_cast<CRS>(buildBoundCRS(node));
5804
15
    }
5805
5806
2.19k
    if (ci_equal(name, WKTConstants::TIMECRS)) {
5807
16
        if (!isNull(nodeP->lookForChild(WKTConstants::BASETIMECRS))) {
5808
2
            return util::nn_static_pointer_cast<CRS>(
5809
2
                buildDerivedTemporalCRS(node));
5810
14
        } else {
5811
14
            return util::nn_static_pointer_cast<CRS>(buildTemporalCRS(node));
5812
14
        }
5813
16
    }
5814
5815
2.18k
    if (ci_equal(name, WKTConstants::DERIVEDPROJCRS)) {
5816
2
        return util::nn_static_pointer_cast<CRS>(
5817
2
            buildDerivedProjectedCRS(node));
5818
2
    }
5819
5820
2.18k
    if (ci_equal(name, WKTConstants::ENGCRS) ||
5821
2.17k
        ci_equal(name, WKTConstants::ENGINEERINGCRS)) {
5822
3
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEENGCRS))) {
5823
0
            return util::nn_static_pointer_cast<CRS>(
5824
0
                buildDerivedEngineeringCRS(node));
5825
3
        } else {
5826
3
            return util::nn_static_pointer_cast<CRS>(buildEngineeringCRS(node));
5827
3
        }
5828
3
    }
5829
5830
2.17k
    if (ci_equal(name, WKTConstants::LOCAL_CS)) {
5831
381
        return util::nn_static_pointer_cast<CRS>(
5832
381
            buildEngineeringCRSFromLocalCS(node));
5833
381
    }
5834
5835
1.79k
    if (ci_equal(name, WKTConstants::PARAMETRICCRS)) {
5836
13
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEPARAMCRS))) {
5837
6
            return util::nn_static_pointer_cast<CRS>(
5838
6
                buildDerivedParametricCRS(node));
5839
7
        } else {
5840
7
            return util::nn_static_pointer_cast<CRS>(buildParametricCRS(node));
5841
7
        }
5842
13
    }
5843
5844
1.78k
    return nullptr;
5845
1.79k
}
5846
5847
// ---------------------------------------------------------------------------
5848
5849
2.29k
BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) {
5850
2.29k
    const auto *nodeP = node->GP();
5851
2.29k
    const std::string &name(nodeP->value());
5852
5853
2.29k
    auto crs = buildCRS(node);
5854
2.29k
    if (crs) {
5855
760
        return util::nn_static_pointer_cast<BaseObject>(NN_NO_CHECK(crs));
5856
760
    }
5857
5858
    // Datum handled by caller code WKTParser::createFromWKT()
5859
5860
1.53k
    if (ci_equal(name, WKTConstants::ENSEMBLE)) {
5861
11
        return util::nn_static_pointer_cast<BaseObject>(buildDatumEnsemble(
5862
11
            node, PrimeMeridian::GREENWICH,
5863
11
            !isNull(nodeP->lookForChild(WKTConstants::ELLIPSOID))));
5864
11
    }
5865
5866
1.52k
    if (ci_equal(name, WKTConstants::VDATUM) ||
5867
585
        ci_equal(name, WKTConstants::VERT_DATUM) ||
5868
584
        ci_equal(name, WKTConstants::VERTICALDATUM) ||
5869
584
        ci_equal(name, WKTConstants::VRF)) {
5870
156
        return util::nn_static_pointer_cast<BaseObject>(
5871
156
            buildVerticalReferenceFrame(node, null_node));
5872
156
    }
5873
5874
1.36k
    if (ci_equal(name, WKTConstants::TDATUM) ||
5875
417
        ci_equal(name, WKTConstants::TIMEDATUM)) {
5876
13
        return util::nn_static_pointer_cast<BaseObject>(
5877
13
            buildTemporalDatum(node));
5878
13
    }
5879
5880
1.35k
    if (ci_equal(name, WKTConstants::EDATUM) ||
5881
412
        ci_equal(name, WKTConstants::ENGINEERINGDATUM)) {
5882
4
        return util::nn_static_pointer_cast<BaseObject>(
5883
4
            buildEngineeringDatum(node));
5884
4
    }
5885
5886
1.35k
    if (ci_equal(name, WKTConstants::PDATUM) ||
5887
408
        ci_equal(name, WKTConstants::PARAMETRICDATUM)) {
5888
4
        return util::nn_static_pointer_cast<BaseObject>(
5889
4
            buildParametricDatum(node));
5890
4
    }
5891
5892
1.34k
    if (ci_equal(name, WKTConstants::ELLIPSOID) ||
5893
408
        ci_equal(name, WKTConstants::SPHEROID)) {
5894
4
        return util::nn_static_pointer_cast<BaseObject>(buildEllipsoid(node));
5895
4
    }
5896
5897
1.34k
    if (ci_equal(name, WKTConstants::COORDINATEOPERATION)) {
5898
11
        auto transf = buildCoordinateOperation(node);
5899
5900
11
        const char *prefixes[] = {
5901
11
            "PROJ-based operation method: ",
5902
11
            "PROJ-based operation method (approximate): "};
5903
11
        for (const char *prefix : prefixes) {
5904
0
            if (starts_with(transf->method()->nameStr(), prefix)) {
5905
0
                auto projString =
5906
0
                    transf->method()->nameStr().substr(strlen(prefix));
5907
0
                return util::nn_static_pointer_cast<BaseObject>(
5908
0
                    PROJBasedOperation::create(
5909
0
                        PropertyMap(), projString, transf->sourceCRS(),
5910
0
                        transf->targetCRS(),
5911
0
                        transf->coordinateOperationAccuracies()));
5912
0
            }
5913
0
        }
5914
5915
11
        return util::nn_static_pointer_cast<BaseObject>(transf);
5916
11
    }
5917
5918
1.33k
    if (ci_equal(name, WKTConstants::CONVERSION)) {
5919
352
        auto conv =
5920
352
            buildConversion(node, UnitOfMeasure::METRE, UnitOfMeasure::DEGREE);
5921
5922
352
        if (starts_with(conv->method()->nameStr(),
5923
352
                        "PROJ-based operation method: ")) {
5924
1
            auto projString = conv->method()->nameStr().substr(
5925
1
                strlen("PROJ-based operation method: "));
5926
1
            return util::nn_static_pointer_cast<BaseObject>(
5927
1
                PROJBasedOperation::create(PropertyMap(), projString, nullptr,
5928
1
                                           nullptr, {}));
5929
1
        }
5930
5931
351
        return util::nn_static_pointer_cast<BaseObject>(conv);
5932
352
    }
5933
5934
979
    if (ci_equal(name, WKTConstants::CONCATENATEDOPERATION)) {
5935
5
        return util::nn_static_pointer_cast<BaseObject>(
5936
5
            buildConcatenatedOperation(node));
5937
5
    }
5938
5939
974
    if (ci_equal(name, WKTConstants::POINTMOTIONOPERATION)) {
5940
1
        return util::nn_static_pointer_cast<BaseObject>(
5941
1
            buildPointMotionOperation(node));
5942
1
    }
5943
5944
973
    if (ci_equal(name, WKTConstants::ID) ||
5945
28
        ci_equal(name, WKTConstants::AUTHORITY)) {
5946
28
        return util::nn_static_pointer_cast<BaseObject>(
5947
28
            NN_NO_CHECK(buildId(node, node, false, false)));
5948
28
    }
5949
5950
945
    if (ci_equal(name, WKTConstants::COORDINATEMETADATA)) {
5951
4
        return util::nn_static_pointer_cast<BaseObject>(
5952
4
            buildCoordinateMetadata(node));
5953
4
    }
5954
5955
941
    throw ParsingException(concat("unhandled keyword: ", name));
5956
945
}
5957
//! @endcond
5958
5959
// ---------------------------------------------------------------------------
5960
5961
//! @cond Doxygen_Suppress
5962
class JSONParser {
5963
    DatabaseContextPtr dbContext_{};
5964
    std::string deformationModelName_{};
5965
    PJ_CONTEXT *ctx_ = nullptr;
5966
5967
    static std::string getString(const json &j, const char *key);
5968
    static json getObject(const json &j, const char *key);
5969
    static json getArray(const json &j, const char *key);
5970
    static int getInteger(const json &j, const char *key);
5971
    static double getNumber(const json &j, const char *key);
5972
    static UnitOfMeasure getUnit(const json &j, const char *key);
5973
    static std::string getName(const json &j);
5974
    static std::string getType(const json &j);
5975
    static Length getLength(const json &j, const char *key);
5976
    static Measure getMeasure(const json &j);
5977
5978
    IdentifierNNPtr buildId(const json &parentJ, const json &j,
5979
                            bool removeInverseOf);
5980
    static ObjectDomainPtr buildObjectDomain(const json &j);
5981
    PropertyMap buildProperties(const json &j, bool removeInverseOf = false,
5982
                                bool nameRequired = true);
5983
5984
    GeographicCRSNNPtr buildGeographicCRS(const json &j);
5985
    GeodeticCRSNNPtr buildGeodeticCRS(const json &j);
5986
    ProjectedCRSNNPtr buildProjectedCRS(const json &j);
5987
    ConversionNNPtr buildConversion(const json &j);
5988
    DatumEnsembleNNPtr buildDatumEnsemble(const json &j);
5989
    GeodeticReferenceFrameNNPtr buildGeodeticReferenceFrame(const json &j);
5990
    VerticalReferenceFrameNNPtr buildVerticalReferenceFrame(const json &j);
5991
    DynamicGeodeticReferenceFrameNNPtr
5992
    buildDynamicGeodeticReferenceFrame(const json &j);
5993
    DynamicVerticalReferenceFrameNNPtr
5994
    buildDynamicVerticalReferenceFrame(const json &j);
5995
    EllipsoidNNPtr buildEllipsoid(const json &j);
5996
    PrimeMeridianNNPtr buildPrimeMeridian(const json &j);
5997
    CoordinateSystemNNPtr buildCS(const json &j);
5998
    MeridianNNPtr buildMeridian(const json &j);
5999
    CoordinateSystemAxisNNPtr buildAxis(const json &j);
6000
    VerticalCRSNNPtr buildVerticalCRS(const json &j);
6001
    CRSNNPtr buildCRS(const json &j);
6002
    CompoundCRSNNPtr buildCompoundCRS(const json &j);
6003
    BoundCRSNNPtr buildBoundCRS(const json &j);
6004
    TransformationNNPtr buildTransformation(const json &j);
6005
    PointMotionOperationNNPtr buildPointMotionOperation(const json &j);
6006
    ConcatenatedOperationNNPtr buildConcatenatedOperation(const json &j);
6007
    CoordinateMetadataNNPtr buildCoordinateMetadata(const json &j);
6008
6009
    void buildGeodeticDatumOrDatumEnsemble(const json &j,
6010
                                           GeodeticReferenceFramePtr &datum,
6011
                                           DatumEnsemblePtr &datumEnsemble);
6012
6013
22
    static util::optional<std::string> getAnchor(const json &j) {
6014
22
        util::optional<std::string> anchor;
6015
22
        if (j.contains("anchor")) {
6016
0
            anchor = getString(j, "anchor");
6017
0
        }
6018
22
        return anchor;
6019
22
    }
6020
6021
0
    static util::optional<common::Measure> getAnchorEpoch(const json &j) {
6022
0
        if (j.contains("anchor_epoch")) {
6023
0
            return util::optional<common::Measure>(common::Measure(
6024
0
                getNumber(j, "anchor_epoch"), common::UnitOfMeasure::YEAR));
6025
0
        }
6026
0
        return util::optional<common::Measure>();
6027
0
    }
6028
6029
25
    EngineeringDatumNNPtr buildEngineeringDatum(const json &j) {
6030
25
        return EngineeringDatum::create(buildProperties(j), getAnchor(j));
6031
25
    }
6032
6033
0
    ParametricDatumNNPtr buildParametricDatum(const json &j) {
6034
0
        return ParametricDatum::create(buildProperties(j), getAnchor(j));
6035
0
    }
6036
6037
0
    TemporalDatumNNPtr buildTemporalDatum(const json &j) {
6038
0
        auto calendar = getString(j, "calendar");
6039
0
        auto origin = DateTime::create(j.contains("time_origin")
6040
0
                                           ? getString(j, "time_origin")
6041
0
                                           : std::string());
6042
0
        return TemporalDatum::create(buildProperties(j), origin, calendar);
6043
0
    }
6044
6045
    template <class TargetCRS, class DatumBuilderType,
6046
              class CSClass = CoordinateSystem>
6047
    util::nn<std::shared_ptr<TargetCRS>> buildCRS(const json &j,
6048
0
                                                  DatumBuilderType f) {
6049
0
        auto datum = (this->*f)(getObject(j, "datum"));
6050
0
        auto cs = buildCS(getObject(j, "coordinate_system"));
6051
0
        auto csCast = util::nn_dynamic_pointer_cast<CSClass>(cs);
6052
0
        if (!csCast) {
6053
0
            throw ParsingException("coordinate_system not of expected type");
6054
0
        }
6055
0
        return TargetCRS::create(buildProperties(j), datum,
6056
0
                                 NN_NO_CHECK(csCast));
6057
0
    }
Unexecuted instantiation: dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::EngineeringCRS> > osgeo::proj::io::JSONParser::buildCRS<osgeo::proj::crs::EngineeringCRS, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::datum::EngineeringDatum> > (osgeo::proj::io::JSONParser::*)(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&), osgeo::proj::cs::CoordinateSystem>(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::datum::EngineeringDatum> > (osgeo::proj::io::JSONParser::*)(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&))
Unexecuted instantiation: dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::ParametricCRS> > osgeo::proj::io::JSONParser::buildCRS<osgeo::proj::crs::ParametricCRS, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::datum::ParametricDatum> > (osgeo::proj::io::JSONParser::*)(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&), osgeo::proj::cs::ParametricCS>(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::datum::ParametricDatum> > (osgeo::proj::io::JSONParser::*)(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&))
Unexecuted instantiation: dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::TemporalCRS> > osgeo::proj::io::JSONParser::buildCRS<osgeo::proj::crs::TemporalCRS, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::datum::TemporalDatum> > (osgeo::proj::io::JSONParser::*)(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&), osgeo::proj::cs::TemporalCS>(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::datum::TemporalDatum> > (osgeo::proj::io::JSONParser::*)(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&))
6058
6059
    template <class TargetCRS, class BaseCRS, class CSClass = CoordinateSystem>
6060
0
    util::nn<std::shared_ptr<TargetCRS>> buildDerivedCRS(const json &j) {
6061
0
        auto baseCRSObj = create(getObject(j, "base_crs"));
6062
0
        auto baseCRS = util::nn_dynamic_pointer_cast<BaseCRS>(baseCRSObj);
6063
0
        if (!baseCRS) {
6064
0
            throw ParsingException("base_crs not of expected type");
6065
0
        }
6066
0
        auto cs = buildCS(getObject(j, "coordinate_system"));
6067
0
        auto csCast = util::nn_dynamic_pointer_cast<CSClass>(cs);
6068
0
        if (!csCast) {
6069
0
            throw ParsingException("coordinate_system not of expected type");
6070
0
        }
6071
0
        auto conv = buildConversion(getObject(j, "conversion"));
6072
0
        return TargetCRS::create(buildProperties(j), NN_NO_CHECK(baseCRS), conv,
6073
0
                                 NN_NO_CHECK(csCast));
6074
0
    }
Unexecuted instantiation: dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::DerivedGeographicCRS> > osgeo::proj::io::JSONParser::buildDerivedCRS<osgeo::proj::crs::DerivedGeographicCRS, osgeo::proj::crs::GeodeticCRS, osgeo::proj::cs::EllipsoidalCS>(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&)
Unexecuted instantiation: dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::DerivedProjectedCRS> > osgeo::proj::io::JSONParser::buildDerivedCRS<osgeo::proj::crs::DerivedProjectedCRS, osgeo::proj::crs::ProjectedCRS, osgeo::proj::cs::CoordinateSystem>(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&)
Unexecuted instantiation: dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::DerivedVerticalCRS> > osgeo::proj::io::JSONParser::buildDerivedCRS<osgeo::proj::crs::DerivedVerticalCRS, osgeo::proj::crs::VerticalCRS, osgeo::proj::cs::VerticalCS>(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&)
Unexecuted instantiation: dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits> > > osgeo::proj::io::JSONParser::buildDerivedCRS<osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits>, osgeo::proj::crs::EngineeringCRS, osgeo::proj::cs::CoordinateSystem>(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&)
Unexecuted instantiation: dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits> > > osgeo::proj::io::JSONParser::buildDerivedCRS<osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits>, osgeo::proj::crs::ParametricCRS, osgeo::proj::cs::ParametricCS>(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&)
Unexecuted instantiation: dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits> > > osgeo::proj::io::JSONParser::buildDerivedCRS<osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits>, osgeo::proj::crs::TemporalCRS, osgeo::proj::cs::TemporalCS>(proj_nlohmann::basic_json<std::__1::map, std::__1::vector, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, bool, long, unsigned long, double, std::__1::allocator, proj_nlohmann::adl_serializer, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > > const&)
6075
6076
0
    void emitRecoverableWarning(const std::string &warningMsg) {
6077
0
        if (ctx_) {
6078
0
            proj_context_log_debug(ctx_, "PROJJSON parsing: %s",
6079
0
                                   warningMsg.c_str());
6080
0
        }
6081
0
    }
6082
6083
    JSONParser(const JSONParser &) = delete;
6084
    JSONParser &operator=(const JSONParser &) = delete;
6085
6086
  public:
6087
90
    JSONParser() = default;
6088
6089
90
    JSONParser &attachDatabaseContext(const DatabaseContextPtr &dbContext) {
6090
90
        dbContext_ = dbContext;
6091
90
        return *this;
6092
90
    }
6093
6094
90
    JSONParser &attachContext(PJ_CONTEXT *ctx) {
6095
90
        ctx_ = ctx;
6096
90
        return *this;
6097
90
    }
6098
6099
    BaseObjectNNPtr create(const json &j);
6100
};
6101
6102
// ---------------------------------------------------------------------------
6103
6104
138
std::string JSONParser::getString(const json &j, const char *key) {
6105
138
    if (!j.contains(key)) {
6106
19
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6107
19
    }
6108
119
    auto v = j[key];
6109
119
    if (!v.is_string()) {
6110
30
        throw ParsingException(std::string("The value of \"") + key +
6111
30
                               "\" should be a string");
6112
30
    }
6113
89
    return v.get<std::string>();
6114
119
}
6115
6116
// ---------------------------------------------------------------------------
6117
6118
25
json JSONParser::getObject(const json &j, const char *key) {
6119
25
    if (!j.contains(key)) {
6120
3
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6121
3
    }
6122
22
    auto v = j[key];
6123
22
    if (!v.is_object()) {
6124
0
        throw ParsingException(std::string("The value of \"") + key +
6125
0
                               "\" should be a object");
6126
0
    }
6127
22
    return v.get<json>();
6128
22
}
6129
6130
// ---------------------------------------------------------------------------
6131
6132
0
json JSONParser::getArray(const json &j, const char *key) {
6133
0
    if (!j.contains(key)) {
6134
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6135
0
    }
6136
0
    auto v = j[key];
6137
0
    if (!v.is_array()) {
6138
0
        throw ParsingException(std::string("The value of \"") + key +
6139
0
                               "\" should be a array");
6140
0
    }
6141
0
    return v.get<json>();
6142
0
}
6143
6144
// ---------------------------------------------------------------------------
6145
6146
0
int JSONParser::getInteger(const json &j, const char *key) {
6147
0
    if (!j.contains(key)) {
6148
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6149
0
    }
6150
0
    auto v = j[key];
6151
0
    if (!v.is_number()) {
6152
0
        throw ParsingException(std::string("The value of \"") + key +
6153
0
                               "\" should be an integer");
6154
0
    }
6155
0
    const double dbl = v.get<double>();
6156
0
    if (!(dbl >= std::numeric_limits<int>::min() &&
6157
0
          dbl <= std::numeric_limits<int>::max() &&
6158
0
          static_cast<int>(dbl) == dbl)) {
6159
0
        throw ParsingException(std::string("The value of \"") + key +
6160
0
                               "\" should be an integer");
6161
0
    }
6162
0
    return static_cast<int>(dbl);
6163
0
}
6164
6165
// ---------------------------------------------------------------------------
6166
6167
0
double JSONParser::getNumber(const json &j, const char *key) {
6168
0
    if (!j.contains(key)) {
6169
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6170
0
    }
6171
0
    auto v = j[key];
6172
0
    if (!v.is_number()) {
6173
0
        throw ParsingException(std::string("The value of \"") + key +
6174
0
                               "\" should be a number");
6175
0
    }
6176
0
    return v.get<double>();
6177
0
}
6178
6179
// ---------------------------------------------------------------------------
6180
6181
0
UnitOfMeasure JSONParser::getUnit(const json &j, const char *key) {
6182
0
    if (!j.contains(key)) {
6183
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6184
0
    }
6185
0
    auto v = j[key];
6186
0
    if (v.is_string()) {
6187
0
        auto vStr = v.get<std::string>();
6188
0
        for (const auto &unit : {UnitOfMeasure::METRE, UnitOfMeasure::DEGREE,
6189
0
                                 UnitOfMeasure::SCALE_UNITY}) {
6190
0
            if (vStr == unit.name())
6191
0
                return unit;
6192
0
        }
6193
0
        throw ParsingException("Unknown unit name: " + vStr);
6194
0
    }
6195
0
    if (!v.is_object()) {
6196
0
        throw ParsingException(std::string("The value of \"") + key +
6197
0
                               "\" should be a string or an object");
6198
0
    }
6199
0
    auto typeStr = getType(v);
6200
0
    UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN;
6201
0
    if (typeStr == "LinearUnit") {
6202
0
        type = UnitOfMeasure::Type::LINEAR;
6203
0
    } else if (typeStr == "AngularUnit") {
6204
0
        type = UnitOfMeasure::Type::ANGULAR;
6205
0
    } else if (typeStr == "ScaleUnit") {
6206
0
        type = UnitOfMeasure::Type::SCALE;
6207
0
    } else if (typeStr == "TimeUnit") {
6208
0
        type = UnitOfMeasure::Type::TIME;
6209
0
    } else if (typeStr == "ParametricUnit") {
6210
0
        type = UnitOfMeasure::Type::PARAMETRIC;
6211
0
    } else if (typeStr == "Unit") {
6212
0
        type = UnitOfMeasure::Type::UNKNOWN;
6213
0
    } else {
6214
0
        throw ParsingException("Unsupported value of \"type\"");
6215
0
    }
6216
0
    auto nameStr = getName(v);
6217
0
    auto convFactor = getNumber(v, "conversion_factor");
6218
0
    std::string authorityStr;
6219
0
    std::string codeStr;
6220
0
    if (v.contains("authority") && v.contains("code")) {
6221
0
        authorityStr = getString(v, "authority");
6222
0
        auto code = v["code"];
6223
0
        if (code.is_string()) {
6224
0
            codeStr = code.get<std::string>();
6225
0
        } else if (code.is_number_integer()) {
6226
0
            codeStr = internal::toString(code.get<int>());
6227
0
        } else {
6228
0
            throw ParsingException("Unexpected type for value of \"code\"");
6229
0
        }
6230
0
    }
6231
0
    return UnitOfMeasure(nameStr, convFactor, type, authorityStr, codeStr);
6232
0
}
6233
6234
// ---------------------------------------------------------------------------
6235
6236
26
std::string JSONParser::getName(const json &j) { return getString(j, "name"); }
6237
6238
// ---------------------------------------------------------------------------
6239
6240
0
std::string JSONParser::getType(const json &j) { return getString(j, "type"); }
6241
6242
// ---------------------------------------------------------------------------
6243
6244
0
Length JSONParser::getLength(const json &j, const char *key) {
6245
0
    if (!j.contains(key)) {
6246
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6247
0
    }
6248
0
    auto v = j[key];
6249
0
    if (v.is_number()) {
6250
0
        return Length(v.get<double>(), UnitOfMeasure::METRE);
6251
0
    }
6252
0
    if (v.is_object()) {
6253
0
        return Length(getMeasure(v));
6254
0
    }
6255
0
    throw ParsingException(std::string("The value of \"") + key +
6256
0
                           "\" should be a number or an object");
6257
0
}
6258
6259
// ---------------------------------------------------------------------------
6260
6261
0
Measure JSONParser::getMeasure(const json &j) {
6262
0
    return Measure(getNumber(j, "value"), getUnit(j, "unit"));
6263
0
}
6264
6265
// ---------------------------------------------------------------------------
6266
6267
22
ObjectDomainPtr JSONParser::buildObjectDomain(const json &j) {
6268
22
    optional<std::string> scope;
6269
22
    if (j.contains("scope")) {
6270
0
        scope = getString(j, "scope");
6271
0
    }
6272
22
    std::string area;
6273
22
    if (j.contains("area")) {
6274
0
        area = getString(j, "area");
6275
0
    }
6276
22
    std::vector<GeographicExtentNNPtr> geogExtent;
6277
22
    if (j.contains("bbox")) {
6278
0
        auto bbox = getObject(j, "bbox");
6279
0
        double south = getNumber(bbox, "south_latitude");
6280
0
        double west = getNumber(bbox, "west_longitude");
6281
0
        double north = getNumber(bbox, "north_latitude");
6282
0
        double east = getNumber(bbox, "east_longitude");
6283
0
        try {
6284
0
            geogExtent.emplace_back(
6285
0
                GeographicBoundingBox::create(west, south, east, north));
6286
0
        } catch (const std::exception &e) {
6287
0
            throw ParsingException(
6288
0
                std::string("Invalid bbox node: ").append(e.what()));
6289
0
        }
6290
0
    }
6291
6292
22
    std::vector<VerticalExtentNNPtr> verticalExtent;
6293
22
    if (j.contains("vertical_extent")) {
6294
0
        const auto vertical_extent = getObject(j, "vertical_extent");
6295
0
        const auto min = getNumber(vertical_extent, "minimum");
6296
0
        const auto max = getNumber(vertical_extent, "maximum");
6297
0
        const auto unit = vertical_extent.contains("unit")
6298
0
                              ? getUnit(vertical_extent, "unit")
6299
0
                              : UnitOfMeasure::METRE;
6300
0
        verticalExtent.emplace_back(VerticalExtent::create(
6301
0
            min, max, util::nn_make_shared<UnitOfMeasure>(unit)));
6302
0
    }
6303
6304
22
    std::vector<TemporalExtentNNPtr> temporalExtent;
6305
22
    if (j.contains("temporal_extent")) {
6306
0
        const auto temporal_extent = getObject(j, "temporal_extent");
6307
0
        const auto start = getString(temporal_extent, "start");
6308
0
        const auto end = getString(temporal_extent, "end");
6309
0
        temporalExtent.emplace_back(TemporalExtent::create(start, end));
6310
0
    }
6311
6312
22
    if (scope.has_value() || !area.empty() || !geogExtent.empty() ||
6313
22
        !verticalExtent.empty() || !temporalExtent.empty()) {
6314
0
        util::optional<std::string> description;
6315
0
        if (!area.empty())
6316
0
            description = area;
6317
0
        ExtentPtr extent;
6318
0
        if (description.has_value() || !geogExtent.empty() ||
6319
0
            !verticalExtent.empty() || !temporalExtent.empty()) {
6320
0
            extent = Extent::create(description, geogExtent, verticalExtent,
6321
0
                                    temporalExtent)
6322
0
                         .as_nullable();
6323
0
        }
6324
0
        return ObjectDomain::create(scope, extent).as_nullable();
6325
0
    }
6326
22
    return nullptr;
6327
22
}
6328
6329
// ---------------------------------------------------------------------------
6330
6331
IdentifierNNPtr JSONParser::buildId(const json &parentJ, const json &j,
6332
0
                                    bool removeInverseOf) {
6333
6334
0
    PropertyMap propertiesId;
6335
0
    auto codeSpace(getString(j, "authority"));
6336
0
    if (removeInverseOf && starts_with(codeSpace, "INVERSE(") &&
6337
0
        codeSpace.back() == ')') {
6338
0
        codeSpace = codeSpace.substr(strlen("INVERSE("));
6339
0
        codeSpace.resize(codeSpace.size() - 1);
6340
0
    }
6341
6342
0
    std::string version;
6343
0
    if (j.contains("version")) {
6344
0
        auto versionJ = j["version"];
6345
0
        if (versionJ.is_string()) {
6346
0
            version = versionJ.get<std::string>();
6347
0
        } else if (versionJ.is_number()) {
6348
0
            const double dblVersion = versionJ.get<double>();
6349
0
            if (dblVersion >= std::numeric_limits<int>::min() &&
6350
0
                dblVersion <= std::numeric_limits<int>::max() &&
6351
0
                static_cast<int>(dblVersion) == dblVersion) {
6352
0
                version = internal::toString(static_cast<int>(dblVersion));
6353
0
            } else {
6354
0
                version = internal::toString(dblVersion, /*precision=*/15);
6355
0
            }
6356
0
        } else {
6357
0
            throw ParsingException("Unexpected type for value of \"version\"");
6358
0
        }
6359
0
    }
6360
6361
    // IAU + 2015 -> IAU_2015
6362
0
    if (dbContext_ && !version.empty()) {
6363
0
        std::string codeSpaceOut;
6364
0
        if (dbContext_->getVersionedAuthority(codeSpace, version,
6365
0
                                              codeSpaceOut)) {
6366
0
            codeSpace = std::move(codeSpaceOut);
6367
0
            version.clear();
6368
0
        }
6369
0
    }
6370
6371
0
    propertiesId.set(metadata::Identifier::CODESPACE_KEY, codeSpace);
6372
0
    propertiesId.set(metadata::Identifier::AUTHORITY_KEY, codeSpace);
6373
0
    if (!j.contains("code")) {
6374
0
        throw ParsingException("Missing \"code\" key");
6375
0
    }
6376
0
    std::string code;
6377
0
    auto codeJ = j["code"];
6378
0
    if (codeJ.is_string()) {
6379
0
        code = codeJ.get<std::string>();
6380
0
    } else if (codeJ.is_number_integer()) {
6381
0
        code = internal::toString(codeJ.get<int>());
6382
0
    } else {
6383
0
        throw ParsingException("Unexpected type for value of \"code\"");
6384
0
    }
6385
6386
    // Prior to PROJ 9.5, when synthetizing an ID for a CONVERSION UTM Zone
6387
    // south, we generated a wrong value. Auto-fix that
6388
0
    if (parentJ.contains("type") && getType(parentJ) == "Conversion" &&
6389
0
        codeSpace == Identifier::EPSG && parentJ.contains("name")) {
6390
0
        const auto parentNodeName(getName(parentJ));
6391
0
        if (ci_starts_with(parentNodeName, "UTM Zone ") &&
6392
0
            parentNodeName.find('S') != std::string::npos) {
6393
0
            const int nZone =
6394
0
                atoi(parentNodeName.c_str() + strlen("UTM Zone "));
6395
0
            if (nZone >= 1 && nZone <= 60) {
6396
0
                code = internal::toString(16100 + nZone);
6397
0
            }
6398
0
        }
6399
0
    }
6400
6401
0
    if (!version.empty()) {
6402
0
        propertiesId.set(Identifier::VERSION_KEY, version);
6403
0
    }
6404
6405
0
    if (j.contains("authority_citation")) {
6406
0
        propertiesId.set(Identifier::AUTHORITY_KEY,
6407
0
                         getString(j, "authority_citation"));
6408
0
    }
6409
6410
0
    if (j.contains("uri")) {
6411
0
        propertiesId.set(Identifier::URI_KEY, getString(j, "uri"));
6412
0
    }
6413
6414
0
    return Identifier::create(code, propertiesId);
6415
0
}
6416
6417
// ---------------------------------------------------------------------------
6418
6419
PropertyMap JSONParser::buildProperties(const json &j, bool removeInverseOf,
6420
26
                                        bool nameRequired) {
6421
26
    PropertyMap map;
6422
6423
26
    if (j.contains("name") || nameRequired) {
6424
26
        std::string name(getName(j));
6425
26
        if (removeInverseOf && starts_with(name, "Inverse of ")) {
6426
0
            name = name.substr(strlen("Inverse of "));
6427
0
        }
6428
26
        map.set(IdentifiedObject::NAME_KEY, name);
6429
26
    }
6430
6431
26
    if (j.contains("ids")) {
6432
0
        auto idsJ = getArray(j, "ids");
6433
0
        auto identifiers = ArrayOfBaseObject::create();
6434
0
        for (const auto &idJ : idsJ) {
6435
0
            if (!idJ.is_object()) {
6436
0
                throw ParsingException(
6437
0
                    "Unexpected type for value of \"ids\" child");
6438
0
            }
6439
0
            identifiers->add(buildId(j, idJ, removeInverseOf));
6440
0
        }
6441
0
        map.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
6442
26
    } else if (j.contains("id")) {
6443
0
        auto idJ = getObject(j, "id");
6444
0
        auto identifiers = ArrayOfBaseObject::create();
6445
0
        identifiers->add(buildId(j, idJ, removeInverseOf));
6446
0
        map.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
6447
0
    }
6448
6449
26
    if (j.contains("remarks")) {
6450
0
        map.set(IdentifiedObject::REMARKS_KEY, getString(j, "remarks"));
6451
0
    }
6452
6453
26
    if (j.contains("usages")) {
6454
0
        ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create();
6455
0
        auto usages = j["usages"];
6456
0
        if (!usages.is_array()) {
6457
0
            throw ParsingException("Unexpected type for value of \"usages\"");
6458
0
        }
6459
0
        for (const auto &usage : usages) {
6460
0
            if (!usage.is_object()) {
6461
0
                throw ParsingException(
6462
0
                    "Unexpected type for value of \"usages\" child");
6463
0
            }
6464
0
            auto objectDomain = buildObjectDomain(usage);
6465
0
            if (!objectDomain) {
6466
0
                throw ParsingException("missing children in \"usages\" child");
6467
0
            }
6468
0
            array->add(NN_NO_CHECK(objectDomain));
6469
0
        }
6470
0
        if (!array->empty()) {
6471
0
            map.set(ObjectUsage::OBJECT_DOMAIN_KEY, array);
6472
0
        }
6473
26
    } else {
6474
26
        auto objectDomain = buildObjectDomain(j);
6475
26
        if (objectDomain) {
6476
0
            map.set(ObjectUsage::OBJECT_DOMAIN_KEY, NN_NO_CHECK(objectDomain));
6477
0
        }
6478
26
    }
6479
6480
26
    return map;
6481
26
}
6482
6483
// ---------------------------------------------------------------------------
6484
6485
BaseObjectNNPtr JSONParser::create(const json &j)
6486
6487
112
{
6488
112
    if (!j.is_object()) {
6489
0
        throw ParsingException("JSON object expected");
6490
0
    }
6491
112
    auto type = getString(j, "type");
6492
112
    if (type == "GeographicCRS") {
6493
25
        return buildGeographicCRS(j);
6494
25
    }
6495
87
    if (type == "GeodeticCRS") {
6496
0
        return buildGeodeticCRS(j);
6497
0
    }
6498
87
    if (type == "ProjectedCRS") {
6499
0
        return buildProjectedCRS(j);
6500
0
    }
6501
87
    if (type == "VerticalCRS") {
6502
0
        return buildVerticalCRS(j);
6503
0
    }
6504
87
    if (type == "CompoundCRS") {
6505
0
        return buildCompoundCRS(j);
6506
0
    }
6507
87
    if (type == "BoundCRS") {
6508
0
        return buildBoundCRS(j);
6509
0
    }
6510
87
    if (type == "EngineeringCRS") {
6511
0
        return buildCRS<EngineeringCRS>(j, &JSONParser::buildEngineeringDatum);
6512
0
    }
6513
87
    if (type == "ParametricCRS") {
6514
0
        return buildCRS<ParametricCRS,
6515
0
                        decltype(&JSONParser::buildParametricDatum),
6516
0
                        ParametricCS>(j, &JSONParser::buildParametricDatum);
6517
0
    }
6518
87
    if (type == "TemporalCRS") {
6519
0
        return buildCRS<TemporalCRS, decltype(&JSONParser::buildTemporalDatum),
6520
0
                        TemporalCS>(j, &JSONParser::buildTemporalDatum);
6521
0
    }
6522
87
    if (type == "DerivedGeodeticCRS") {
6523
0
        auto baseCRSObj = create(getObject(j, "base_crs"));
6524
0
        auto baseCRS = util::nn_dynamic_pointer_cast<GeodeticCRS>(baseCRSObj);
6525
0
        if (!baseCRS) {
6526
0
            throw ParsingException("base_crs not of expected type");
6527
0
        }
6528
0
        auto cs = buildCS(getObject(j, "coordinate_system"));
6529
0
        auto conv = buildConversion(getObject(j, "conversion"));
6530
0
        auto csCartesian = util::nn_dynamic_pointer_cast<CartesianCS>(cs);
6531
0
        if (csCartesian)
6532
0
            return DerivedGeodeticCRS::create(buildProperties(j),
6533
0
                                              NN_NO_CHECK(baseCRS), conv,
6534
0
                                              NN_NO_CHECK(csCartesian));
6535
0
        auto csSpherical = util::nn_dynamic_pointer_cast<SphericalCS>(cs);
6536
0
        if (csSpherical)
6537
0
            return DerivedGeodeticCRS::create(buildProperties(j),
6538
0
                                              NN_NO_CHECK(baseCRS), conv,
6539
0
                                              NN_NO_CHECK(csSpherical));
6540
0
        throw ParsingException("coordinate_system not of expected type");
6541
0
    }
6542
87
    if (type == "DerivedGeographicCRS") {
6543
0
        return buildDerivedCRS<DerivedGeographicCRS, GeodeticCRS,
6544
0
                               EllipsoidalCS>(j);
6545
0
    }
6546
87
    if (type == "DerivedProjectedCRS") {
6547
0
        return buildDerivedCRS<DerivedProjectedCRS, ProjectedCRS>(j);
6548
0
    }
6549
87
    if (type == "DerivedVerticalCRS") {
6550
0
        return buildDerivedCRS<DerivedVerticalCRS, VerticalCRS, VerticalCS>(j);
6551
0
    }
6552
87
    if (type == "DerivedEngineeringCRS") {
6553
0
        return buildDerivedCRS<DerivedEngineeringCRS, EngineeringCRS>(j);
6554
0
    }
6555
87
    if (type == "DerivedParametricCRS") {
6556
0
        return buildDerivedCRS<DerivedParametricCRS, ParametricCRS,
6557
0
                               ParametricCS>(j);
6558
0
    }
6559
87
    if (type == "DerivedTemporalCRS") {
6560
0
        return buildDerivedCRS<DerivedTemporalCRS, TemporalCRS, TemporalCS>(j);
6561
0
    }
6562
87
    if (type == "DatumEnsemble") {
6563
0
        return buildDatumEnsemble(j);
6564
0
    }
6565
87
    if (type == "GeodeticReferenceFrame") {
6566
0
        return buildGeodeticReferenceFrame(j);
6567
0
    }
6568
87
    if (type == "VerticalReferenceFrame") {
6569
1
        return buildVerticalReferenceFrame(j);
6570
1
    }
6571
86
    if (type == "DynamicGeodeticReferenceFrame") {
6572
0
        return buildDynamicGeodeticReferenceFrame(j);
6573
0
    }
6574
86
    if (type == "DynamicVerticalReferenceFrame") {
6575
0
        return buildDynamicVerticalReferenceFrame(j);
6576
0
    }
6577
86
    if (type == "EngineeringDatum") {
6578
25
        return buildEngineeringDatum(j);
6579
25
    }
6580
61
    if (type == "ParametricDatum") {
6581
0
        return buildParametricDatum(j);
6582
0
    }
6583
61
    if (type == "TemporalDatum") {
6584
0
        return buildTemporalDatum(j);
6585
0
    }
6586
61
    if (type == "Ellipsoid") {
6587
0
        return buildEllipsoid(j);
6588
0
    }
6589
61
    if (type == "PrimeMeridian") {
6590
0
        return buildPrimeMeridian(j);
6591
0
    }
6592
61
    if (type == "CoordinateSystem") {
6593
0
        return buildCS(j);
6594
0
    }
6595
61
    if (type == "Conversion") {
6596
0
        return buildConversion(j);
6597
0
    }
6598
61
    if (type == "Transformation") {
6599
0
        return buildTransformation(j);
6600
0
    }
6601
61
    if (type == "PointMotionOperation") {
6602
0
        return buildPointMotionOperation(j);
6603
0
    }
6604
61
    if (type == "ConcatenatedOperation") {
6605
0
        return buildConcatenatedOperation(j);
6606
0
    }
6607
61
    if (type == "CoordinateMetadata") {
6608
0
        return buildCoordinateMetadata(j);
6609
0
    }
6610
61
    if (type == "Axis") {
6611
0
        return buildAxis(j);
6612
0
    }
6613
61
    throw ParsingException("Unsupported value of \"type\"");
6614
61
}
6615
6616
// ---------------------------------------------------------------------------
6617
6618
void JSONParser::buildGeodeticDatumOrDatumEnsemble(
6619
    const json &j, GeodeticReferenceFramePtr &datum,
6620
25
    DatumEnsemblePtr &datumEnsemble) {
6621
25
    if (j.contains("datum")) {
6622
22
        auto datumJ = getObject(j, "datum");
6623
6624
22
        if (j.contains("deformation_models")) {
6625
0
            auto deformationModelsJ = getArray(j, "deformation_models");
6626
0
            if (!deformationModelsJ.empty()) {
6627
0
                const auto &deformationModelJ = deformationModelsJ[0];
6628
0
                deformationModelName_ = getString(deformationModelJ, "name");
6629
                // We can handle only one for now
6630
0
            }
6631
0
        }
6632
6633
22
        datum = util::nn_dynamic_pointer_cast<GeodeticReferenceFrame>(
6634
22
            create(datumJ));
6635
22
        if (!datum) {
6636
0
            throw ParsingException("datum of wrong type");
6637
0
        }
6638
6639
22
        deformationModelName_.clear();
6640
22
    } else {
6641
3
        datumEnsemble =
6642
3
            buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable();
6643
3
    }
6644
25
}
6645
6646
// ---------------------------------------------------------------------------
6647
6648
25
GeographicCRSNNPtr JSONParser::buildGeographicCRS(const json &j) {
6649
25
    GeodeticReferenceFramePtr datum;
6650
25
    DatumEnsemblePtr datumEnsemble;
6651
25
    buildGeodeticDatumOrDatumEnsemble(j, datum, datumEnsemble);
6652
25
    auto csJ = getObject(j, "coordinate_system");
6653
25
    auto ellipsoidalCS =
6654
25
        util::nn_dynamic_pointer_cast<EllipsoidalCS>(buildCS(csJ));
6655
25
    if (!ellipsoidalCS) {
6656
0
        throw ParsingException("expected an ellipsoidal CS");
6657
0
    }
6658
25
    return GeographicCRS::create(buildProperties(j), datum, datumEnsemble,
6659
25
                                 NN_NO_CHECK(ellipsoidalCS));
6660
25
}
6661
6662
// ---------------------------------------------------------------------------
6663
6664
0
GeodeticCRSNNPtr JSONParser::buildGeodeticCRS(const json &j) {
6665
0
    GeodeticReferenceFramePtr datum;
6666
0
    DatumEnsemblePtr datumEnsemble;
6667
0
    buildGeodeticDatumOrDatumEnsemble(j, datum, datumEnsemble);
6668
0
    auto csJ = getObject(j, "coordinate_system");
6669
0
    auto cs = buildCS(csJ);
6670
0
    auto props = buildProperties(j);
6671
0
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
6672
0
    if (cartesianCS) {
6673
0
        if (cartesianCS->axisList().size() != 3) {
6674
0
            throw ParsingException(
6675
0
                "Cartesian CS for a GeodeticCRS should have 3 axis");
6676
0
        }
6677
0
        try {
6678
0
            return GeodeticCRS::create(props, datum, datumEnsemble,
6679
0
                                       NN_NO_CHECK(cartesianCS));
6680
0
        } catch (const util::Exception &e) {
6681
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
6682
0
                                   e.what());
6683
0
        }
6684
0
    }
6685
6686
0
    auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
6687
0
    if (sphericalCS) {
6688
0
        try {
6689
0
            return GeodeticCRS::create(props, datum, datumEnsemble,
6690
0
                                       NN_NO_CHECK(sphericalCS));
6691
0
        } catch (const util::Exception &e) {
6692
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
6693
0
                                   e.what());
6694
0
        }
6695
0
    }
6696
0
    throw ParsingException("expected a Cartesian or spherical CS");
6697
0
}
6698
6699
// ---------------------------------------------------------------------------
6700
6701
0
ProjectedCRSNNPtr JSONParser::buildProjectedCRS(const json &j) {
6702
0
    auto jBaseCRS = getObject(j, "base_crs");
6703
0
    auto jBaseCS = getObject(jBaseCRS, "coordinate_system");
6704
0
    auto baseCS = buildCS(jBaseCS);
6705
0
    auto baseCRS = dynamic_cast<EllipsoidalCS *>(baseCS.get()) != nullptr
6706
0
                       ? util::nn_static_pointer_cast<GeodeticCRS>(
6707
0
                             buildGeographicCRS(jBaseCRS))
6708
0
                       : buildGeodeticCRS(jBaseCRS);
6709
0
    auto csJ = getObject(j, "coordinate_system");
6710
0
    auto cartesianCS = util::nn_dynamic_pointer_cast<CartesianCS>(buildCS(csJ));
6711
0
    if (!cartesianCS) {
6712
0
        throw ParsingException("expected a Cartesian CS");
6713
0
    }
6714
0
    auto conv = buildConversion(getObject(j, "conversion"));
6715
0
    return ProjectedCRS::create(buildProperties(j), baseCRS, conv,
6716
0
                                NN_NO_CHECK(cartesianCS));
6717
0
}
6718
6719
// ---------------------------------------------------------------------------
6720
6721
0
VerticalCRSNNPtr JSONParser::buildVerticalCRS(const json &j) {
6722
0
    VerticalReferenceFramePtr datum;
6723
0
    DatumEnsemblePtr datumEnsemble;
6724
0
    if (j.contains("datum")) {
6725
0
        auto datumJ = getObject(j, "datum");
6726
6727
0
        if (j.contains("deformation_models")) {
6728
0
            auto deformationModelsJ = getArray(j, "deformation_models");
6729
0
            if (!deformationModelsJ.empty()) {
6730
0
                const auto &deformationModelJ = deformationModelsJ[0];
6731
0
                deformationModelName_ = getString(deformationModelJ, "name");
6732
                // We can handle only one for now
6733
0
            }
6734
0
        }
6735
6736
0
        datum = util::nn_dynamic_pointer_cast<VerticalReferenceFrame>(
6737
0
            create(datumJ));
6738
0
        if (!datum) {
6739
0
            throw ParsingException("datum of wrong type");
6740
0
        }
6741
0
    } else {
6742
0
        datumEnsemble =
6743
0
            buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable();
6744
0
    }
6745
0
    auto csJ = getObject(j, "coordinate_system");
6746
0
    auto verticalCS = util::nn_dynamic_pointer_cast<VerticalCS>(buildCS(csJ));
6747
0
    if (!verticalCS) {
6748
0
        throw ParsingException("expected a vertical CS");
6749
0
    }
6750
6751
0
    const auto buildGeoidModel = [this, &datum, &datumEnsemble,
6752
0
                                  &verticalCS](const json &geoidModelJ) {
6753
0
        auto propsModel = buildProperties(geoidModelJ);
6754
0
        const auto dummyCRS = VerticalCRS::create(
6755
0
            PropertyMap(), datum, datumEnsemble, NN_NO_CHECK(verticalCS));
6756
0
        CRSPtr interpolationCRS;
6757
0
        if (geoidModelJ.contains("interpolation_crs")) {
6758
0
            auto interpolationCRSJ =
6759
0
                getObject(geoidModelJ, "interpolation_crs");
6760
0
            interpolationCRS = buildCRS(interpolationCRSJ).as_nullable();
6761
0
        }
6762
0
        return Transformation::create(
6763
0
            propsModel, dummyCRS,
6764
0
            GeographicCRS::EPSG_4979, // arbitrarily chosen. Ignored,
6765
0
            interpolationCRS,
6766
0
            OperationMethod::create(PropertyMap(),
6767
0
                                    std::vector<OperationParameterNNPtr>()),
6768
0
            {}, {});
6769
0
    };
6770
6771
0
    auto props = buildProperties(j);
6772
0
    if (j.contains("geoid_model")) {
6773
0
        auto geoidModelJ = getObject(j, "geoid_model");
6774
0
        props.set("GEOID_MODEL", buildGeoidModel(geoidModelJ));
6775
0
    } else if (j.contains("geoid_models")) {
6776
0
        auto geoidModelsJ = getArray(j, "geoid_models");
6777
0
        auto geoidModels = ArrayOfBaseObject::create();
6778
0
        for (const auto &geoidModelJ : geoidModelsJ) {
6779
0
            geoidModels->add(buildGeoidModel(geoidModelJ));
6780
0
        }
6781
0
        props.set("GEOID_MODEL", geoidModels);
6782
0
    }
6783
6784
0
    return VerticalCRS::create(props, datum, datumEnsemble,
6785
0
                               NN_NO_CHECK(verticalCS));
6786
0
}
6787
6788
// ---------------------------------------------------------------------------
6789
6790
0
CRSNNPtr JSONParser::buildCRS(const json &j) {
6791
0
    auto crs = util::nn_dynamic_pointer_cast<CRS>(create(j));
6792
0
    if (crs) {
6793
0
        return NN_NO_CHECK(crs);
6794
0
    }
6795
0
    throw ParsingException("Object is not a CRS");
6796
0
}
6797
6798
// ---------------------------------------------------------------------------
6799
6800
0
CompoundCRSNNPtr JSONParser::buildCompoundCRS(const json &j) {
6801
0
    auto componentsJ = getArray(j, "components");
6802
0
    std::vector<CRSNNPtr> components;
6803
0
    for (const auto &componentJ : componentsJ) {
6804
0
        if (!componentJ.is_object()) {
6805
0
            throw ParsingException(
6806
0
                "Unexpected type for a \"components\" child");
6807
0
        }
6808
0
        components.push_back(buildCRS(componentJ));
6809
0
    }
6810
0
    return CompoundCRS::create(buildProperties(j), components);
6811
0
}
6812
6813
// ---------------------------------------------------------------------------
6814
6815
0
ConversionNNPtr JSONParser::buildConversion(const json &j) {
6816
0
    auto methodJ = getObject(j, "method");
6817
0
    auto convProps = buildProperties(j);
6818
0
    auto methodProps = buildProperties(methodJ);
6819
0
    if (!j.contains("parameters")) {
6820
0
        return Conversion::create(convProps, methodProps, {}, {});
6821
0
    }
6822
6823
0
    auto parametersJ = getArray(j, "parameters");
6824
0
    std::vector<OperationParameterNNPtr> parameters;
6825
0
    std::vector<ParameterValueNNPtr> values;
6826
0
    for (const auto &param : parametersJ) {
6827
0
        if (!param.is_object()) {
6828
0
            throw ParsingException(
6829
0
                "Unexpected type for a \"parameters\" child");
6830
0
        }
6831
0
        parameters.emplace_back(
6832
0
            OperationParameter::create(buildProperties(param)));
6833
0
        if (isIntegerParameter(parameters.back())) {
6834
0
            values.emplace_back(
6835
0
                ParameterValue::create(getInteger(param, "value")));
6836
0
        } else {
6837
0
            values.emplace_back(ParameterValue::create(getMeasure(param)));
6838
0
        }
6839
0
    }
6840
6841
0
    auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
6842
0
        dbContext_, parameters, values);
6843
6844
0
    std::string convName;
6845
0
    std::string methodName;
6846
0
    if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) &&
6847
0
        methodProps.getStringValue(IdentifiedObject::NAME_KEY, methodName) &&
6848
0
        starts_with(convName, "Inverse of ") &&
6849
0
        starts_with(methodName, "Inverse of ")) {
6850
6851
0
        auto invConvProps = buildProperties(j, true);
6852
0
        auto invMethodProps = buildProperties(methodJ, true);
6853
0
        auto conv = NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(
6854
0
            Conversion::create(invConvProps, invMethodProps, parameters, values)
6855
0
                ->inverse()));
6856
0
        if (interpolationCRS)
6857
0
            conv->setInterpolationCRS(interpolationCRS);
6858
0
        return conv;
6859
0
    }
6860
0
    auto conv = Conversion::create(convProps, methodProps, parameters, values);
6861
0
    if (interpolationCRS)
6862
0
        conv->setInterpolationCRS(interpolationCRS);
6863
0
    return conv;
6864
0
}
6865
6866
// ---------------------------------------------------------------------------
6867
6868
0
BoundCRSNNPtr JSONParser::buildBoundCRS(const json &j) {
6869
6870
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6871
0
    auto targetCRS = buildCRS(getObject(j, "target_crs"));
6872
0
    auto transformationJ = getObject(j, "transformation");
6873
0
    auto methodJ = getObject(transformationJ, "method");
6874
0
    auto parametersJ = getArray(transformationJ, "parameters");
6875
0
    std::vector<OperationParameterNNPtr> parameters;
6876
0
    std::vector<ParameterValueNNPtr> values;
6877
0
    for (const auto &param : parametersJ) {
6878
0
        if (!param.is_object()) {
6879
0
            throw ParsingException(
6880
0
                "Unexpected type for a \"parameters\" child");
6881
0
        }
6882
0
        parameters.emplace_back(
6883
0
            OperationParameter::create(buildProperties(param)));
6884
0
        if (param.contains("value")) {
6885
0
            auto v = param["value"];
6886
0
            if (v.is_string()) {
6887
0
                values.emplace_back(
6888
0
                    ParameterValue::createFilename(v.get<std::string>()));
6889
0
                continue;
6890
0
            }
6891
0
        }
6892
0
        values.emplace_back(ParameterValue::create(getMeasure(param)));
6893
0
    }
6894
6895
0
    const auto transformation = [&]() {
6896
        // Unofficial extension / mostly for testing purposes.
6897
        // Allow to explicitly specify the source_crs of the transformation of
6898
        // the boundCRS if it is not the source_crs of the BoundCRS. Cf
6899
        // https://github.com/OSGeo/PROJ/issues/3428 use case
6900
0
        if (transformationJ.contains("source_crs")) {
6901
0
            auto sourceTransformationCRS =
6902
0
                buildCRS(getObject(transformationJ, "source_crs"));
6903
0
            auto interpolationCRS =
6904
0
                dealWithEPSGCodeForInterpolationCRSParameter(
6905
0
                    dbContext_, parameters, values);
6906
0
            return Transformation::create(
6907
0
                buildProperties(transformationJ), sourceTransformationCRS,
6908
0
                targetCRS, interpolationCRS, buildProperties(methodJ),
6909
0
                parameters, values, std::vector<PositionalAccuracyNNPtr>());
6910
0
        }
6911
6912
0
        return buildTransformationForBoundCRS(
6913
0
            dbContext_, buildProperties(transformationJ),
6914
0
            buildProperties(methodJ), sourceCRS, targetCRS, parameters, values);
6915
0
    }();
6916
6917
0
    return BoundCRS::create(buildProperties(j,
6918
0
                                            /* removeInverseOf= */ false,
6919
0
                                            /* nameRequired=*/false),
6920
0
                            sourceCRS, targetCRS, transformation);
6921
0
}
6922
6923
// ---------------------------------------------------------------------------
6924
6925
0
TransformationNNPtr JSONParser::buildTransformation(const json &j) {
6926
6927
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6928
0
    auto targetCRS = buildCRS(getObject(j, "target_crs"));
6929
0
    auto methodJ = getObject(j, "method");
6930
0
    auto parametersJ = getArray(j, "parameters");
6931
0
    std::vector<OperationParameterNNPtr> parameters;
6932
0
    std::vector<ParameterValueNNPtr> values;
6933
0
    for (const auto &param : parametersJ) {
6934
0
        if (!param.is_object()) {
6935
0
            throw ParsingException(
6936
0
                "Unexpected type for a \"parameters\" child");
6937
0
        }
6938
0
        parameters.emplace_back(
6939
0
            OperationParameter::create(buildProperties(param)));
6940
0
        if (param.contains("value")) {
6941
0
            auto v = param["value"];
6942
0
            if (v.is_string()) {
6943
0
                values.emplace_back(
6944
0
                    ParameterValue::createFilename(v.get<std::string>()));
6945
0
                continue;
6946
0
            }
6947
0
        }
6948
0
        values.emplace_back(ParameterValue::create(getMeasure(param)));
6949
0
    }
6950
0
    CRSPtr interpolationCRS;
6951
0
    if (j.contains("interpolation_crs")) {
6952
0
        interpolationCRS =
6953
0
            buildCRS(getObject(j, "interpolation_crs")).as_nullable();
6954
0
    }
6955
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
6956
0
    if (j.contains("accuracy")) {
6957
0
        accuracies.push_back(
6958
0
            PositionalAccuracy::create(getString(j, "accuracy")));
6959
0
    }
6960
6961
0
    return Transformation::create(buildProperties(j), sourceCRS, targetCRS,
6962
0
                                  interpolationCRS, buildProperties(methodJ),
6963
0
                                  parameters, values, accuracies);
6964
0
}
6965
6966
// ---------------------------------------------------------------------------
6967
6968
0
PointMotionOperationNNPtr JSONParser::buildPointMotionOperation(const json &j) {
6969
6970
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6971
0
    auto methodJ = getObject(j, "method");
6972
0
    auto parametersJ = getArray(j, "parameters");
6973
0
    std::vector<OperationParameterNNPtr> parameters;
6974
0
    std::vector<ParameterValueNNPtr> values;
6975
0
    for (const auto &param : parametersJ) {
6976
0
        if (!param.is_object()) {
6977
0
            throw ParsingException(
6978
0
                "Unexpected type for a \"parameters\" child");
6979
0
        }
6980
0
        parameters.emplace_back(
6981
0
            OperationParameter::create(buildProperties(param)));
6982
0
        if (param.contains("value")) {
6983
0
            auto v = param["value"];
6984
0
            if (v.is_string()) {
6985
0
                values.emplace_back(
6986
0
                    ParameterValue::createFilename(v.get<std::string>()));
6987
0
                continue;
6988
0
            }
6989
0
        }
6990
0
        values.emplace_back(ParameterValue::create(getMeasure(param)));
6991
0
    }
6992
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
6993
0
    if (j.contains("accuracy")) {
6994
0
        accuracies.push_back(
6995
0
            PositionalAccuracy::create(getString(j, "accuracy")));
6996
0
    }
6997
6998
0
    return PointMotionOperation::create(buildProperties(j), sourceCRS,
6999
0
                                        buildProperties(methodJ), parameters,
7000
0
                                        values, accuracies);
7001
0
}
7002
7003
// ---------------------------------------------------------------------------
7004
7005
ConcatenatedOperationNNPtr
7006
0
JSONParser::buildConcatenatedOperation(const json &j) {
7007
7008
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
7009
0
    auto targetCRS = buildCRS(getObject(j, "target_crs"));
7010
0
    auto stepsJ = getArray(j, "steps");
7011
0
    std::vector<CoordinateOperationNNPtr> operations;
7012
0
    for (const auto &stepJ : stepsJ) {
7013
0
        if (!stepJ.is_object()) {
7014
0
            throw ParsingException("Unexpected type for a \"steps\" child");
7015
0
        }
7016
0
        auto op = nn_dynamic_pointer_cast<CoordinateOperation>(create(stepJ));
7017
0
        if (!op) {
7018
0
            throw ParsingException("Invalid content in a \"steps\" child");
7019
0
        }
7020
0
        operations.emplace_back(NN_NO_CHECK(op));
7021
0
    }
7022
7023
0
    ConcatenatedOperation::fixSteps(sourceCRS, targetCRS, operations,
7024
0
                                    dbContext_,
7025
0
                                    /* fixDirectionAllowed = */ true);
7026
7027
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
7028
0
    if (j.contains("accuracy")) {
7029
0
        accuracies.push_back(
7030
0
            PositionalAccuracy::create(getString(j, "accuracy")));
7031
0
    }
7032
7033
0
    try {
7034
0
        return ConcatenatedOperation::create(buildProperties(j), operations,
7035
0
                                             accuracies);
7036
0
    } catch (const InvalidOperation &e) {
7037
0
        throw ParsingException(
7038
0
            std::string("Cannot build concatenated operation: ") + e.what());
7039
0
    }
7040
0
}
7041
7042
// ---------------------------------------------------------------------------
7043
7044
0
CoordinateMetadataNNPtr JSONParser::buildCoordinateMetadata(const json &j) {
7045
7046
0
    auto crs = buildCRS(getObject(j, "crs"));
7047
0
    if (j.contains("coordinateEpoch")) {
7048
0
        auto jCoordinateEpoch = j["coordinateEpoch"];
7049
0
        if (jCoordinateEpoch.is_number()) {
7050
0
            return CoordinateMetadata::create(
7051
0
                crs, jCoordinateEpoch.get<double>(), dbContext_);
7052
0
        }
7053
0
        throw ParsingException(
7054
0
            "Unexpected type for value of \"coordinateEpoch\"");
7055
0
    }
7056
0
    return CoordinateMetadata::create(crs);
7057
0
}
7058
7059
// ---------------------------------------------------------------------------
7060
7061
0
MeridianNNPtr JSONParser::buildMeridian(const json &j) {
7062
0
    if (!j.contains("longitude")) {
7063
0
        throw ParsingException("Missing \"longitude\" key");
7064
0
    }
7065
0
    auto longitude = j["longitude"];
7066
0
    if (longitude.is_number()) {
7067
0
        return Meridian::create(
7068
0
            Angle(longitude.get<double>(), UnitOfMeasure::DEGREE));
7069
0
    } else if (longitude.is_object()) {
7070
0
        return Meridian::create(Angle(getMeasure(longitude)));
7071
0
    }
7072
0
    throw ParsingException("Unexpected type for value of \"longitude\"");
7073
0
}
7074
7075
// ---------------------------------------------------------------------------
7076
7077
0
CoordinateSystemAxisNNPtr JSONParser::buildAxis(const json &j) {
7078
0
    auto dirString = getString(j, "direction");
7079
0
    auto abbreviation = getString(j, "abbreviation");
7080
0
    const UnitOfMeasure unit(
7081
0
        j.contains("unit")
7082
0
            ? getUnit(j, "unit")
7083
0
            : UnitOfMeasure(std::string(), 1.0, UnitOfMeasure::Type::NONE));
7084
0
    auto direction = AxisDirection::valueOf(dirString);
7085
0
    if (!direction) {
7086
0
        throw ParsingException(concat("unhandled axis direction: ", dirString));
7087
0
    }
7088
0
    auto meridian = j.contains("meridian")
7089
0
                        ? buildMeridian(getObject(j, "meridian")).as_nullable()
7090
0
                        : nullptr;
7091
7092
0
    util::optional<double> minVal;
7093
0
    if (j.contains("minimum_value")) {
7094
0
        minVal = getNumber(j, "minimum_value");
7095
0
    }
7096
7097
0
    util::optional<double> maxVal;
7098
0
    if (j.contains("maximum_value")) {
7099
0
        maxVal = getNumber(j, "maximum_value");
7100
0
    }
7101
7102
0
    util::optional<RangeMeaning> rangeMeaning;
7103
0
    if (j.contains("range_meaning")) {
7104
0
        const auto val = getString(j, "range_meaning");
7105
0
        const RangeMeaning *meaning = RangeMeaning::valueOf(val);
7106
0
        if (meaning == nullptr) {
7107
0
            throw ParsingException(
7108
0
                concat("buildAxis: invalid range_meaning value: ", val));
7109
0
        }
7110
0
        rangeMeaning = util::optional<RangeMeaning>(*meaning);
7111
0
    }
7112
7113
0
    return CoordinateSystemAxis::create(buildProperties(j), abbreviation,
7114
0
                                        *direction, unit, minVal, maxVal,
7115
0
                                        rangeMeaning, meridian);
7116
0
}
7117
7118
// ---------------------------------------------------------------------------
7119
7120
0
CoordinateSystemNNPtr JSONParser::buildCS(const json &j) {
7121
0
    auto subtype = getString(j, "subtype");
7122
0
    if (!j.contains("axis")) {
7123
0
        throw ParsingException("Missing \"axis\" key");
7124
0
    }
7125
0
    auto jAxisList = j["axis"];
7126
0
    if (!jAxisList.is_array()) {
7127
0
        throw ParsingException("Unexpected type for value of \"axis\"");
7128
0
    }
7129
0
    std::vector<CoordinateSystemAxisNNPtr> axisList;
7130
0
    for (const auto &axis : jAxisList) {
7131
0
        if (!axis.is_object()) {
7132
0
            throw ParsingException(
7133
0
                "Unexpected type for value of a \"axis\" member");
7134
0
        }
7135
0
        axisList.emplace_back(buildAxis(axis));
7136
0
    }
7137
0
    const PropertyMap &csMap = emptyPropertyMap;
7138
0
    const auto axisCount = axisList.size();
7139
0
    if (subtype == EllipsoidalCS::WKT2_TYPE) {
7140
0
        if (axisCount == 2) {
7141
0
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1]);
7142
0
        }
7143
0
        if (axisCount == 3) {
7144
0
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1],
7145
0
                                         axisList[2]);
7146
0
        }
7147
0
        throw ParsingException("Expected 2 or 3 axis");
7148
0
    }
7149
0
    if (subtype == CartesianCS::WKT2_TYPE) {
7150
0
        if (axisCount == 2) {
7151
0
            if (axisList[0]->unit() != axisList[1]->unit()) {
7152
0
                emitRecoverableWarning(
7153
0
                    "All axis of a CartesianCS must have the same unit");
7154
0
            }
7155
0
            return CartesianCS::create(csMap, axisList[0], axisList[1],
7156
0
                                       /* enforceSameUnit = */ false);
7157
0
        }
7158
0
        if (axisCount == 3) {
7159
0
            if (axisList[0]->unit() != axisList[1]->unit() ||
7160
0
                axisList[0]->unit() != axisList[2]->unit()) {
7161
0
                emitRecoverableWarning(
7162
0
                    "All axis of a CartesianCS must have the same unit");
7163
0
            }
7164
0
            return CartesianCS::create(csMap, axisList[0], axisList[1],
7165
0
                                       axisList[2],
7166
0
                                       /* enforceSameUnit = */ false);
7167
0
        }
7168
0
        throw ParsingException("Expected 2 or 3 axis");
7169
0
    }
7170
0
    if (subtype == AffineCS::WKT2_TYPE) {
7171
0
        if (axisCount == 2) {
7172
0
            return AffineCS::create(csMap, axisList[0], axisList[1]);
7173
0
        }
7174
0
        if (axisCount == 3) {
7175
0
            return AffineCS::create(csMap, axisList[0], axisList[1],
7176
0
                                    axisList[2]);
7177
0
        }
7178
0
        throw ParsingException("Expected 2 or 3 axis");
7179
0
    }
7180
0
    if (subtype == VerticalCS::WKT2_TYPE) {
7181
0
        if (axisCount == 1) {
7182
0
            return VerticalCS::create(csMap, axisList[0]);
7183
0
        }
7184
0
        throw ParsingException("Expected 1 axis");
7185
0
    }
7186
0
    if (subtype == SphericalCS::WKT2_TYPE) {
7187
0
        if (axisCount == 2) {
7188
            // Extension to ISO19111 to support (planet)-ocentric CS with
7189
            // geocentric latitude
7190
0
            return SphericalCS::create(csMap, axisList[0], axisList[1]);
7191
0
        } else if (axisCount == 3) {
7192
0
            return SphericalCS::create(csMap, axisList[0], axisList[1],
7193
0
                                       axisList[2]);
7194
0
        }
7195
0
        throw ParsingException("Expected 2 or 3 axis");
7196
0
    }
7197
0
    if (subtype == OrdinalCS::WKT2_TYPE) {
7198
0
        return OrdinalCS::create(csMap, axisList);
7199
0
    }
7200
0
    if (subtype == ParametricCS::WKT2_TYPE) {
7201
0
        if (axisCount == 1) {
7202
0
            return ParametricCS::create(csMap, axisList[0]);
7203
0
        }
7204
0
        throw ParsingException("Expected 1 axis");
7205
0
    }
7206
0
    if (subtype == DateTimeTemporalCS::WKT2_2019_TYPE) {
7207
0
        if (axisCount == 1) {
7208
0
            return DateTimeTemporalCS::create(csMap, axisList[0]);
7209
0
        }
7210
0
        throw ParsingException("Expected 1 axis");
7211
0
    }
7212
0
    if (subtype == TemporalCountCS::WKT2_2019_TYPE) {
7213
0
        if (axisCount == 1) {
7214
0
            return TemporalCountCS::create(csMap, axisList[0]);
7215
0
        }
7216
0
        throw ParsingException("Expected 1 axis");
7217
0
    }
7218
0
    if (subtype == TemporalMeasureCS::WKT2_2019_TYPE) {
7219
0
        if (axisCount == 1) {
7220
0
            return TemporalMeasureCS::create(csMap, axisList[0]);
7221
0
        }
7222
0
        throw ParsingException("Expected 1 axis");
7223
0
    }
7224
0
    throw ParsingException("Unhandled value for subtype");
7225
0
}
7226
7227
// ---------------------------------------------------------------------------
7228
7229
0
DatumEnsembleNNPtr JSONParser::buildDatumEnsemble(const json &j) {
7230
0
    std::vector<DatumNNPtr> datums;
7231
0
    if (j.contains("members")) {
7232
0
        auto membersJ = getArray(j, "members");
7233
0
        const bool hasEllipsoid(j.contains("ellipsoid"));
7234
0
        for (const auto &memberJ : membersJ) {
7235
0
            if (!memberJ.is_object()) {
7236
0
                throw ParsingException(
7237
0
                    "Unexpected type for value of a \"members\" member");
7238
0
            }
7239
0
            auto datumName(getName(memberJ));
7240
0
            bool datumAdded = false;
7241
0
            if (dbContext_ && memberJ.contains("id")) {
7242
0
                auto id = getObject(memberJ, "id");
7243
0
                auto authority = getString(id, "authority");
7244
0
                auto authFactory = AuthorityFactory::create(
7245
0
                    NN_NO_CHECK(dbContext_), authority);
7246
0
                auto code = id["code"];
7247
0
                std::string codeStr;
7248
0
                if (code.is_string()) {
7249
0
                    codeStr = code.get<std::string>();
7250
0
                } else if (code.is_number_integer()) {
7251
0
                    codeStr = internal::toString(code.get<int>());
7252
0
                } else {
7253
0
                    throw ParsingException(
7254
0
                        "Unexpected type for value of \"code\"");
7255
0
                }
7256
0
                try {
7257
0
                    datums.push_back(authFactory->createDatum(codeStr));
7258
0
                    datumAdded = true;
7259
0
                } catch (const std::exception &) {
7260
                    // Silently ignore, as this isn't necessary an error.
7261
                    // If an older PROJ version parses a DatumEnsemble object of
7262
                    // a more recent PROJ version where the datum ensemble got
7263
                    // a new member, it might be unknown from the older PROJ.
7264
0
                }
7265
0
            }
7266
7267
0
            if (dbContext_ && !datumAdded) {
7268
0
                auto authFactory = AuthorityFactory::create(
7269
0
                    NN_NO_CHECK(dbContext_), std::string());
7270
0
                auto list = authFactory->createObjectsFromName(
7271
0
                    datumName, {AuthorityFactory::ObjectType::DATUM},
7272
0
                    false /* approximate=false*/);
7273
0
                if (!list.empty()) {
7274
0
                    auto datum =
7275
0
                        util::nn_dynamic_pointer_cast<Datum>(list.front());
7276
0
                    if (!datum)
7277
0
                        throw ParsingException(
7278
0
                            "DatumEnsemble member is not a datum");
7279
0
                    datums.push_back(NN_NO_CHECK(datum));
7280
0
                    datumAdded = true;
7281
0
                }
7282
0
            }
7283
7284
0
            if (!datumAdded) {
7285
                // Fallback if no db match
7286
0
                if (hasEllipsoid) {
7287
0
                    datums.emplace_back(GeodeticReferenceFrame::create(
7288
0
                        buildProperties(memberJ),
7289
0
                        buildEllipsoid(getObject(j, "ellipsoid")),
7290
0
                        optional<std::string>(), PrimeMeridian::GREENWICH));
7291
0
                } else {
7292
0
                    datums.emplace_back(VerticalReferenceFrame::create(
7293
0
                        buildProperties(memberJ)));
7294
0
                }
7295
0
            }
7296
0
        }
7297
0
    } else {
7298
0
        auto name = getString(j, "name");
7299
0
        if (dbContext_) {
7300
0
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
7301
0
                                                        std::string());
7302
0
            auto res = authFactory->createObjectsFromName(
7303
0
                name, {AuthorityFactory::ObjectType::DATUM_ENSEMBLE}, true, 1);
7304
0
            if (res.size() == 1) {
7305
0
                auto datumEnsemble =
7306
0
                    dynamic_cast<const DatumEnsemble *>(res.front().get());
7307
0
                if (datumEnsemble) {
7308
0
                    datums = datumEnsemble->datums();
7309
0
                }
7310
0
            } else {
7311
0
                throw ParsingException(
7312
0
                    "No entry for datum ensemble '" + name +
7313
0
                    "' in database, and no explicit member specified");
7314
0
            }
7315
0
        } else {
7316
0
            throw ParsingException("Datum ensemble '" + name +
7317
0
                                   "' has no explicit member specified and no "
7318
0
                                   "connection to database");
7319
0
        }
7320
0
    }
7321
7322
0
    return DatumEnsemble::create(
7323
0
        buildProperties(j), datums,
7324
0
        PositionalAccuracy::create(getString(j, "accuracy")));
7325
0
}
7326
7327
// ---------------------------------------------------------------------------
7328
7329
GeodeticReferenceFrameNNPtr
7330
0
JSONParser::buildGeodeticReferenceFrame(const json &j) {
7331
0
    auto ellipsoidJ = getObject(j, "ellipsoid");
7332
0
    auto pm = j.contains("prime_meridian")
7333
0
                  ? buildPrimeMeridian(getObject(j, "prime_meridian"))
7334
0
                  : PrimeMeridian::GREENWICH;
7335
0
    return GeodeticReferenceFrame::create(buildProperties(j),
7336
0
                                          buildEllipsoid(ellipsoidJ),
7337
0
                                          getAnchor(j), getAnchorEpoch(j), pm);
7338
0
}
7339
7340
// ---------------------------------------------------------------------------
7341
7342
DynamicGeodeticReferenceFrameNNPtr
7343
0
JSONParser::buildDynamicGeodeticReferenceFrame(const json &j) {
7344
0
    auto ellipsoidJ = getObject(j, "ellipsoid");
7345
0
    auto pm = j.contains("prime_meridian")
7346
0
                  ? buildPrimeMeridian(getObject(j, "prime_meridian"))
7347
0
                  : PrimeMeridian::GREENWICH;
7348
0
    Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"),
7349
0
                                UnitOfMeasure::YEAR);
7350
0
    optional<std::string> deformationModel;
7351
0
    if (j.contains("deformation_model")) {
7352
        // Before PROJJSON v0.5 / PROJ 9.1
7353
0
        deformationModel = getString(j, "deformation_model");
7354
0
    } else if (!deformationModelName_.empty()) {
7355
0
        deformationModel = deformationModelName_;
7356
0
    }
7357
0
    return DynamicGeodeticReferenceFrame::create(
7358
0
        buildProperties(j), buildEllipsoid(ellipsoidJ), getAnchor(j), pm,
7359
0
        frameReferenceEpoch, deformationModel);
7360
0
}
7361
7362
// ---------------------------------------------------------------------------
7363
7364
VerticalReferenceFrameNNPtr
7365
1
JSONParser::buildVerticalReferenceFrame(const json &j) {
7366
1
    return VerticalReferenceFrame::create(buildProperties(j), getAnchor(j),
7367
1
                                          getAnchorEpoch(j));
7368
1
}
7369
7370
// ---------------------------------------------------------------------------
7371
7372
DynamicVerticalReferenceFrameNNPtr
7373
0
JSONParser::buildDynamicVerticalReferenceFrame(const json &j) {
7374
0
    Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"),
7375
0
                                UnitOfMeasure::YEAR);
7376
0
    optional<std::string> deformationModel;
7377
0
    if (j.contains("deformation_model")) {
7378
        // Before PROJJSON v0.5 / PROJ 9.1
7379
0
        deformationModel = getString(j, "deformation_model");
7380
0
    } else if (!deformationModelName_.empty()) {
7381
0
        deformationModel = deformationModelName_;
7382
0
    }
7383
0
    return DynamicVerticalReferenceFrame::create(
7384
0
        buildProperties(j), getAnchor(j), util::optional<RealizationMethod>(),
7385
0
        frameReferenceEpoch, deformationModel);
7386
0
}
7387
7388
// ---------------------------------------------------------------------------
7389
7390
0
PrimeMeridianNNPtr JSONParser::buildPrimeMeridian(const json &j) {
7391
0
    if (!j.contains("longitude")) {
7392
0
        throw ParsingException("Missing \"longitude\" key");
7393
0
    }
7394
0
    auto longitude = j["longitude"];
7395
0
    if (longitude.is_number()) {
7396
0
        return PrimeMeridian::create(
7397
0
            buildProperties(j),
7398
0
            Angle(longitude.get<double>(), UnitOfMeasure::DEGREE));
7399
0
    } else if (longitude.is_object()) {
7400
0
        return PrimeMeridian::create(buildProperties(j),
7401
0
                                     Angle(getMeasure(longitude)));
7402
0
    }
7403
0
    throw ParsingException("Unexpected type for value of \"longitude\"");
7404
0
}
7405
7406
// ---------------------------------------------------------------------------
7407
7408
0
EllipsoidNNPtr JSONParser::buildEllipsoid(const json &j) {
7409
0
    if (j.contains("semi_major_axis")) {
7410
0
        auto semiMajorAxis = getLength(j, "semi_major_axis");
7411
0
        const auto ellpsProperties = buildProperties(j);
7412
0
        std::string ellpsName;
7413
0
        ellpsProperties.getStringValue(IdentifiedObject::NAME_KEY, ellpsName);
7414
0
        const auto celestialBody(Ellipsoid::guessBodyName(
7415
0
            dbContext_, semiMajorAxis.getSIValue(), ellpsName));
7416
0
        if (j.contains("semi_minor_axis")) {
7417
0
            return Ellipsoid::createTwoAxis(ellpsProperties, semiMajorAxis,
7418
0
                                            getLength(j, "semi_minor_axis"),
7419
0
                                            celestialBody);
7420
0
        } else if (j.contains("inverse_flattening")) {
7421
0
            return Ellipsoid::createFlattenedSphere(
7422
0
                ellpsProperties, semiMajorAxis,
7423
0
                Scale(getNumber(j, "inverse_flattening")), celestialBody);
7424
0
        } else {
7425
0
            throw ParsingException(
7426
0
                "Missing semi_minor_axis or inverse_flattening");
7427
0
        }
7428
0
    } else if (j.contains("radius")) {
7429
0
        auto radius = getLength(j, "radius");
7430
0
        const auto celestialBody(
7431
0
            Ellipsoid::guessBodyName(dbContext_, radius.getSIValue()));
7432
0
        return Ellipsoid::createSphere(buildProperties(j), radius,
7433
0
                                       celestialBody);
7434
0
    }
7435
0
    throw ParsingException("Missing semi_major_axis or radius");
7436
0
}
7437
7438
//! @endcond
7439
7440
// ---------------------------------------------------------------------------
7441
7442
//! @cond Doxygen_Suppress
7443
7444
// import a CRS encoded as OGC Best Practice document 11-135.
7445
7446
static const char *const crsURLPrefixes[] = {
7447
    "http://opengis.net/def/crs",     "https://opengis.net/def/crs",
7448
    "http://www.opengis.net/def/crs", "https://www.opengis.net/def/crs",
7449
    "www.opengis.net/def/crs",
7450
};
7451
7452
3.09k
static bool isCRSURL(const std::string &text) {
7453
15.3k
    for (const auto crsURLPrefix : crsURLPrefixes) {
7454
15.3k
        if (starts_with(text, crsURLPrefix)) {
7455
60
            return true;
7456
60
        }
7457
15.3k
    }
7458
3.03k
    return false;
7459
3.09k
}
7460
7461
static CRSNNPtr importFromCRSURL(const std::string &text,
7462
57
                                 const DatabaseContextNNPtr &dbContext) {
7463
    // e.g http://www.opengis.net/def/crs/EPSG/0/4326
7464
57
    std::vector<std::string> parts;
7465
200
    for (const auto crsURLPrefix : crsURLPrefixes) {
7466
200
        if (starts_with(text, crsURLPrefix)) {
7467
57
            parts = split(text.substr(strlen(crsURLPrefix)), '/');
7468
57
            break;
7469
57
        }
7470
200
    }
7471
7472
    // e.g
7473
    // "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/3855"
7474
57
    if (!parts.empty() && starts_with(parts[0], "-compound?")) {
7475
19
        parts = split(text.substr(text.find('?') + 1), '&');
7476
19
        std::map<int, std::string> mapParts;
7477
19
        for (const auto &part : parts) {
7478
19
            const auto queryParam = split(part, '=');
7479
19
            if (queryParam.size() != 2) {
7480
13
                throw ParsingException("invalid OGC CRS URL");
7481
13
            }
7482
6
            try {
7483
6
                mapParts[std::stoi(queryParam[0])] = queryParam[1];
7484
6
            } catch (const std::exception &) {
7485
6
                throw ParsingException("invalid OGC CRS URL");
7486
6
            }
7487
6
        }
7488
0
        std::vector<CRSNNPtr> components;
7489
0
        std::string name;
7490
0
        for (size_t i = 1; i <= mapParts.size(); ++i) {
7491
0
            const auto iter = mapParts.find(static_cast<int>(i));
7492
0
            if (iter == mapParts.end()) {
7493
0
                throw ParsingException("invalid OGC CRS URL");
7494
0
            }
7495
0
            components.emplace_back(importFromCRSURL(iter->second, dbContext));
7496
0
            if (!name.empty()) {
7497
0
                name += " + ";
7498
0
            }
7499
0
            name += components.back()->nameStr();
7500
0
        }
7501
0
        return CompoundCRS::create(
7502
0
            util::PropertyMap().set(IdentifiedObject::NAME_KEY, name),
7503
0
            components);
7504
0
    }
7505
7506
38
    if (parts.size() < 4) {
7507
29
        throw ParsingException("invalid OGC CRS URL");
7508
29
    }
7509
7510
9
    const auto &auth_name = parts[1];
7511
9
    const auto &code = parts[3];
7512
9
    try {
7513
9
        auto factoryCRS = AuthorityFactory::create(dbContext, auth_name);
7514
9
        return factoryCRS->createCoordinateReferenceSystem(code, true);
7515
9
    } catch (...) {
7516
9
        const auto &version = parts[2];
7517
9
        if (version.empty() || version == "0") {
7518
4
            const auto authoritiesFromAuthName =
7519
4
                dbContext->getVersionedAuthoritiesFromName(auth_name);
7520
4
            for (const auto &authNameVersioned : authoritiesFromAuthName) {
7521
0
                try {
7522
0
                    auto factoryCRS =
7523
0
                        AuthorityFactory::create(dbContext, authNameVersioned);
7524
0
                    return factoryCRS->createCoordinateReferenceSystem(code,
7525
0
                                                                       true);
7526
0
                } catch (...) {
7527
0
                }
7528
0
            }
7529
4
            throw;
7530
4
        }
7531
5
        std::string authNameWithVersion;
7532
5
        if (!dbContext->getVersionedAuthority(auth_name, version,
7533
5
                                              authNameWithVersion)) {
7534
5
            throw;
7535
5
        }
7536
0
        auto factoryCRS =
7537
0
            AuthorityFactory::create(dbContext, authNameWithVersion);
7538
0
        return factoryCRS->createCoordinateReferenceSystem(code, true);
7539
5
    }
7540
9
}
7541
7542
// ---------------------------------------------------------------------------
7543
7544
/* Import a CRS encoded as WMSAUTO string.
7545
 *
7546
 * Note that the WMS 1.3 specification does not include the
7547
 * units code, while apparently earlier specs do.  We try to
7548
 * guess around this.
7549
 *
7550
 * (code derived from GDAL's importFromWMSAUTO())
7551
 */
7552
7553
195
static CRSNNPtr importFromWMSAUTO(const std::string &text) {
7554
7555
195
    int nUnitsId = 9001;
7556
195
    double dfRefLong;
7557
195
    double dfRefLat = 0.0;
7558
7559
195
    assert(ci_starts_with(text, "AUTO:"));
7560
195
    const auto parts = split(text.substr(strlen("AUTO:")), ',');
7561
7562
195
    try {
7563
195
        constexpr int AUTO_MOLLWEIDE = 42005;
7564
195
        if (parts.size() == 4) {
7565
4
            nUnitsId = std::stoi(parts[1]);
7566
4
            dfRefLong = c_locale_stod(parts[2]);
7567
4
            dfRefLat = c_locale_stod(parts[3]);
7568
191
        } else if (parts.size() == 3 && std::stoi(parts[0]) == AUTO_MOLLWEIDE) {
7569
3
            nUnitsId = std::stoi(parts[1]);
7570
3
            dfRefLong = c_locale_stod(parts[2]);
7571
188
        } else if (parts.size() == 3) {
7572
120
            dfRefLong = c_locale_stod(parts[1]);
7573
120
            dfRefLat = c_locale_stod(parts[2]);
7574
120
        } else if (parts.size() == 2 && std::stoi(parts[0]) == AUTO_MOLLWEIDE) {
7575
32
            dfRefLong = c_locale_stod(parts[1]);
7576
36
        } else {
7577
36
            throw ParsingException("invalid WMS AUTO CRS definition");
7578
36
        }
7579
7580
159
        const auto getConversion = [dfRefLong, dfRefLat, &parts]() {
7581
87
            const int nProjId = std::stoi(parts[0]);
7582
87
            switch (nProjId) {
7583
4
            case 42001: // Auto UTM
7584
4
                if (!(dfRefLong >= -180 && dfRefLong < 180)) {
7585
0
                    throw ParsingException("invalid WMS AUTO CRS definition: "
7586
0
                                           "invalid longitude");
7587
0
                }
7588
4
                return Conversion::createUTM(
7589
4
                    util::PropertyMap(),
7590
4
                    static_cast<int>(floor((dfRefLong + 180.0) / 6.0)) + 1,
7591
4
                    dfRefLat >= 0.0);
7592
7593
6
            case 42002: // Auto TM (strangely very UTM-like).
7594
6
                return Conversion::createTransverseMercator(
7595
6
                    util::PropertyMap(), common::Angle(0),
7596
6
                    common::Angle(dfRefLong), common::Scale(0.9996),
7597
6
                    common::Length(500000),
7598
6
                    common::Length((dfRefLat >= 0.0) ? 0.0 : 10000000.0));
7599
7600
26
            case 42003: // Auto Orthographic.
7601
26
                return Conversion::createOrthographic(
7602
26
                    util::PropertyMap(), common::Angle(dfRefLat),
7603
26
                    common::Angle(dfRefLong), common::Length(0),
7604
26
                    common::Length(0));
7605
7606
15
            case 42004: // Auto Equirectangular
7607
15
                return Conversion::createEquidistantCylindrical(
7608
15
                    util::PropertyMap(), common::Angle(dfRefLat),
7609
15
                    common::Angle(dfRefLong), common::Length(0),
7610
15
                    common::Length(0));
7611
7612
17
            case 42005: // MSVC 2015 thinks that AUTO_MOLLWEIDE is not constant
7613
17
                return Conversion::createMollweide(
7614
17
                    util::PropertyMap(), common::Angle(dfRefLong),
7615
17
                    common::Length(0), common::Length(0));
7616
7617
19
            default:
7618
19
                throw ParsingException("invalid WMS AUTO CRS definition: "
7619
19
                                       "unsupported projection id");
7620
87
            }
7621
87
        };
7622
7623
159
        const auto getUnits = [nUnitsId]() -> const UnitOfMeasure & {
7624
68
            switch (nUnitsId) {
7625
67
            case 9001:
7626
67
                return UnitOfMeasure::METRE;
7627
7628
0
            case 9002:
7629
0
                return UnitOfMeasure::FOOT;
7630
7631
0
            case 9003:
7632
0
                return UnitOfMeasure::US_FOOT;
7633
7634
1
            default:
7635
1
                throw ParsingException("invalid WMS AUTO CRS definition: "
7636
1
                                       "unsupported units code");
7637
68
            }
7638
68
        };
7639
7640
159
        return crs::ProjectedCRS::create(
7641
159
            util::PropertyMap().set(IdentifiedObject::NAME_KEY, "unknown"),
7642
159
            crs::GeographicCRS::EPSG_4326, getConversion(),
7643
159
            cs::CartesianCS::createEastingNorthing(getUnits()));
7644
7645
195
    } catch (const std::exception &) {
7646
128
        throw ParsingException("invalid WMS AUTO CRS definition");
7647
128
    }
7648
195
}
7649
7650
// ---------------------------------------------------------------------------
7651
7652
static BaseObjectNNPtr createFromURNPart(const DatabaseContextPtr &dbContext,
7653
                                         const std::string &type,
7654
                                         const std::string &authName,
7655
                                         const std::string &version,
7656
42
                                         const std::string &code) {
7657
42
    if (!dbContext) {
7658
3
        throw ParsingException("no database context specified");
7659
3
    }
7660
39
    try {
7661
39
        auto factory =
7662
39
            AuthorityFactory::create(NN_NO_CHECK(dbContext), authName);
7663
39
        if (type == "crs") {
7664
0
            return factory->createCoordinateReferenceSystem(code);
7665
0
        }
7666
39
        if (type == "coordinateOperation") {
7667
1
            return factory->createCoordinateOperation(code, true);
7668
1
        }
7669
38
        if (type == "datum") {
7670
0
            return factory->createDatum(code);
7671
0
        }
7672
38
        if (type == "ensemble") {
7673
0
            return factory->createDatumEnsemble(code);
7674
0
        }
7675
38
        if (type == "ellipsoid") {
7676
0
            return factory->createEllipsoid(code);
7677
0
        }
7678
38
        if (type == "meridian") {
7679
0
            return factory->createPrimeMeridian(code);
7680
0
        }
7681
        // Extension of OGC URN syntax to CoordinateMetadata
7682
38
        if (type == "coordinateMetadata") {
7683
0
            return factory->createCoordinateMetadata(code);
7684
0
        }
7685
38
        throw ParsingException(concat("unhandled object type: ", type));
7686
39
    } catch (...) {
7687
39
        if (version.empty()) {
7688
7
            const auto authoritiesFromAuthName =
7689
7
                dbContext->getVersionedAuthoritiesFromName(authName);
7690
7
            for (const auto &authNameVersioned : authoritiesFromAuthName) {
7691
0
                try {
7692
0
                    return createFromURNPart(dbContext, type, authNameVersioned,
7693
0
                                             std::string(), code);
7694
0
                } catch (...) {
7695
0
                }
7696
0
            }
7697
7
            throw;
7698
7
        }
7699
32
        std::string authNameWithVersion;
7700
32
        if (!dbContext->getVersionedAuthority(authName, version,
7701
32
                                              authNameWithVersion)) {
7702
32
            throw;
7703
32
        }
7704
0
        return createFromURNPart(dbContext, type, authNameWithVersion,
7705
0
                                 std::string(), code);
7706
32
    }
7707
39
}
7708
7709
// ---------------------------------------------------------------------------
7710
7711
static BaseObjectNNPtr createFromUserInput(const std::string &text,
7712
                                           const DatabaseContextPtr &dbContext,
7713
                                           bool usePROJ4InitRules,
7714
                                           PJ_CONTEXT *ctx,
7715
24.8k
                                           bool ignoreCoordinateEpoch) {
7716
24.8k
    std::size_t idxFirstCharNotSpace = text.find_first_not_of(" \t\r\n");
7717
24.8k
    if (idxFirstCharNotSpace > 0 && idxFirstCharNotSpace != std::string::npos) {
7718
415
        return createFromUserInput(text.substr(idxFirstCharNotSpace), dbContext,
7719
415
                                   usePROJ4InitRules, ctx,
7720
415
                                   ignoreCoordinateEpoch);
7721
415
    }
7722
7723
    // Parse strings like "ITRF2014 @ 2025.0"
7724
24.4k
    const auto posAt = text.find('@');
7725
24.4k
    if (!ignoreCoordinateEpoch && posAt != std::string::npos) {
7726
7727
        // Try first as if belonged to the name
7728
4.67k
        try {
7729
4.67k
            return createFromUserInput(text, dbContext, usePROJ4InitRules, ctx,
7730
4.67k
                                       /* ignoreCoordinateEpoch = */ true);
7731
4.67k
        } catch (...) {
7732
2.44k
        }
7733
7734
2.44k
        std::string leftPart = text.substr(0, posAt);
7735
2.68k
        while (!leftPart.empty() && leftPart.back() == ' ')
7736
244
            leftPart.resize(leftPart.size() - 1);
7737
2.44k
        const auto nonSpacePos = text.find_first_not_of(' ', posAt + 1);
7738
2.44k
        if (nonSpacePos != std::string::npos) {
7739
1.97k
            auto obj =
7740
1.97k
                createFromUserInput(leftPart, dbContext, usePROJ4InitRules, ctx,
7741
1.97k
                                    /* ignoreCoordinateEpoch = */ true);
7742
1.97k
            auto crs = nn_dynamic_pointer_cast<CRS>(obj);
7743
1.97k
            if (crs) {
7744
145
                double epoch;
7745
145
                try {
7746
145
                    epoch = c_locale_stod(text.substr(nonSpacePos));
7747
145
                } catch (const std::exception &) {
7748
60
                    throw ParsingException("non-numeric value after @");
7749
60
                }
7750
85
                try {
7751
85
                    return CoordinateMetadata::create(NN_NO_CHECK(crs), epoch,
7752
85
                                                      dbContext);
7753
85
                } catch (const std::exception &e) {
7754
9
                    throw ParsingException(
7755
9
                        std::string(
7756
9
                            "CoordinateMetadata::create() failed with: ") +
7757
9
                        e.what());
7758
9
                }
7759
85
            }
7760
1.97k
        }
7761
2.44k
    }
7762
7763
22.0k
    if (!text.empty() && text[0] == '{') {
7764
1.72k
        json j;
7765
1.72k
        try {
7766
18.4k
            j = json::parse(text, [](int depth, json::parse_event_t, json &) {
7767
18.4k
                if (depth >= 128)
7768
6
                    throw ParsingException("Too deep nesting in JSON content");
7769
18.4k
                return true;
7770
18.4k
            });
7771
1.72k
        } catch (const std::exception &e) {
7772
1.63k
            throw ParsingException(e.what());
7773
1.63k
        }
7774
90
        return JSONParser()
7775
90
            .attachContext(ctx)
7776
90
            .attachDatabaseContext(dbContext)
7777
90
            .create(j);
7778
1.72k
    }
7779
7780
20.3k
    if (!ci_starts_with(text, "step proj=") &&
7781
18.8k
        !ci_starts_with(text, "step +proj=")) {
7782
1.73M
        for (const auto &wktConstant : WKTConstants::constants()) {
7783
1.73M
            if (ci_starts_with(text, wktConstant)) {
7784
3.31k
                for (auto wkt = text.c_str() + wktConstant.size(); *wkt != '\0';
7785
3.30k
                     ++wkt) {
7786
3.30k
                    if (isspace(static_cast<unsigned char>(*wkt)))
7787
241
                        continue;
7788
3.06k
                    if (*wkt == '[') {
7789
2.95k
                        return WKTParser()
7790
2.95k
                            .attachDatabaseContext(dbContext)
7791
2.95k
                            .setStrict(false)
7792
2.95k
                            .createFromWKT(text);
7793
2.95k
                    }
7794
104
                    break;
7795
3.06k
                }
7796
3.06k
            }
7797
1.73M
        }
7798
18.8k
    }
7799
7800
17.3k
    const char *textWithoutPlusPrefix = text.c_str();
7801
17.3k
    if (textWithoutPlusPrefix[0] == '+')
7802
1.93k
        textWithoutPlusPrefix++;
7803
7804
17.3k
    if (strncmp(textWithoutPlusPrefix, "proj=", strlen("proj=")) == 0 ||
7805
6.78k
        text.find(" +proj=") != std::string::npos ||
7806
6.25k
        text.find(" proj=") != std::string::npos ||
7807
3.49k
        strncmp(textWithoutPlusPrefix, "init=", strlen("init=")) == 0 ||
7808
3.20k
        text.find(" +init=") != std::string::npos ||
7809
3.17k
        text.find(" init=") != std::string::npos ||
7810
12.8k
        strncmp(textWithoutPlusPrefix, "title=", strlen("title=")) == 0) {
7811
12.8k
        return PROJStringParser()
7812
12.8k
            .attachDatabaseContext(dbContext)
7813
12.8k
            .attachContext(ctx)
7814
12.8k
            .setUsePROJ4InitRules(ctx != nullptr
7815
12.8k
                                      ? (proj_context_get_use_proj4_init_rules(
7816
12.8k
                                             ctx, false) == TRUE)
7817
12.8k
                                      : usePROJ4InitRules)
7818
12.8k
            .createFromPROJString(text);
7819
12.8k
    }
7820
7821
4.57k
    if (isCRSURL(text) && dbContext) {
7822
57
        return importFromCRSURL(text, NN_NO_CHECK(dbContext));
7823
57
    }
7824
7825
4.51k
    if (ci_starts_with(text, "AUTO:")) {
7826
140
        return importFromWMSAUTO(text);
7827
140
    }
7828
7829
4.37k
    std::vector<std::string> tokens;
7830
4.37k
    if (text.find(' ') == std::string::npos)
7831
2.31k
        tokens = split(text, ':');
7832
4.37k
    if (tokens.size() == 2) {
7833
78
        if (!dbContext) {
7834
5
            throw ParsingException("no database context specified");
7835
5
        }
7836
73
        DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext));
7837
73
        const auto &authName = tokens[0];
7838
73
        const auto &code = tokens[1];
7839
73
        auto factory = AuthorityFactory::create(dbContextNNPtr, authName);
7840
73
        try {
7841
73
            return factory->createCoordinateReferenceSystem(code);
7842
73
        } catch (...) {
7843
7844
            // Convenience for well-known misused code
7845
            // See https://github.com/OSGeo/PROJ/issues/1730
7846
73
            if (ci_equal(authName, "EPSG") && code == "102100") {
7847
0
                factory = AuthorityFactory::create(dbContextNNPtr, "ESRI");
7848
0
                return factory->createCoordinateReferenceSystem(code);
7849
0
            }
7850
7851
73
            const auto authoritiesFromAuthName =
7852
73
                dbContextNNPtr->getVersionedAuthoritiesFromName(authName);
7853
73
            for (const auto &authNameVersioned : authoritiesFromAuthName) {
7854
1
                factory =
7855
1
                    AuthorityFactory::create(dbContextNNPtr, authNameVersioned);
7856
1
                try {
7857
1
                    return factory->createCoordinateReferenceSystem(code);
7858
1
                } catch (...) {
7859
1
                }
7860
1
            }
7861
7862
73
            const auto allAuthorities = dbContextNNPtr->getAuthorities();
7863
534
            for (const auto &authCandidate : allAuthorities) {
7864
534
                if (ci_equal(authCandidate, authName)) {
7865
17
                    factory =
7866
17
                        AuthorityFactory::create(dbContextNNPtr, authCandidate);
7867
17
                    try {
7868
17
                        return factory->createCoordinateReferenceSystem(code);
7869
17
                    } catch (...) {
7870
                        // EPSG:4326+3855
7871
17
                        auto tokensCode = split(code, '+');
7872
17
                        if (tokensCode.size() == 2) {
7873
2
                            auto crs1(factory->createCoordinateReferenceSystem(
7874
2
                                tokensCode[0], false));
7875
2
                            auto crs2(factory->createCoordinateReferenceSystem(
7876
2
                                tokensCode[1], false));
7877
2
                            return CompoundCRS::createLax(
7878
2
                                util::PropertyMap().set(
7879
2
                                    IdentifiedObject::NAME_KEY,
7880
2
                                    crs1->nameStr() + " + " + crs2->nameStr()),
7881
2
                                {crs1, crs2}, dbContext);
7882
2
                        }
7883
15
                        throw;
7884
17
                    }
7885
17
                }
7886
534
            }
7887
56
            throw;
7888
73
        }
7889
4.29k
    } else if (tokens.size() == 3) {
7890
        // ESRI:103668+EPSG:5703 ... compound
7891
22
        auto tokensCenter = split(tokens[1], '+');
7892
22
        if (tokensCenter.size() == 2) {
7893
3
            if (!dbContext) {
7894
3
                throw ParsingException("no database context specified");
7895
3
            }
7896
0
            DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext));
7897
7898
0
            const auto &authName1 = tokens[0];
7899
0
            const auto &code1 = tokensCenter[0];
7900
0
            const auto &authName2 = tokensCenter[1];
7901
0
            const auto &code2 = tokens[2];
7902
7903
0
            auto factory1 = AuthorityFactory::create(dbContextNNPtr, authName1);
7904
0
            auto crs1 = factory1->createCoordinateReferenceSystem(code1, false);
7905
0
            auto factory2 = AuthorityFactory::create(dbContextNNPtr, authName2);
7906
0
            auto crs2 = factory2->createCoordinateReferenceSystem(code2, false);
7907
0
            return CompoundCRS::createLax(
7908
0
                util::PropertyMap().set(IdentifiedObject::NAME_KEY,
7909
0
                                        crs1->nameStr() + " + " +
7910
0
                                            crs2->nameStr()),
7911
0
                {crs1, crs2}, dbContext);
7912
3
        }
7913
22
    }
7914
7915
4.29k
    if (starts_with(text, "urn:ogc:def:crs,")) {
7916
12
        if (!dbContext) {
7917
2
            throw ParsingException("no database context specified");
7918
2
        }
7919
10
        auto tokensComma = split(text, ',');
7920
10
        if (tokensComma.size() == 4 && starts_with(tokensComma[1], "crs:") &&
7921
0
            starts_with(tokensComma[2], "cs:") &&
7922
0
            starts_with(tokensComma[3], "coordinateOperation:")) {
7923
            // OGC 07-092r2: para 7.5.4
7924
            // URN combined references for projected or derived CRSs
7925
0
            const auto &crsPart = tokensComma[1];
7926
0
            const auto tokensCRS = split(crsPart, ':');
7927
0
            if (tokensCRS.size() != 4) {
7928
0
                throw ParsingException(
7929
0
                    concat("invalid crs component: ", crsPart));
7930
0
            }
7931
0
            auto factoryCRS =
7932
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensCRS[1]);
7933
0
            auto baseCRS =
7934
0
                factoryCRS->createCoordinateReferenceSystem(tokensCRS[3], true);
7935
7936
0
            const auto &csPart = tokensComma[2];
7937
0
            auto tokensCS = split(csPart, ':');
7938
0
            if (tokensCS.size() != 4) {
7939
0
                throw ParsingException(
7940
0
                    concat("invalid cs component: ", csPart));
7941
0
            }
7942
0
            auto factoryCS =
7943
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensCS[1]);
7944
0
            auto cs = factoryCS->createCoordinateSystem(tokensCS[3]);
7945
7946
0
            const auto &opPart = tokensComma[3];
7947
0
            auto tokensOp = split(opPart, ':');
7948
0
            if (tokensOp.size() != 4) {
7949
0
                throw ParsingException(
7950
0
                    concat("invalid coordinateOperation component: ", opPart));
7951
0
            }
7952
0
            auto factoryOp =
7953
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensOp[1]);
7954
0
            auto op = factoryOp->createCoordinateOperation(tokensOp[3], true);
7955
7956
0
            const auto &baseName = baseCRS->nameStr();
7957
0
            std::string name(baseName);
7958
0
            auto geogCRS =
7959
0
                util::nn_dynamic_pointer_cast<GeographicCRS>(baseCRS);
7960
0
            if (geogCRS &&
7961
0
                geogCRS->coordinateSystem()->axisList().size() == 3 &&
7962
0
                baseName.find("3D") == std::string::npos) {
7963
0
                name += " (3D)";
7964
0
            }
7965
0
            name += " / ";
7966
0
            name += op->nameStr();
7967
0
            auto props =
7968
0
                util::PropertyMap().set(IdentifiedObject::NAME_KEY, name);
7969
7970
0
            if (auto conv = util::nn_dynamic_pointer_cast<Conversion>(op)) {
7971
0
                auto convNN = NN_NO_CHECK(conv);
7972
0
                if (geogCRS != nullptr) {
7973
0
                    auto geogCRSNN = NN_NO_CHECK(geogCRS);
7974
0
                    if (CartesianCSPtr ccs =
7975
0
                            util::nn_dynamic_pointer_cast<CartesianCS>(cs)) {
7976
0
                        return ProjectedCRS::create(props, geogCRSNN, convNN,
7977
0
                                                    NN_NO_CHECK(ccs));
7978
0
                    }
7979
0
                    if (EllipsoidalCSPtr ecs =
7980
0
                            util::nn_dynamic_pointer_cast<EllipsoidalCS>(cs)) {
7981
0
                        return DerivedGeographicCRS::create(
7982
0
                            props, geogCRSNN, convNN, NN_NO_CHECK(ecs));
7983
0
                    }
7984
0
                } else if (dynamic_cast<GeodeticCRS *>(baseCRS.get()) &&
7985
0
                           dynamic_cast<CartesianCS *>(cs.get())) {
7986
0
                    return DerivedGeodeticCRS::create(
7987
0
                        props,
7988
0
                        NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
7989
0
                            baseCRS)),
7990
0
                        convNN,
7991
0
                        NN_NO_CHECK(
7992
0
                            util::nn_dynamic_pointer_cast<CartesianCS>(cs)));
7993
0
                } else if (auto pcrs =
7994
0
                               util::nn_dynamic_pointer_cast<ProjectedCRS>(
7995
0
                                   baseCRS)) {
7996
0
                    return DerivedProjectedCRS::create(props, NN_NO_CHECK(pcrs),
7997
0
                                                       convNN, cs);
7998
0
                } else if (auto vertBaseCRS =
7999
0
                               util::nn_dynamic_pointer_cast<VerticalCRS>(
8000
0
                                   baseCRS)) {
8001
0
                    if (auto vertCS =
8002
0
                            util::nn_dynamic_pointer_cast<VerticalCS>(cs)) {
8003
0
                        const int methodCode = convNN->method()->getEPSGCode();
8004
0
                        std::string newName(baseName);
8005
0
                        std::string unitNameSuffix;
8006
0
                        for (const char *suffix : {" (ft)", " (ftUS)"}) {
8007
0
                            if (ends_with(newName, suffix)) {
8008
0
                                unitNameSuffix = suffix;
8009
0
                                newName.resize(newName.size() - strlen(suffix));
8010
0
                                break;
8011
0
                            }
8012
0
                        }
8013
0
                        bool newNameOk = false;
8014
0
                        if (methodCode ==
8015
0
                                EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR ||
8016
0
                            methodCode ==
8017
0
                                EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
8018
0
                            const auto &unitName =
8019
0
                                vertCS->axisList()[0]->unit().name();
8020
0
                            if (unitName == UnitOfMeasure::METRE.name()) {
8021
0
                                newNameOk = true;
8022
0
                            } else if (unitName == UnitOfMeasure::FOOT.name()) {
8023
0
                                newName += " (ft)";
8024
0
                                newNameOk = true;
8025
0
                            } else if (unitName ==
8026
0
                                       UnitOfMeasure::US_FOOT.name()) {
8027
0
                                newName += " (ftUS)";
8028
0
                                newNameOk = true;
8029
0
                            }
8030
0
                        } else if (methodCode ==
8031
0
                                   EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
8032
0
                            if (ends_with(newName, " height")) {
8033
0
                                newName.resize(newName.size() -
8034
0
                                               strlen(" height"));
8035
0
                                newName += " depth";
8036
0
                                newName += unitNameSuffix;
8037
0
                                newNameOk = true;
8038
0
                            } else if (ends_with(newName, " depth")) {
8039
0
                                newName.resize(newName.size() -
8040
0
                                               strlen(" depth"));
8041
0
                                newName += " height";
8042
0
                                newName += unitNameSuffix;
8043
0
                                newNameOk = true;
8044
0
                            }
8045
0
                        }
8046
0
                        if (newNameOk) {
8047
0
                            props.set(IdentifiedObject::NAME_KEY, newName);
8048
0
                        }
8049
0
                        return DerivedVerticalCRS::create(
8050
0
                            props, NN_NO_CHECK(vertBaseCRS), convNN,
8051
0
                            NN_NO_CHECK(vertCS));
8052
0
                    }
8053
0
                }
8054
0
            }
8055
8056
0
            throw ParsingException("unsupported combination of baseCRS, CS "
8057
0
                                   "and coordinateOperation for a "
8058
0
                                   "DerivedCRS");
8059
0
        }
8060
8061
        // OGC 07-092r2: para 7.5.2
8062
        // URN combined references for compound coordinate reference systems
8063
10
        std::vector<CRSNNPtr> components;
8064
10
        std::string name;
8065
10
        for (size_t i = 1; i < tokensComma.size(); i++) {
8066
10
            tokens = split(tokensComma[i], ':');
8067
10
            if (tokens.size() != 4) {
8068
7
                throw ParsingException(
8069
7
                    concat("invalid crs component: ", tokensComma[i]));
8070
7
            }
8071
3
            const auto &type = tokens[0];
8072
3
            auto factory =
8073
3
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[1]);
8074
3
            const auto &code = tokens[3];
8075
3
            if (type == "crs") {
8076
0
                auto crs(factory->createCoordinateReferenceSystem(code, false));
8077
0
                components.emplace_back(crs);
8078
0
                if (!name.empty()) {
8079
0
                    name += " + ";
8080
0
                }
8081
0
                name += crs->nameStr();
8082
3
            } else {
8083
3
                throw ParsingException(
8084
3
                    concat("unexpected object type: ", type));
8085
3
            }
8086
3
        }
8087
0
        return CompoundCRS::create(
8088
0
            util::PropertyMap().set(IdentifiedObject::NAME_KEY, name),
8089
0
            components);
8090
10
    }
8091
8092
    // OGC 07-092r2: para 7.5.3
8093
    // 7.5.3 URN combined references for concatenated operations
8094
4.28k
    if (starts_with(text, "urn:ogc:def:coordinateOperation,")) {
8095
10
        if (!dbContext) {
8096
3
            throw ParsingException("no database context specified");
8097
3
        }
8098
7
        auto tokensComma = split(text, ',');
8099
7
        std::vector<CoordinateOperationNNPtr> components;
8100
7
        for (size_t i = 1; i < tokensComma.size(); i++) {
8101
7
            tokens = split(tokensComma[i], ':');
8102
7
            if (tokens.size() != 4) {
8103
4
                throw ParsingException(concat(
8104
4
                    "invalid coordinateOperation component: ", tokensComma[i]));
8105
4
            }
8106
3
            const auto &type = tokens[0];
8107
3
            auto factory =
8108
3
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[1]);
8109
3
            const auto &code = tokens[3];
8110
3
            if (type == "coordinateOperation") {
8111
0
                auto op(factory->createCoordinateOperation(code, false));
8112
0
                components.emplace_back(op);
8113
3
            } else {
8114
3
                throw ParsingException(
8115
3
                    concat("unexpected object type: ", type));
8116
3
            }
8117
3
        }
8118
0
        return ConcatenatedOperation::createComputeMetadata(components, true);
8119
7
    }
8120
8121
    // urn:ogc:def:crs:EPSG::4326
8122
4.27k
    if (tokens.size() == 7 && tolower(tokens[0]) == "urn") {
8123
8124
11
        const std::string type(tokens[3] == "CRS" ? "crs" : tokens[3]);
8125
11
        const auto &authName = tokens[4];
8126
11
        const auto &version = tokens[5];
8127
11
        const auto &code = tokens[6];
8128
11
        return createFromURNPart(dbContext, type, authName, version, code);
8129
11
    }
8130
8131
    // urn:ogc:def:crs:OGC::AUTO42001:-117:33
8132
4.26k
    if (tokens.size() > 7 && tokens[0] == "urn" && tokens[4] == "OGC" &&
8133
58
        ci_starts_with(tokens[6], "AUTO")) {
8134
55
        const auto textAUTO = text.substr(text.find(":AUTO") + 5);
8135
55
        return importFromWMSAUTO("AUTO:" + replaceAll(textAUTO, ":", ","));
8136
55
    }
8137
8138
    // Legacy urn:opengis:crs:EPSG:0:4326 (note the missing def: compared to
8139
    // above)
8140
4.20k
    if (tokens.size() == 6 && tokens[0] == "urn" && tokens[2] != "def") {
8141
24
        const auto &type = tokens[2];
8142
24
        const auto &authName = tokens[3];
8143
24
        const auto &version = tokens[4];
8144
24
        const auto &code = tokens[5];
8145
24
        return createFromURNPart(dbContext, type, authName, version, code);
8146
24
    }
8147
8148
    // Legacy urn:x-ogc:def:crs:EPSG:4326 (note the missing version)
8149
4.18k
    if (tokens.size() == 6 && tokens[0] == "urn") {
8150
7
        const auto &type = tokens[3];
8151
7
        const auto &authName = tokens[4];
8152
7
        const auto &code = tokens[5];
8153
7
        return createFromURNPart(dbContext, type, authName, std::string(),
8154
7
                                 code);
8155
7
    }
8156
8157
4.17k
    if (dbContext) {
8158
2.63k
        auto factory =
8159
2.63k
            AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string());
8160
8161
2.63k
        const auto searchObject =
8162
2.63k
            [&factory](
8163
2.63k
                const std::string &objectName, bool approximateMatch,
8164
2.63k
                const std::vector<AuthorityFactory::ObjectType> &objectTypes)
8165
7.85k
            -> IdentifiedObjectPtr {
8166
7.85k
            constexpr size_t limitResultCount = 10;
8167
7.85k
            auto res = factory->createObjectsFromName(
8168
7.85k
                objectName, objectTypes, approximateMatch, limitResultCount);
8169
7.85k
            if (res.size() == 1) {
8170
137
                return res.front().as_nullable();
8171
137
            }
8172
7.71k
            if (res.size() > 1) {
8173
1.30k
                if (objectTypes.size() == 1 &&
8174
1.03k
                    objectTypes[0] == AuthorityFactory::ObjectType::CRS) {
8175
1.31k
                    for (size_t ndim = 2; ndim <= 3; ndim++) {
8176
4.18k
                        for (const auto &obj : res) {
8177
4.18k
                            auto crs =
8178
4.18k
                                dynamic_cast<crs::GeographicCRS *>(obj.get());
8179
4.18k
                            if (crs &&
8180
1.46k
                                crs->coordinateSystem()->axisList().size() ==
8181
1.46k
                                    ndim) {
8182
899
                                return obj.as_nullable();
8183
899
                            }
8184
4.18k
                        }
8185
1.18k
                    }
8186
1.03k
                }
8187
8188
                // If there's exactly only one object whose name is equivalent
8189
                // to the user input, return it.
8190
1.19k
                for (int pass = 0; pass <= 1; ++pass) {
8191
802
                    IdentifiedObjectPtr identifiedObj;
8192
6.29k
                    for (const auto &obj : res) {
8193
6.29k
                        if (Identifier::isEquivalentName(
8194
6.29k
                                obj->nameStr().c_str(), objectName.c_str(),
8195
6.29k
                                /* biggerDifferencesAllowed = */ pass == 1)) {
8196
7
                            if (identifiedObj == nullptr) {
8197
7
                                identifiedObj = obj.as_nullable();
8198
7
                            } else {
8199
0
                                identifiedObj = nullptr;
8200
0
                                break;
8201
0
                            }
8202
7
                        }
8203
6.29k
                    }
8204
802
                    if (identifiedObj) {
8205
7
                        return identifiedObj;
8206
7
                    }
8207
802
                }
8208
8209
395
                std::string msg("several objects matching this name: ");
8210
395
                bool first = true;
8211
2.65k
                for (const auto &obj : res) {
8212
2.65k
                    if (msg.size() > 200) {
8213
238
                        msg += ", ...";
8214
238
                        break;
8215
238
                    }
8216
2.41k
                    if (!first) {
8217
2.01k
                        msg += ", ";
8218
2.01k
                    }
8219
2.41k
                    first = false;
8220
2.41k
                    msg += obj->nameStr();
8221
2.41k
                }
8222
395
                throw ParsingException(msg);
8223
402
            }
8224
6.41k
            return nullptr;
8225
7.71k
        };
8226
8227
2.63k
        const auto searchCRS = [&searchObject](const std::string &objectName) {
8228
71
            const auto objectTypes = std::vector<AuthorityFactory::ObjectType>{
8229
71
                AuthorityFactory::ObjectType::CRS};
8230
71
            {
8231
71
                constexpr bool approximateMatch = false;
8232
71
                auto ret =
8233
71
                    searchObject(objectName, approximateMatch, objectTypes);
8234
71
                if (ret)
8235
1
                    return ret;
8236
71
            }
8237
8238
70
            constexpr bool approximateMatch = true;
8239
70
            return searchObject(objectName, approximateMatch, objectTypes);
8240
71
        };
8241
8242
        // strings like "WGS 84 + EGM96 height"
8243
2.63k
        CompoundCRSPtr compoundCRS;
8244
2.63k
        try {
8245
2.63k
            const auto tokensCompound = split(text, " + ");
8246
2.63k
            if (tokensCompound.size() == 2) {
8247
37
                auto obj1 = searchCRS(tokensCompound[0]);
8248
37
                auto obj2 = searchCRS(tokensCompound[1]);
8249
37
                auto crs1 = std::dynamic_pointer_cast<CRS>(obj1);
8250
37
                auto crs2 = std::dynamic_pointer_cast<CRS>(obj2);
8251
37
                if (crs1 && crs2) {
8252
4
                    compoundCRS =
8253
4
                        CompoundCRS::create(
8254
4
                            util::PropertyMap().set(IdentifiedObject::NAME_KEY,
8255
4
                                                    crs1->nameStr() + " + " +
8256
4
                                                        crs2->nameStr()),
8257
4
                            {NN_NO_CHECK(crs1), NN_NO_CHECK(crs2)})
8258
4
                            .as_nullable();
8259
4
                }
8260
37
            }
8261
2.63k
        } catch (const std::exception &) {
8262
10
        }
8263
8264
        // First pass: exact match on CRS objects
8265
        // Second pass: exact match on other objects
8266
        // Third pass: approximate match on CRS objects
8267
        // Fourth pass: approximate match on other objects
8268
        // But only allow approximate matching if the size of the text is
8269
        // large enough (>= 5), otherwise we get a lot of false positives:
8270
        // "foo" -> "Amersfoort", "bar" -> "Barbados 1938"
8271
        // Also only accept approximate matching if the ratio between the
8272
        // input and match size is not too small, so that "omerc" doesn't match
8273
        // with "WGS 84 / Pseudo-Mercator"
8274
2.63k
        const int maxNumberPasses = text.size() <= 4 ? 2 : 4;
8275
9.57k
        for (int pass = 0; pass < maxNumberPasses; ++pass) {
8276
7.71k
            const bool approximateMatch = (pass >= 2);
8277
7.71k
            auto ret = searchObject(
8278
7.71k
                text, approximateMatch,
8279
7.71k
                (pass == 0 || pass == 2)
8280
7.71k
                    ? std::vector<
8281
4.28k
                          AuthorityFactory::ObjectType>{AuthorityFactory::
8282
4.28k
                                                            ObjectType::CRS}
8283
7.71k
                    : std::vector<AuthorityFactory::ObjectType>{
8284
3.42k
                          AuthorityFactory::ObjectType::ELLIPSOID,
8285
3.42k
                          AuthorityFactory::ObjectType::DATUM,
8286
3.42k
                          AuthorityFactory::ObjectType::DATUM_ENSEMBLE,
8287
3.42k
                          AuthorityFactory::ObjectType::COORDINATE_OPERATION});
8288
7.71k
            if (ret) {
8289
1.01k
                if (!approximateMatch ||
8290
979
                    ret->nameStr().size() < 2 * text.size())
8291
778
                    return NN_NO_CHECK(ret);
8292
1.01k
            }
8293
6.93k
            if (compoundCRS) {
8294
0
                if (!approximateMatch ||
8295
0
                    compoundCRS->nameStr().size() < 2 * text.size())
8296
0
                    return NN_NO_CHECK(compoundCRS);
8297
0
            }
8298
6.93k
        }
8299
2.63k
    }
8300
8301
3.39k
    throw ParsingException("unrecognized format / unknown name");
8302
4.17k
}
8303
//! @endcond
8304
8305
// ---------------------------------------------------------------------------
8306
8307
/** \brief Instantiate a sub-class of BaseObject from a user specified text.
8308
 *
8309
 * The text can be a:
8310
 * <ul>
8311
 * <li>WKT string</li>
8312
 * <li>PROJ string</li>
8313
 * <li>database code, prefixed by its authority. e.g. "EPSG:4326"</li>
8314
 * <li>OGC URN. e.g. "urn:ogc:def:crs:EPSG::4326",
8315
 *     "urn:ogc:def:coordinateOperation:EPSG::1671",
8316
 *     "urn:ogc:def:ellipsoid:EPSG::7001"
8317
 *     or "urn:ogc:def:datum:EPSG::6326"</li>
8318
 * <li> OGC URN combining references for compound coordinate reference systems
8319
 *      e.g. "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717"
8320
 *      We also accept a custom abbreviated syntax EPSG:2393+5717
8321
 *      or ESRI:103668+EPSG:5703
8322
 * </li>
8323
 * <li> OGC URN combining references for references for projected or derived
8324
 * CRSs
8325
 *      e.g. for Projected 3D CRS "UTM zone 31N / WGS 84 (3D)"
8326
 *      "urn:ogc:def:crs,crs:EPSG::4979,cs:PROJ::ENh,coordinateOperation:EPSG::16031"
8327
 * </li>
8328
 * <li>Extension of OGC URN for CoordinateMetadata.
8329
 *     e.g.
8330
 * "urn:ogc:def:coordinateMetadata:NRCAN::NAD83_CSRS_1997_MTM11_HT2_1997"</li>
8331
 * <li> OGC URN combining references for concatenated operations
8332
 *      e.g.
8333
 * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</li>
8334
 * <li>OGC URL for a single CRS. e.g.
8335
 * "http://www.opengis.net/def/crs/EPSG/0/4326"</li>
8336
 * <li>OGC URL for a compound
8337
 * CRS. e.g
8338
 * "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/3855"</li>
8339
 * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
8340
 *     uniqueness is not guaranteed, the function may apply heuristics to
8341
 *     determine the appropriate best match.</li>
8342
 * <li>a CRS name and a coordinate epoch, separated with '@'. For example
8343
 *     "ITRF2014@2025.0". (added in PROJ 9.2)</li>
8344
 * <li>a compound CRS made from two object names separated with " + ".
8345
 *     e.g. "WGS 84 + EGM96 height"</li>
8346
 * <li>PROJJSON string</li>
8347
 * </ul>
8348
 *
8349
 * @param text One of the above mentioned text format
8350
 * @param dbContext Database context, or nullptr (in which case database
8351
 * lookups will not work)
8352
 * @param usePROJ4InitRules When set to true,
8353
 * init=epsg:XXXX syntax will be allowed and will be interpreted according to
8354
 * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
8355
 * order and will expect/output coordinates in radians. ProjectedCRS will have
8356
 * easting, northing axis order (except the ones with Transverse Mercator South
8357
 * Orientated projection). In that mode, the epsg:XXXX syntax will be also
8358
 * interpreted the same way.
8359
 * @throw ParsingException if the string cannot be parsed.
8360
 */
8361
BaseObjectNNPtr createFromUserInput(const std::string &text,
8362
                                    const DatabaseContextPtr &dbContext,
8363
320
                                    bool usePROJ4InitRules) {
8364
320
    return createFromUserInput(text, dbContext, usePROJ4InitRules, nullptr,
8365
320
                               /* ignoreCoordinateEpoch = */ false);
8366
320
}
8367
8368
// ---------------------------------------------------------------------------
8369
8370
/** \brief Instantiate a sub-class of BaseObject from a user specified text.
8371
 *
8372
 * The text can be a:
8373
 * <ul>
8374
 * <li>WKT string</li>
8375
 * <li>PROJ string</li>
8376
 * <li>database code, prefixed by its authority. e.g. "EPSG:4326"</li>
8377
 * <li>OGC URN. e.g. "urn:ogc:def:crs:EPSG::4326",
8378
 *     "urn:ogc:def:coordinateOperation:EPSG::1671",
8379
 *     "urn:ogc:def:ellipsoid:EPSG::7001"
8380
 *     or "urn:ogc:def:datum:EPSG::6326"</li>
8381
 * <li> OGC URN combining references for compound coordinate reference systems
8382
 *      e.g. "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717"
8383
 *      We also accept a custom abbreviated syntax EPSG:2393+5717
8384
 * </li>
8385
 * <li> OGC URN combining references for references for projected or derived
8386
 * CRSs
8387
 *      e.g. for Projected 3D CRS "UTM zone 31N / WGS 84 (3D)"
8388
 *      "urn:ogc:def:crs,crs:EPSG::4979,cs:PROJ::ENh,coordinateOperation:EPSG::16031"
8389
 * </li>
8390
 * <li>Extension of OGC URN for CoordinateMetadata.
8391
 *     e.g.
8392
 * "urn:ogc:def:coordinateMetadata:NRCAN::NAD83_CSRS_1997_MTM11_HT2_1997"</li>
8393
 * <li> OGC URN combining references for concatenated operations
8394
 *      e.g.
8395
 * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</li>
8396
 * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
8397
 *     uniqueness is not guaranteed, the function may apply heuristics to
8398
 *     determine the appropriate best match.</li>
8399
 * <li>a compound CRS made from two object names separated with " + ".
8400
 *     e.g. "WGS 84 + EGM96 height"</li>
8401
 * <li>PROJJSON string</li>
8402
 * </ul>
8403
 *
8404
 * @param text One of the above mentioned text format
8405
 * @param ctx PROJ context
8406
 * @throw ParsingException if the string cannot be parsed.
8407
 */
8408
17.4k
BaseObjectNNPtr createFromUserInput(const std::string &text, PJ_CONTEXT *ctx) {
8409
17.4k
    DatabaseContextPtr dbContext;
8410
17.4k
    try {
8411
17.4k
        if (ctx != nullptr) {
8412
            // Only connect to proj.db if needed
8413
17.4k
            if (text.find("proj=") == std::string::npos ||
8414
11.6k
                text.find("init=") != std::string::npos) {
8415
6.33k
                dbContext =
8416
6.33k
                    ctx->get_cpp_context()->getDatabaseContext().as_nullable();
8417
6.33k
            }
8418
17.4k
        }
8419
17.4k
    } catch (const std::exception &) {
8420
0
    }
8421
17.4k
    return createFromUserInput(text, dbContext, false, ctx,
8422
17.4k
                               /* ignoreCoordinateEpoch = */ false);
8423
17.4k
}
8424
8425
// ---------------------------------------------------------------------------
8426
8427
/** \brief Instantiate a sub-class of BaseObject from a WKT string.
8428
 *
8429
 * By default, validation is strict (to the extent of the checks that are
8430
 * actually implemented. Currently only WKT1 strict grammar is checked), and
8431
 * any issue detected will cause an exception to be thrown, unless
8432
 * setStrict(false) is called priorly.
8433
 *
8434
 * In non-strict mode, non-fatal issues will be recovered and simply listed
8435
 * in warningList(). This does not prevent more severe errors to cause an
8436
 * exception to be thrown.
8437
 *
8438
 * @throw ParsingException if the string cannot be parsed.
8439
 */
8440
2.95k
BaseObjectNNPtr WKTParser::createFromWKT(const std::string &wkt) {
8441
8442
2.95k
    const auto dialect = guessDialect(wkt);
8443
2.95k
    d->maybeEsriStyle_ = (dialect == WKTGuessedDialect::WKT1_ESRI);
8444
2.95k
    if (d->maybeEsriStyle_) {
8445
1.72k
        if (wkt.find("PARAMETER[\"X_Scale\",") != std::string::npos) {
8446
51
            d->esriStyle_ = true;
8447
51
            d->maybeEsriStyle_ = false;
8448
51
        }
8449
1.72k
    }
8450
8451
2.95k
    const auto build = [this, &wkt]() -> BaseObjectNNPtr {
8452
2.95k
        size_t indexEnd;
8453
2.95k
        WKTNodeNNPtr root = WKTNode::createFrom(wkt, 0, 0, indexEnd);
8454
2.95k
        const std::string &name(root->GP()->value());
8455
2.95k
        if (ci_equal(name, WKTConstants::DATUM) ||
8456
2.55k
            ci_equal(name, WKTConstants::GEODETICDATUM) ||
8457
2.55k
            ci_equal(name, WKTConstants::TRF)) {
8458
8459
95
            auto primeMeridian = PrimeMeridian::GREENWICH;
8460
95
            if (indexEnd < wkt.size()) {
8461
83
                indexEnd = skipSpace(wkt, indexEnd);
8462
83
                if (indexEnd < wkt.size() && wkt[indexEnd] == ',') {
8463
65
                    ++indexEnd;
8464
65
                    indexEnd = skipSpace(wkt, indexEnd);
8465
65
                    if (indexEnd < wkt.size() &&
8466
59
                        ci_starts_with(wkt.c_str() + indexEnd,
8467
59
                                       WKTConstants::PRIMEM.c_str())) {
8468
29
                        primeMeridian = d->buildPrimeMeridian(
8469
29
                            WKTNode::createFrom(wkt, indexEnd, 0, indexEnd),
8470
29
                            UnitOfMeasure::DEGREE);
8471
29
                    }
8472
65
                }
8473
83
            }
8474
95
            return d->buildGeodeticReferenceFrame(root, primeMeridian,
8475
95
                                                  null_node);
8476
2.86k
        } else if (ci_equal(name, WKTConstants::GEOGCS) ||
8477
2.06k
                   ci_equal(name, WKTConstants::PROJCS)) {
8478
            // Parse implicit compoundCRS from ESRI that is
8479
            // "PROJCS[...],VERTCS[...]" or "GEOGCS[...],VERTCS[...]"
8480
1.24k
            if (indexEnd < wkt.size()) {
8481
905
                indexEnd = skipSpace(wkt, indexEnd);
8482
905
                if (indexEnd < wkt.size() && wkt[indexEnd] == ',') {
8483
327
                    ++indexEnd;
8484
327
                    indexEnd = skipSpace(wkt, indexEnd);
8485
327
                    if (indexEnd < wkt.size() &&
8486
322
                        ci_starts_with(wkt.c_str() + indexEnd,
8487
322
                                       WKTConstants::VERTCS.c_str())) {
8488
235
                        auto horizCRS = d->buildCRS(root);
8489
235
                        if (horizCRS) {
8490
206
                            auto vertCRS =
8491
206
                                d->buildVerticalCRS(WKTNode::createFrom(
8492
206
                                    wkt, indexEnd, 0, indexEnd));
8493
206
                            return CompoundCRS::createLax(
8494
206
                                util::PropertyMap().set(
8495
206
                                    IdentifiedObject::NAME_KEY,
8496
206
                                    horizCRS->nameStr() + " + " +
8497
206
                                        vertCRS->nameStr()),
8498
206
                                {NN_NO_CHECK(horizCRS), vertCRS},
8499
206
                                d->dbContext_);
8500
206
                        }
8501
235
                    }
8502
327
                }
8503
905
            }
8504
1.24k
        }
8505
2.65k
        return d->build(root);
8506
2.95k
    };
8507
8508
2.95k
    auto obj = build();
8509
8510
2.95k
    if (dialect == WKTGuessedDialect::WKT1_GDAL ||
8511
1.33k
        dialect == WKTGuessedDialect::WKT1_ESRI) {
8512
887
        auto errorMsg = pj_wkt1_parse(wkt);
8513
887
        if (!errorMsg.empty()) {
8514
555
            d->emitGrammarError(errorMsg);
8515
555
        }
8516
2.06k
    } else if (dialect == WKTGuessedDialect::WKT2_2015 ||
8517
532
               dialect == WKTGuessedDialect::WKT2_2019) {
8518
532
        auto errorMsg = pj_wkt2_parse(wkt);
8519
532
        if (!errorMsg.empty()) {
8520
473
            d->emitGrammarError(errorMsg);
8521
473
        }
8522
532
    }
8523
8524
2.95k
    return obj;
8525
2.95k
}
8526
8527
// ---------------------------------------------------------------------------
8528
8529
/** \brief Attach a database context, to allow queries in it if needed.
8530
 */
8531
WKTParser &
8532
2.95k
WKTParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) {
8533
2.95k
    d->dbContext_ = dbContext;
8534
2.95k
    return *this;
8535
2.95k
}
8536
8537
// ---------------------------------------------------------------------------
8538
8539
/** \brief Guess the "dialect" of the WKT string.
8540
 */
8541
WKTParser::WKTGuessedDialect
8542
2.95k
WKTParser::guessDialect(const std::string &inputWkt) noexcept {
8543
8544
    // cppcheck complains (rightly) that the method could be static
8545
2.95k
    (void)this;
8546
8547
2.95k
    std::string wkt = inputWkt;
8548
2.95k
    std::size_t idxFirstCharNotSpace = wkt.find_first_not_of(" \t\r\n");
8549
2.95k
    if (idxFirstCharNotSpace > 0 && idxFirstCharNotSpace != std::string::npos) {
8550
0
        wkt = wkt.substr(idxFirstCharNotSpace);
8551
0
    }
8552
2.95k
    if (ci_starts_with(wkt, WKTConstants::VERTCS)) {
8553
182
        return WKTGuessedDialect::WKT1_ESRI;
8554
182
    }
8555
2.77k
    const std::string *const wkt1_keywords[] = {
8556
2.77k
        &WKTConstants::GEOCCS, &WKTConstants::GEOGCS,  &WKTConstants::COMPD_CS,
8557
2.77k
        &WKTConstants::PROJCS, &WKTConstants::VERT_CS, &WKTConstants::LOCAL_CS};
8558
11.6k
    for (const auto &pointerKeyword : wkt1_keywords) {
8559
11.6k
        if (ci_starts_with(wkt, *pointerKeyword)) {
8560
8561
1.84k
            if ((ci_find(wkt, "GEOGCS[\"GCS_") != std::string::npos ||
8562
1.52k
                 (!ci_starts_with(wkt, WKTConstants::LOCAL_CS) &&
8563
1.38k
                  ci_find(wkt, "AXIS[") == std::string::npos &&
8564
1.27k
                  ci_find(wkt, "AUTHORITY[") == std::string::npos)) &&
8565
                // WKT1:GDAL and WKT1:ESRI have both a
8566
                // Hotine_Oblique_Mercator_Azimuth_Center If providing a
8567
                // WKT1:GDAL without AXIS, we may wrongly detect it as WKT1:ESRI
8568
                // and skip the rectified_grid_angle parameter cf
8569
                // https://github.com/OSGeo/PROJ/issues/3279
8570
1.55k
                ci_find(wkt, "PARAMETER[\"rectified_grid_angle") ==
8571
1.55k
                    std::string::npos) {
8572
1.54k
                return WKTGuessedDialect::WKT1_ESRI;
8573
1.54k
            }
8574
8575
304
            return WKTGuessedDialect::WKT1_GDAL;
8576
1.84k
        }
8577
11.6k
    }
8578
8579
929
    const std::string *const wkt2_2019_only_keywords[] = {
8580
929
        &WKTConstants::GEOGCRS,
8581
        // contained in previous one
8582
        // &WKTConstants::BASEGEOGCRS,
8583
929
        &WKTConstants::CONCATENATEDOPERATION, &WKTConstants::USAGE,
8584
929
        &WKTConstants::DYNAMIC, &WKTConstants::FRAMEEPOCH, &WKTConstants::MODEL,
8585
929
        &WKTConstants::VELOCITYGRID, &WKTConstants::ENSEMBLE,
8586
929
        &WKTConstants::DERIVEDPROJCRS, &WKTConstants::BASEPROJCRS,
8587
929
        &WKTConstants::GEOGRAPHICCRS, &WKTConstants::TRF, &WKTConstants::VRF,
8588
929
        &WKTConstants::POINTMOTIONOPERATION};
8589
8590
12.4k
    for (const auto &pointerKeyword : wkt2_2019_only_keywords) {
8591
12.4k
        auto pos = ci_find(wkt, *pointerKeyword);
8592
12.4k
        if (pos != std::string::npos &&
8593
322
            wkt[pos + pointerKeyword->size()] == '[') {
8594
257
            return WKTGuessedDialect::WKT2_2019;
8595
257
        }
8596
12.4k
    }
8597
672
    static const char *const wkt2_2019_only_substrings[] = {
8598
672
        "CS[TemporalDateTime,",
8599
672
        "CS[TemporalCount,",
8600
672
        "CS[TemporalMeasure,",
8601
672
    };
8602
2.00k
    for (const auto &substrings : wkt2_2019_only_substrings) {
8603
2.00k
        if (ci_find(wkt, substrings) != std::string::npos) {
8604
14
            return WKTGuessedDialect::WKT2_2019;
8605
14
        }
8606
2.00k
    }
8607
8608
23.5k
    for (const auto &wktConstant : WKTConstants::constants()) {
8609
23.5k
        if (ci_starts_with(wkt, wktConstant)) {
8610
659
            for (auto wktPtr = wkt.c_str() + wktConstant.size();
8611
866
                 *wktPtr != '\0'; ++wktPtr) {
8612
866
                if (isspace(static_cast<unsigned char>(*wktPtr)))
8613
207
                    continue;
8614
659
                if (*wktPtr == '[') {
8615
658
                    return WKTGuessedDialect::WKT2_2015;
8616
658
                }
8617
1
                break;
8618
659
            }
8619
659
        }
8620
23.5k
    }
8621
8622
0
    return WKTGuessedDialect::NOT_WKT;
8623
658
}
8624
8625
// ---------------------------------------------------------------------------
8626
8627
//! @cond Doxygen_Suppress
8628
FormattingException::FormattingException(const char *message)
8629
12
    : Exception(message) {}
8630
8631
// ---------------------------------------------------------------------------
8632
8633
FormattingException::FormattingException(const std::string &message)
8634
559
    : Exception(message) {}
8635
8636
// ---------------------------------------------------------------------------
8637
8638
0
FormattingException::FormattingException(const FormattingException &) = default;
8639
8640
// ---------------------------------------------------------------------------
8641
8642
571
FormattingException::~FormattingException() = default;
8643
8644
// ---------------------------------------------------------------------------
8645
8646
4
void FormattingException::Throw(const char *msg) {
8647
4
    throw FormattingException(msg);
8648
4
}
8649
8650
// ---------------------------------------------------------------------------
8651
8652
0
void FormattingException::Throw(const std::string &msg) {
8653
0
    throw FormattingException(msg);
8654
0
}
8655
8656
// ---------------------------------------------------------------------------
8657
8658
4.43k
ParsingException::ParsingException(const char *message) : Exception(message) {}
8659
8660
// ---------------------------------------------------------------------------
8661
8662
ParsingException::ParsingException(const std::string &message)
8663
3.43k
    : Exception(message) {}
8664
8665
// ---------------------------------------------------------------------------
8666
8667
0
ParsingException::ParsingException(const ParsingException &) = default;
8668
8669
// ---------------------------------------------------------------------------
8670
8671
7.86k
ParsingException::~ParsingException() = default;
8672
8673
// ---------------------------------------------------------------------------
8674
8675
1.23M
IPROJStringExportable::~IPROJStringExportable() = default;
8676
8677
// ---------------------------------------------------------------------------
8678
8679
std::string IPROJStringExportable::exportToPROJString(
8680
123k
    PROJStringFormatter *formatter) const {
8681
123k
    const bool bIsCRS = dynamic_cast<const crs::CRS *>(this) != nullptr;
8682
123k
    if (bIsCRS) {
8683
0
        formatter->setCRSExport(true);
8684
0
    }
8685
123k
    _exportToPROJString(formatter);
8686
123k
    if (formatter->getAddNoDefs() && bIsCRS) {
8687
0
        if (!formatter->hasParam("no_defs")) {
8688
0
            formatter->addParam("no_defs");
8689
0
        }
8690
0
    }
8691
123k
    if (bIsCRS) {
8692
0
        if (!formatter->hasParam("type")) {
8693
0
            formatter->addParam("type", "crs");
8694
0
        }
8695
0
        formatter->setCRSExport(false);
8696
0
    }
8697
123k
    return formatter->toString();
8698
123k
}
8699
//! @endcond
8700
8701
// ---------------------------------------------------------------------------
8702
8703
//! @cond Doxygen_Suppress
8704
8705
struct Step {
8706
    std::string name{};
8707
    bool isInit = false;
8708
    bool inverted{false};
8709
8710
    struct KeyValue {
8711
        std::string key{};
8712
        std::string value{};
8713
        bool usedByParser = false; // only for PROJStringParser used
8714
8715
3.31M
        explicit KeyValue(const std::string &keyIn) : key(keyIn) {}
8716
8717
        KeyValue(const char *keyIn, const std::string &valueIn);
8718
8719
        KeyValue(const std::string &keyIn, const std::string &valueIn)
8720
8.07M
            : key(keyIn), value(valueIn) {}
8721
8722
        // cppcheck-suppress functionStatic
8723
6.32M
        bool keyEquals(const char *otherKey) const noexcept {
8724
6.32M
            return key == otherKey;
8725
6.32M
        }
8726
8727
        // cppcheck-suppress functionStatic
8728
2.15M
        bool equals(const char *otherKey, const char *otherVal) const noexcept {
8729
2.15M
            return key == otherKey && value == otherVal;
8730
2.15M
        }
8731
8732
620
        bool operator==(const KeyValue &other) const noexcept {
8733
620
            return key == other.key && value == other.value;
8734
620
        }
8735
8736
122k
        bool operator!=(const KeyValue &other) const noexcept {
8737
122k
            return key != other.key || value != other.value;
8738
122k
        }
8739
    };
8740
8741
    std::vector<KeyValue> paramValues{};
8742
8743
54.9k
    bool hasKey(const char *keyName) const {
8744
551k
        for (const auto &kv : paramValues) {
8745
551k
            if (kv.key == keyName) {
8746
37
                return true;
8747
37
            }
8748
551k
        }
8749
54.8k
        return false;
8750
54.9k
    }
8751
};
8752
8753
Step::KeyValue::KeyValue(const char *keyIn, const std::string &valueIn)
8754
83.6k
    : key(keyIn), value(valueIn) {}
8755
8756
struct PROJStringFormatter::Private {
8757
    PROJStringFormatter::Convention convention_ =
8758
        PROJStringFormatter::Convention::PROJ_5;
8759
    std::vector<double> toWGS84Parameters_{};
8760
    std::string vDatumExtension_{};
8761
    std::string geoidCRSValue_{};
8762
    std::string hDatumExtension_{};
8763
    crs::GeographicCRSPtr geogCRSOfCompoundCRS_{};
8764
8765
    std::list<Step> steps_{};
8766
    std::vector<Step::KeyValue> globalParamValues_{};
8767
8768
    struct InversionStackElt {
8769
        std::list<Step>::iterator startIter{};
8770
        bool iterValid = false;
8771
        bool currentInversionState = false;
8772
    };
8773
    std::vector<InversionStackElt> inversionStack_{InversionStackElt()};
8774
    bool omitProjLongLatIfPossible_ = false;
8775
    std::vector<bool> omitZUnitConversion_{false};
8776
    std::vector<bool> omitHorizontalConversionInVertTransformation_{false};
8777
    DatabaseContextPtr dbContext_{};
8778
    bool useApproxTMerc_ = false;
8779
    bool addNoDefs_ = true;
8780
    bool coordOperationOptimizations_ = false;
8781
    bool crsExport_ = false;
8782
    bool legacyCRSToCRSContext_ = false;
8783
    bool multiLine_ = false;
8784
    bool normalizeOutput_ = false;
8785
    int indentWidth_ = 2;
8786
    int indentLevel_ = 0;
8787
    int maxLineLength_ = 80;
8788
8789
    std::string result_{};
8790
8791
    // cppcheck-suppress functionStatic
8792
    void appendToResult(const char *str);
8793
8794
    // cppcheck-suppress functionStatic
8795
    void addStep();
8796
};
8797
8798
//! @endcond
8799
8800
// ---------------------------------------------------------------------------
8801
8802
//! @cond Doxygen_Suppress
8803
PROJStringFormatter::PROJStringFormatter(Convention conventionIn,
8804
                                         const DatabaseContextPtr &dbContext)
8805
376k
    : d(std::make_unique<Private>()) {
8806
376k
    d->convention_ = conventionIn;
8807
376k
    d->dbContext_ = dbContext;
8808
376k
}
8809
//! @endcond
8810
8811
// ---------------------------------------------------------------------------
8812
8813
//! @cond Doxygen_Suppress
8814
376k
PROJStringFormatter::~PROJStringFormatter() = default;
8815
//! @endcond
8816
8817
// ---------------------------------------------------------------------------
8818
8819
/** \brief Constructs a new formatter.
8820
 *
8821
 * A formatter can be used only once (its internal state is mutated)
8822
 *
8823
 * Its default behavior can be adjusted with the different setters.
8824
 *
8825
 * @param conventionIn PROJ string flavor. Defaults to Convention::PROJ_5
8826
 * @param dbContext Database context (can help to find alternative grid names).
8827
 * May be nullptr
8828
 * @return new formatter.
8829
 */
8830
PROJStringFormatterNNPtr
8831
PROJStringFormatter::create(Convention conventionIn,
8832
376k
                            DatabaseContextPtr dbContext) {
8833
376k
    return NN_NO_CHECK(PROJStringFormatter::make_unique<PROJStringFormatter>(
8834
376k
        conventionIn, dbContext));
8835
376k
}
8836
8837
// ---------------------------------------------------------------------------
8838
8839
/** \brief Set whether approximate Transverse Mercator or UTM should be used */
8840
0
void PROJStringFormatter::setUseApproxTMerc(bool flag) {
8841
0
    d->useApproxTMerc_ = flag;
8842
0
}
8843
8844
// ---------------------------------------------------------------------------
8845
8846
/** \brief Whether to use multi line output or not. */
8847
PROJStringFormatter &
8848
0
PROJStringFormatter::setMultiLine(bool multiLine) noexcept {
8849
0
    d->multiLine_ = multiLine;
8850
0
    return *this;
8851
0
}
8852
8853
// ---------------------------------------------------------------------------
8854
8855
/** \brief Set number of spaces for each indentation level (defaults to 2).
8856
 */
8857
PROJStringFormatter &
8858
0
PROJStringFormatter::setIndentationWidth(int width) noexcept {
8859
0
    d->indentWidth_ = width;
8860
0
    return *this;
8861
0
}
8862
8863
// ---------------------------------------------------------------------------
8864
8865
/** \brief Set the maximum size of a line (when multiline output is enable).
8866
 * Can be set to 0 for unlimited length.
8867
 */
8868
PROJStringFormatter &
8869
0
PROJStringFormatter::setMaxLineLength(int maxLineLength) noexcept {
8870
0
    d->maxLineLength_ = maxLineLength;
8871
0
    return *this;
8872
0
}
8873
8874
// ---------------------------------------------------------------------------
8875
8876
/** \brief Returns the PROJ string. */
8877
255k
const std::string &PROJStringFormatter::toString() const {
8878
8879
255k
    assert(d->inversionStack_.size() == 1);
8880
8881
255k
    d->result_.clear();
8882
8883
255k
    auto &steps = d->steps_;
8884
8885
255k
    if (d->normalizeOutput_) {
8886
        // Sort +key=value options of each step in lexicographic order.
8887
13.4k
        for (auto &step : steps) {
8888
11.5k
            std::sort(step.paramValues.begin(), step.paramValues.end(),
8889
4.02M
                      [](const Step::KeyValue &a, const Step::KeyValue &b) {
8890
4.02M
                          return a.key < b.key;
8891
4.02M
                      });
8892
11.5k
        }
8893
13.4k
    }
8894
8895
3.79M
    for (auto iter = steps.begin(); iter != steps.end();) {
8896
        // Remove no-op helmert
8897
3.54M
        auto &step = *iter;
8898
3.54M
        const auto paramCount = step.paramValues.size();
8899
3.54M
        if (step.name == "helmert" && (paramCount == 3 || paramCount == 8) &&
8900
155k
            step.paramValues[0].equals("x", "0") &&
8901
23.3k
            step.paramValues[1].equals("y", "0") &&
8902
13.1k
            step.paramValues[2].equals("z", "0") &&
8903
12.9k
            (paramCount == 3 ||
8904
483
             (step.paramValues[3].equals("rx", "0") &&
8905
116
              step.paramValues[4].equals("ry", "0") &&
8906
114
              step.paramValues[5].equals("rz", "0") &&
8907
82
              step.paramValues[6].equals("s", "0") &&
8908
12.4k
              step.paramValues[7].keyEquals("convention")))) {
8909
12.4k
            iter = steps.erase(iter);
8910
3.53M
        } else if (d->coordOperationOptimizations_ &&
8911
3.11M
                   step.name == "unitconvert" && paramCount == 2 &&
8912
882k
                   step.paramValues[0].keyEquals("xy_in") &&
8913
871k
                   step.paramValues[1].keyEquals("xy_out") &&
8914
870k
                   step.paramValues[0].value == step.paramValues[1].value) {
8915
44
            iter = steps.erase(iter);
8916
3.53M
        } else if (step.name == "push" && step.inverted) {
8917
34.5k
            step.name = "pop";
8918
34.5k
            step.inverted = false;
8919
34.5k
            ++iter;
8920
3.49M
        } else if (step.name == "pop" && step.inverted) {
8921
43.2k
            step.name = "push";
8922
43.2k
            step.inverted = false;
8923
43.2k
            ++iter;
8924
3.45M
        } else if (step.name == "noop" && steps.size() > 1) {
8925
1.59k
            iter = steps.erase(iter);
8926
3.45M
        } else {
8927
3.45M
            ++iter;
8928
3.45M
        }
8929
3.54M
    }
8930
8931
3.53M
    for (auto &step : steps) {
8932
3.53M
        if (!step.inverted) {
8933
1.98M
            continue;
8934
1.98M
        }
8935
8936
1.54M
        const auto paramCount = step.paramValues.size();
8937
8938
        // axisswap order=2,1 (or 1,-2) is its own inverse
8939
1.54M
        if (step.name == "axisswap" && paramCount == 1 &&
8940
361k
            (step.paramValues[0].equals("order", "2,1") ||
8941
349k
             step.paramValues[0].equals("order", "1,-2"))) {
8942
349k
            step.inverted = false;
8943
349k
            continue;
8944
349k
        }
8945
8946
        // axisswap inv order=2,-1 ==> axisswap order -2,1
8947
1.20M
        if (step.name == "axisswap" && paramCount == 1 &&
8948
12.0k
            step.paramValues[0].equals("order", "2,-1")) {
8949
433
            step.inverted = false;
8950
433
            step.paramValues[0] = Step::KeyValue("order", "-2,1");
8951
433
            continue;
8952
433
        }
8953
8954
        // axisswap order=1,2,-3 is its own inverse
8955
1.20M
        if (step.name == "axisswap" && paramCount == 1 &&
8956
11.6k
            step.paramValues[0].equals("order", "1,2,-3")) {
8957
0
            step.inverted = false;
8958
0
            continue;
8959
0
        }
8960
8961
        // handle unitconvert inverse
8962
1.20M
        if (step.name == "unitconvert" && paramCount == 2 &&
8963
470k
            step.paramValues[0].keyEquals("xy_in") &&
8964
461k
            step.paramValues[1].keyEquals("xy_out")) {
8965
460k
            std::swap(step.paramValues[0].value, step.paramValues[1].value);
8966
460k
            step.inverted = false;
8967
460k
            continue;
8968
460k
        }
8969
8970
739k
        if (step.name == "unitconvert" && paramCount == 2 &&
8971
9.44k
            step.paramValues[0].keyEquals("z_in") &&
8972
8.22k
            step.paramValues[1].keyEquals("z_out")) {
8973
7.33k
            std::swap(step.paramValues[0].value, step.paramValues[1].value);
8974
7.33k
            step.inverted = false;
8975
7.33k
            continue;
8976
7.33k
        }
8977
8978
732k
        if (step.name == "unitconvert" && paramCount == 4 &&
8979
33.0k
            step.paramValues[0].keyEquals("xy_in") &&
8980
32.3k
            step.paramValues[1].keyEquals("z_in") &&
8981
32.0k
            step.paramValues[2].keyEquals("xy_out") &&
8982
31.8k
            step.paramValues[3].keyEquals("z_out")) {
8983
31.6k
            std::swap(step.paramValues[0].value, step.paramValues[2].value);
8984
31.6k
            std::swap(step.paramValues[1].value, step.paramValues[3].value);
8985
31.6k
            step.inverted = false;
8986
31.6k
            continue;
8987
31.6k
        }
8988
8989
        // set does the same in forward and inverse paths
8990
700k
        if (step.name == "set") {
8991
4.70k
            step.inverted = false;
8992
4.70k
        }
8993
700k
    }
8994
8995
255k
    {
8996
255k
        auto iterCur = steps.begin();
8997
255k
        if (iterCur != steps.end()) {
8998
248k
            ++iterCur;
8999
248k
        }
9000
4.11M
        while (iterCur != steps.end()) {
9001
9002
3.85M
            assert(iterCur != steps.begin());
9003
3.85M
            auto iterPrev = std::prev(iterCur);
9004
3.85M
            auto &prevStep = *iterPrev;
9005
3.85M
            auto &curStep = *iterCur;
9006
9007
3.85M
            const auto curStepParamCount = curStep.paramValues.size();
9008
3.85M
            const auto prevStepParamCount = prevStep.paramValues.size();
9009
9010
3.85M
            const auto deletePrevAndCurIter = [&steps, &iterPrev, &iterCur]() {
9011
765k
                iterCur = steps.erase(iterPrev, std::next(iterCur));
9012
765k
                if (iterCur != steps.begin())
9013
693k
                    iterCur = std::prev(iterCur);
9014
765k
                if (iterCur == steps.begin() && iterCur != steps.end())
9015
151k
                    ++iterCur;
9016
765k
            };
9017
9018
            // longlat (or its inverse) with ellipsoid only is a no-op
9019
            // do that only for an internal step
9020
3.85M
            if (std::next(iterCur) != steps.end() &&
9021
3.61M
                curStep.name == "longlat" && curStepParamCount == 1 &&
9022
369k
                curStep.paramValues[0].keyEquals("ellps")) {
9023
341k
                iterCur = steps.erase(iterCur);
9024
341k
                continue;
9025
341k
            }
9026
9027
            // push v_x followed by pop v_x is a no-op.
9028
3.51M
            if (curStep.name == "pop" && prevStep.name == "push" &&
9029
10.0k
                !curStep.inverted && !prevStep.inverted &&
9030
10.0k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9031
8.87k
                curStep.paramValues[0].key == prevStep.paramValues[0].key) {
9032
8.49k
                deletePrevAndCurIter();
9033
8.49k
                continue;
9034
8.49k
            }
9035
9036
            // pop v_x followed by push v_x is, almost, a no-op. For our
9037
            // purposes,
9038
            // we consider it as a no-op for better pipeline optimizations.
9039
3.50M
            if (curStep.name == "push" && prevStep.name == "pop" &&
9040
7.34k
                !curStep.inverted && !prevStep.inverted &&
9041
7.34k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9042
5.89k
                curStep.paramValues[0].key == prevStep.paramValues[0].key) {
9043
5.21k
                deletePrevAndCurIter();
9044
5.21k
                continue;
9045
5.21k
            }
9046
9047
            // unitconvert (xy) followed by its inverse is a no-op
9048
3.50M
            if (curStep.name == "unitconvert" &&
9049
1.28M
                prevStep.name == "unitconvert" && !curStep.inverted &&
9050
397k
                !prevStep.inverted && curStepParamCount == 2 &&
9051
364k
                prevStepParamCount == 2 &&
9052
338k
                curStep.paramValues[0].keyEquals("xy_in") &&
9053
333k
                prevStep.paramValues[0].keyEquals("xy_in") &&
9054
326k
                curStep.paramValues[1].keyEquals("xy_out") &&
9055
326k
                prevStep.paramValues[1].keyEquals("xy_out") &&
9056
326k
                curStep.paramValues[0].value == prevStep.paramValues[1].value &&
9057
326k
                curStep.paramValues[1].value == prevStep.paramValues[0].value) {
9058
326k
                deletePrevAndCurIter();
9059
326k
                continue;
9060
326k
            }
9061
9062
            // unitconvert (z) followed by its inverse is a no-op
9063
3.17M
            if (curStep.name == "unitconvert" &&
9064
959k
                prevStep.name == "unitconvert" && !curStep.inverted &&
9065
71.3k
                !prevStep.inverted && curStepParamCount == 2 &&
9066
38.3k
                prevStepParamCount == 2 &&
9067
11.8k
                curStep.paramValues[0].keyEquals("z_in") &&
9068
4.18k
                prevStep.paramValues[0].keyEquals("z_in") &&
9069
1.86k
                curStep.paramValues[1].keyEquals("z_out") &&
9070
1.71k
                prevStep.paramValues[1].keyEquals("z_out") &&
9071
1.59k
                curStep.paramValues[0].value == prevStep.paramValues[1].value &&
9072
1.40k
                curStep.paramValues[1].value == prevStep.paramValues[0].value) {
9073
1.26k
                deletePrevAndCurIter();
9074
1.26k
                continue;
9075
1.26k
            }
9076
9077
            // unitconvert (xyz) followed by its inverse is a no-op
9078
3.17M
            if (curStep.name == "unitconvert" &&
9079
958k
                prevStep.name == "unitconvert" && !curStep.inverted &&
9080
70.0k
                !prevStep.inverted && curStepParamCount == 4 &&
9081
30.6k
                prevStepParamCount == 4 &&
9082
18.5k
                curStep.paramValues[0].keyEquals("xy_in") &&
9083
18.3k
                prevStep.paramValues[0].keyEquals("xy_in") &&
9084
18.1k
                curStep.paramValues[1].keyEquals("z_in") &&
9085
17.9k
                prevStep.paramValues[1].keyEquals("z_in") &&
9086
17.9k
                curStep.paramValues[2].keyEquals("xy_out") &&
9087
17.8k
                prevStep.paramValues[2].keyEquals("xy_out") &&
9088
17.8k
                curStep.paramValues[3].keyEquals("z_out") &&
9089
17.8k
                prevStep.paramValues[3].keyEquals("z_out") &&
9090
17.7k
                curStep.paramValues[0].value == prevStep.paramValues[2].value &&
9091
17.2k
                curStep.paramValues[1].value == prevStep.paramValues[3].value &&
9092
17.0k
                curStep.paramValues[2].value == prevStep.paramValues[0].value &&
9093
16.8k
                curStep.paramValues[3].value == prevStep.paramValues[1].value) {
9094
14.9k
                deletePrevAndCurIter();
9095
14.9k
                continue;
9096
14.9k
            }
9097
9098
3.15M
            const auto deletePrevIter = [&steps, &iterPrev, &iterCur]() {
9099
20.3k
                steps.erase(iterPrev, iterCur);
9100
20.3k
                if (iterCur != steps.begin())
9101
17.3k
                    iterCur = std::prev(iterCur);
9102
20.3k
                if (iterCur == steps.begin())
9103
4.74k
                    ++iterCur;
9104
20.3k
            };
9105
9106
            // combine unitconvert (xy) and unitconvert (z)
9107
3.15M
            bool changeDone = false;
9108
9.46M
            for (int k = 0; k < 2; ++k) {
9109
6.31M
                auto &first = (k == 0) ? curStep : prevStep;
9110
6.31M
                auto &second = (k == 0) ? prevStep : curStep;
9111
6.31M
                if (first.name == "unitconvert" &&
9112
1.56M
                    second.name == "unitconvert" && !first.inverted &&
9113
107k
                    !second.inverted && first.paramValues.size() == 2 &&
9114
57.9k
                    second.paramValues.size() == 2 &&
9115
19.1k
                    second.paramValues[0].keyEquals("xy_in") &&
9116
10.1k
                    second.paramValues[1].keyEquals("xy_out") &&
9117
9.67k
                    first.paramValues[0].keyEquals("z_in") &&
9118
8.86k
                    first.paramValues[1].keyEquals("z_out")) {
9119
9120
8.47k
                    const std::string xy_in(second.paramValues[0].value);
9121
8.47k
                    const std::string xy_out(second.paramValues[1].value);
9122
8.47k
                    const std::string z_in(first.paramValues[0].value);
9123
8.47k
                    const std::string z_out(first.paramValues[1].value);
9124
9125
8.47k
                    iterCur->paramValues.clear();
9126
8.47k
                    iterCur->paramValues.emplace_back(
9127
8.47k
                        Step::KeyValue("xy_in", xy_in));
9128
8.47k
                    iterCur->paramValues.emplace_back(
9129
8.47k
                        Step::KeyValue("z_in", z_in));
9130
8.47k
                    iterCur->paramValues.emplace_back(
9131
8.47k
                        Step::KeyValue("xy_out", xy_out));
9132
8.47k
                    iterCur->paramValues.emplace_back(
9133
8.47k
                        Step::KeyValue("z_out", z_out));
9134
9135
8.47k
                    deletePrevIter();
9136
8.47k
                    changeDone = true;
9137
8.47k
                    break;
9138
8.47k
                }
9139
6.31M
            }
9140
3.15M
            if (changeDone) {
9141
8.47k
                continue;
9142
8.47k
            }
9143
9144
            // +step +proj=unitconvert +xy_in=X1 +xy_out=X2
9145
            //  +step +proj=unitconvert +xy_in=X2 +z_in=Z1 +xy_out=X1 +z_out=Z2
9146
            // ==> step +proj=unitconvert +z_in=Z1 +z_out=Z2
9147
9.40M
            for (int k = 0; k < 2; ++k) {
9148
6.29M
                auto &first = (k == 0) ? curStep : prevStep;
9149
6.29M
                auto &second = (k == 0) ? prevStep : curStep;
9150
6.29M
                if (first.name == "unitconvert" &&
9151
1.53M
                    second.name == "unitconvert" && !first.inverted &&
9152
82.5k
                    !second.inverted && first.paramValues.size() == 4 &&
9153
45.5k
                    second.paramValues.size() == 2 &&
9154
38.2k
                    first.paramValues[0].keyEquals("xy_in") &&
9155
37.7k
                    first.paramValues[1].keyEquals("z_in") &&
9156
37.5k
                    first.paramValues[2].keyEquals("xy_out") &&
9157
37.3k
                    first.paramValues[3].keyEquals("z_out") &&
9158
37.2k
                    second.paramValues[0].keyEquals("xy_in") &&
9159
35.6k
                    second.paramValues[1].keyEquals("xy_out") &&
9160
35.6k
                    first.paramValues[0].value == second.paramValues[1].value &&
9161
35.0k
                    first.paramValues[2].value == second.paramValues[0].value) {
9162
35.0k
                    const std::string z_in(first.paramValues[1].value);
9163
35.0k
                    const std::string z_out(first.paramValues[3].value);
9164
35.0k
                    if (z_in != z_out) {
9165
8.56k
                        iterCur->paramValues.clear();
9166
8.56k
                        iterCur->paramValues.emplace_back(
9167
8.56k
                            Step::KeyValue("z_in", z_in));
9168
8.56k
                        iterCur->paramValues.emplace_back(
9169
8.56k
                            Step::KeyValue("z_out", z_out));
9170
8.56k
                        deletePrevIter();
9171
26.4k
                    } else {
9172
26.4k
                        deletePrevAndCurIter();
9173
26.4k
                    }
9174
35.0k
                    changeDone = true;
9175
35.0k
                    break;
9176
35.0k
                }
9177
6.29M
            }
9178
3.15M
            if (changeDone) {
9179
35.0k
                continue;
9180
35.0k
            }
9181
9182
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2
9183
            // +step +proj=unitconvert +z_in=Z2 +z_out=Z3
9184
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2
9185
            // +z_out=Z3
9186
3.11M
            if (prevStep.name == "unitconvert" &&
9187
575k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9188
11.4k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9189
5.32k
                curStep.paramValues.size() == 2 &&
9190
1.71k
                prevStep.paramValues[0].keyEquals("xy_in") &&
9191
1.48k
                prevStep.paramValues[1].keyEquals("z_in") &&
9192
1.38k
                prevStep.paramValues[2].keyEquals("xy_out") &&
9193
1.27k
                prevStep.paramValues[3].keyEquals("z_out") &&
9194
1.21k
                curStep.paramValues[0].keyEquals("z_in") &&
9195
848
                curStep.paramValues[1].keyEquals("z_out") &&
9196
761
                prevStep.paramValues[3].value == curStep.paramValues[0].value) {
9197
658
                const std::string xy_in(prevStep.paramValues[0].value);
9198
658
                const std::string z_in(prevStep.paramValues[1].value);
9199
658
                const std::string xy_out(prevStep.paramValues[2].value);
9200
658
                const std::string z_out(curStep.paramValues[1].value);
9201
9202
658
                iterCur->paramValues.clear();
9203
658
                iterCur->paramValues.emplace_back(
9204
658
                    Step::KeyValue("xy_in", xy_in));
9205
658
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9206
658
                iterCur->paramValues.emplace_back(
9207
658
                    Step::KeyValue("xy_out", xy_out));
9208
658
                iterCur->paramValues.emplace_back(
9209
658
                    Step::KeyValue("z_out", z_out));
9210
9211
658
                deletePrevIter();
9212
658
                continue;
9213
658
            }
9214
9215
            // +step +proj=unitconvert +z_in=Z1 +z_out=Z2
9216
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z2 +xy_out=X2 +z_out=Z3
9217
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2
9218
            // +z_out=Z3
9219
3.11M
            if (prevStep.name == "unitconvert" &&
9220
575k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9221
10.7k
                !curStep.inverted && prevStep.paramValues.size() == 2 &&
9222
3.94k
                curStep.paramValues.size() == 4 &&
9223
1.55k
                prevStep.paramValues[0].keyEquals("z_in") &&
9224
621
                prevStep.paramValues[1].keyEquals("z_out") &&
9225
562
                curStep.paramValues[0].keyEquals("xy_in") &&
9226
516
                curStep.paramValues[1].keyEquals("z_in") &&
9227
499
                curStep.paramValues[2].keyEquals("xy_out") &&
9228
488
                curStep.paramValues[3].keyEquals("z_out") &&
9229
488
                prevStep.paramValues[1].value == curStep.paramValues[1].value) {
9230
113
                const std::string xy_in(curStep.paramValues[0].value);
9231
113
                const std::string z_in(prevStep.paramValues[0].value);
9232
113
                const std::string xy_out(curStep.paramValues[2].value);
9233
113
                const std::string z_out(curStep.paramValues[3].value);
9234
9235
113
                iterCur->paramValues.clear();
9236
113
                iterCur->paramValues.emplace_back(
9237
113
                    Step::KeyValue("xy_in", xy_in));
9238
113
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9239
113
                iterCur->paramValues.emplace_back(
9240
113
                    Step::KeyValue("xy_out", xy_out));
9241
113
                iterCur->paramValues.emplace_back(
9242
113
                    Step::KeyValue("z_out", z_out));
9243
9244
113
                deletePrevIter();
9245
113
                continue;
9246
113
            }
9247
9248
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2
9249
            // +step +proj=unitconvert +xy_in=X2 +xy_out=X3
9250
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X3
9251
            // +z_out=Z2
9252
3.11M
            if (prevStep.name == "unitconvert" &&
9253
575k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9254
10.6k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9255
4.66k
                curStep.paramValues.size() == 2 &&
9256
1.05k
                prevStep.paramValues[0].keyEquals("xy_in") &&
9257
824
                prevStep.paramValues[1].keyEquals("z_in") &&
9258
724
                prevStep.paramValues[2].keyEquals("xy_out") &&
9259
620
                prevStep.paramValues[3].keyEquals("z_out") &&
9260
558
                curStep.paramValues[0].keyEquals("xy_in") &&
9261
285
                curStep.paramValues[1].keyEquals("xy_out") &&
9262
228
                prevStep.paramValues[2].value == curStep.paramValues[0].value) {
9263
31
                const std::string xy_in(prevStep.paramValues[0].value);
9264
31
                const std::string z_in(prevStep.paramValues[1].value);
9265
31
                const std::string xy_out(curStep.paramValues[1].value);
9266
31
                const std::string z_out(prevStep.paramValues[3].value);
9267
9268
31
                iterCur->paramValues.clear();
9269
31
                iterCur->paramValues.emplace_back(
9270
31
                    Step::KeyValue("xy_in", xy_in));
9271
31
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9272
31
                iterCur->paramValues.emplace_back(
9273
31
                    Step::KeyValue("xy_out", xy_out));
9274
31
                iterCur->paramValues.emplace_back(
9275
31
                    Step::KeyValue("z_out", z_out));
9276
9277
31
                deletePrevIter();
9278
31
                continue;
9279
31
            }
9280
9281
            // clang-format off
9282
            // A bit odd. Used to simplify geog3d_feet -> EPSG:6318+6360
9283
            // of https://github.com/OSGeo/PROJ/issues/3938
9284
            // where we get originally
9285
            // +step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=rad +z_out=us-ft
9286
            // +step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m
9287
            // and want it simplified as:
9288
            // +step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=deg +z_out=us-ft
9289
            //
9290
            // More generally:
9291
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2
9292
            // +step +proj=unitconvert +xy_in=X2 +z_in=Z3 +xy_out=X3 +z_out=Z3
9293
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X3 +z_out=Z2
9294
            // clang-format on
9295
3.11M
            if (prevStep.name == "unitconvert" &&
9296
575k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9297
10.6k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9298
4.63k
                curStep.paramValues.size() == 4 &&
9299
3.56k
                prevStep.paramValues[0].keyEquals("xy_in") &&
9300
3.32k
                prevStep.paramValues[1].keyEquals("z_in") &&
9301
3.16k
                prevStep.paramValues[2].keyEquals("xy_out") &&
9302
3.10k
                prevStep.paramValues[3].keyEquals("z_out") &&
9303
2.96k
                curStep.paramValues[0].keyEquals("xy_in") &&
9304
2.84k
                curStep.paramValues[1].keyEquals("z_in") &&
9305
2.82k
                curStep.paramValues[2].keyEquals("xy_out") &&
9306
2.78k
                curStep.paramValues[3].keyEquals("z_out") &&
9307
2.78k
                prevStep.paramValues[2].value == curStep.paramValues[0].value &&
9308
2.30k
                curStep.paramValues[1].value == curStep.paramValues[3].value) {
9309
1.54k
                const std::string xy_in(prevStep.paramValues[0].value);
9310
1.54k
                const std::string z_in(prevStep.paramValues[1].value);
9311
1.54k
                const std::string xy_out(curStep.paramValues[2].value);
9312
1.54k
                const std::string z_out(prevStep.paramValues[3].value);
9313
9314
1.54k
                iterCur->paramValues.clear();
9315
1.54k
                iterCur->paramValues.emplace_back(
9316
1.54k
                    Step::KeyValue("xy_in", xy_in));
9317
1.54k
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9318
1.54k
                iterCur->paramValues.emplace_back(
9319
1.54k
                    Step::KeyValue("xy_out", xy_out));
9320
1.54k
                iterCur->paramValues.emplace_back(
9321
1.54k
                    Step::KeyValue("z_out", z_out));
9322
9323
1.54k
                deletePrevIter();
9324
1.54k
                continue;
9325
1.54k
            }
9326
9327
            // clang-format off
9328
            // Variant of above
9329
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z1
9330
            // +step +proj=unitconvert +xy_in=X2 +z_in=Z2 +xy_out=X3 +z_out=Z3
9331
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z2 +xy_out=X3 +z_out=Z3
9332
            // clang-format on
9333
3.11M
            if (prevStep.name == "unitconvert" &&
9334
573k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9335
9.08k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9336
3.08k
                curStep.paramValues.size() == 4 &&
9337
2.01k
                prevStep.paramValues[0].keyEquals("xy_in") &&
9338
1.77k
                prevStep.paramValues[1].keyEquals("z_in") &&
9339
1.61k
                prevStep.paramValues[2].keyEquals("xy_out") &&
9340
1.55k
                prevStep.paramValues[3].keyEquals("z_out") &&
9341
1.42k
                curStep.paramValues[0].keyEquals("xy_in") &&
9342
1.29k
                curStep.paramValues[1].keyEquals("z_in") &&
9343
1.27k
                curStep.paramValues[2].keyEquals("xy_out") &&
9344
1.23k
                curStep.paramValues[3].keyEquals("z_out") &&
9345
1.23k
                prevStep.paramValues[1].value ==
9346
1.23k
                    prevStep.paramValues[3].value &&
9347
1.15k
                curStep.paramValues[0].value == prevStep.paramValues[2].value) {
9348
718
                const std::string xy_in(prevStep.paramValues[0].value);
9349
718
                const std::string z_in(curStep.paramValues[1].value);
9350
718
                const std::string xy_out(curStep.paramValues[2].value);
9351
718
                const std::string z_out(curStep.paramValues[3].value);
9352
9353
718
                iterCur->paramValues.clear();
9354
718
                iterCur->paramValues.emplace_back(
9355
718
                    Step::KeyValue("xy_in", xy_in));
9356
718
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9357
718
                iterCur->paramValues.emplace_back(
9358
718
                    Step::KeyValue("xy_out", xy_out));
9359
718
                iterCur->paramValues.emplace_back(
9360
718
                    Step::KeyValue("z_out", z_out));
9361
9362
718
                deletePrevIter();
9363
718
                continue;
9364
718
            }
9365
9366
            // unitconvert (1), axisswap order=2,1, unitconvert(2)  ==>
9367
            // axisswap order=2,1, unitconvert (1), unitconvert(2) which
9368
            // will get further optimized by previous case
9369
3.11M
            if (std::next(iterCur) != steps.end() &&
9370
2.87M
                prevStep.name == "unitconvert" && curStep.name == "axisswap" &&
9371
309k
                curStepParamCount == 1 &&
9372
308k
                curStep.paramValues[0].equals("order", "2,1")) {
9373
299k
                auto iterNext = std::next(iterCur);
9374
299k
                auto &nextStep = *iterNext;
9375
299k
                if (nextStep.name == "unitconvert") {
9376
2.47k
                    std::swap(*iterPrev, *iterCur);
9377
2.47k
                    ++iterCur;
9378
2.47k
                    continue;
9379
2.47k
                }
9380
299k
            }
9381
9382
            // axisswap order=2,1 followed by itself is a no-op
9383
3.11M
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9384
346k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9385
345k
                curStep.paramValues[0].equals("order", "2,1") &&
9386
341k
                prevStep.paramValues[0].equals("order", "2,1")) {
9387
341k
                deletePrevAndCurIter();
9388
341k
                continue;
9389
341k
            }
9390
9391
            // axisswap order=2,-1 followed by axisswap order=-2,1 is a no-op
9392
2.76M
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9393
5.00k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9394
4.43k
                !prevStep.inverted &&
9395
4.23k
                prevStep.paramValues[0].equals("order", "2,-1") &&
9396
152
                !curStep.inverted &&
9397
152
                curStep.paramValues[0].equals("order", "-2,1")) {
9398
152
                deletePrevAndCurIter();
9399
152
                continue;
9400
152
            }
9401
9402
            // axisswap order=2,-1 followed by axisswap order=1,-2 is
9403
            // equivalent to axisswap order=2,1
9404
2.76M
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9405
4.85k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9406
4.28k
                !prevStep.inverted &&
9407
4.08k
                prevStep.paramValues[0].equals("order", "2,-1") &&
9408
0
                !curStep.inverted &&
9409
0
                curStep.paramValues[0].equals("order", "1,-2")) {
9410
0
                prevStep.inverted = false;
9411
0
                prevStep.paramValues[0] = Step::KeyValue("order", "2,1");
9412
                // Delete this iter
9413
0
                iterCur = steps.erase(iterCur);
9414
0
                continue;
9415
0
            }
9416
9417
            // axisswap order=2,1 followed by axisswap order=2,-1 is
9418
            // equivalent to axisswap order=1,-2
9419
            // Same for axisswap order=-2,1 followed by axisswap order=2,1
9420
2.76M
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9421
4.85k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9422
4.28k
                ((prevStep.paramValues[0].equals("order", "2,1") &&
9423
15
                  !curStep.inverted &&
9424
15
                  curStep.paramValues[0].equals("order", "2,-1")) ||
9425
4.27k
                 (prevStep.paramValues[0].equals("order", "-2,1") &&
9426
61
                  !prevStep.inverted &&
9427
59
                  curStep.paramValues[0].equals("order", "2,1")))) {
9428
9429
16
                prevStep.inverted = false;
9430
16
                prevStep.paramValues[0] = Step::KeyValue("order", "1,-2");
9431
                // Delete this iter
9432
16
                iterCur = steps.erase(iterCur);
9433
16
                continue;
9434
16
            }
9435
9436
            // axisswap order=2,1, unitconvert, axisswap order=2,1 -> can
9437
            // suppress axisswap
9438
2.76M
            if (std::next(iterCur) != steps.end() &&
9439
2.55M
                prevStep.name == "axisswap" && curStep.name == "unitconvert" &&
9440
79.9k
                prevStepParamCount == 1 &&
9441
79.4k
                prevStep.paramValues[0].equals("order", "2,1")) {
9442
67.1k
                auto iterNext = std::next(iterCur);
9443
67.1k
                auto &nextStep = *iterNext;
9444
67.1k
                if (nextStep.name == "axisswap" &&
9445
1.80k
                    nextStep.paramValues.size() == 1 &&
9446
1.80k
                    nextStep.paramValues[0].equals("order", "2,1")) {
9447
1.79k
                    steps.erase(iterPrev);
9448
1.79k
                    steps.erase(iterNext);
9449
                    // Coverity complains about invalid usage of iterCur
9450
                    // due to the above erase(iterNext). To the best of our
9451
                    // understanding, this is a false-positive.
9452
                    // coverity[use_iterator]
9453
1.79k
                    if (iterCur != steps.begin())
9454
1.70k
                        iterCur = std::prev(iterCur);
9455
1.79k
                    if (iterCur == steps.begin())
9456
95
                        ++iterCur;
9457
1.79k
                    continue;
9458
1.79k
                }
9459
67.1k
            }
9460
9461
            // "+proj=set +v_4=X" followed by "+proj=set +v_4=X +omit_fwd"
9462
            // can be optimized as "+proj=set +v_4=X"
9463
2.76M
            if (curStep.name == "set" && prevStep.name == "set" &&
9464
4.31k
                !curStep.inverted && !prevStep.inverted &&
9465
4.31k
                curStepParamCount == 2 && prevStepParamCount == 1 &&
9466
0
                curStep.paramValues[0].keyEquals("v_4") &&
9467
0
                prevStep.paramValues[0].keyEquals("v_4") &&
9468
0
                curStep.paramValues[1].keyEquals("omit_fwd") &&
9469
0
                curStep.paramValues[0].value == prevStep.paramValues[0].value) {
9470
9471
0
                iterCur = steps.erase(iterCur);
9472
0
                continue;
9473
0
            }
9474
9475
            // "+proj=set +v_4=X +omit_inv" followed by "+proj=set +v_4=X"
9476
            // can be optimized as "+proj=set +v_4=X"
9477
2.76M
            if (curStep.name == "set" && prevStep.name == "set" &&
9478
4.31k
                !curStep.inverted && !prevStep.inverted &&
9479
4.31k
                curStepParamCount == 1 && prevStepParamCount == 2 &&
9480
0
                curStep.paramValues[0].keyEquals("v_4") &&
9481
0
                prevStep.paramValues[0].keyEquals("v_4") &&
9482
0
                prevStep.paramValues[1].keyEquals("omit_inv") &&
9483
0
                curStep.paramValues[0].value == prevStep.paramValues[0].value) {
9484
9485
0
                deletePrevIter();
9486
0
                continue;
9487
0
            }
9488
9489
            // for practical purposes WGS84 and GRS80 ellipsoids are
9490
            // equivalents (cartesian transform between both lead to differences
9491
            // of the order of 1e-14 deg..).
9492
            // No need to do a cart roundtrip for that...
9493
            // and actually IGNF uses the GRS80 definition for the WGS84 datum
9494
2.76M
            if (curStep.name == "cart" && prevStep.name == "cart" &&
9495
27.9k
                curStep.inverted == !prevStep.inverted &&
9496
27.9k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9497
27.4k
                ((curStep.paramValues[0].equals("ellps", "WGS84") &&
9498
13.6k
                  prevStep.paramValues[0].equals("ellps", "GRS80")) ||
9499
21.3k
                 (curStep.paramValues[0].equals("ellps", "GRS80") &&
9500
7.01k
                  prevStep.paramValues[0].equals("ellps", "WGS84")))) {
9501
7.01k
                deletePrevAndCurIter();
9502
7.01k
                continue;
9503
7.01k
            }
9504
9505
2.75M
            if (curStep.name == "helmert" && prevStep.name == "helmert" &&
9506
11.8k
                curStepParamCount == 3 &&
9507
9.01k
                curStepParamCount == prevStepParamCount) {
9508
8.27k
                std::map<std::string, double> leftParamsMap;
9509
8.27k
                std::map<std::string, double> rightParamsMap;
9510
8.27k
                try {
9511
24.4k
                    for (const auto &kv : prevStep.paramValues) {
9512
24.4k
                        leftParamsMap[kv.key] = c_locale_stod(kv.value);
9513
24.4k
                    }
9514
24.0k
                    for (const auto &kv : curStep.paramValues) {
9515
24.0k
                        rightParamsMap[kv.key] = c_locale_stod(kv.value);
9516
24.0k
                    }
9517
8.27k
                } catch (const std::invalid_argument &) {
9518
297
                    break;
9519
297
                }
9520
7.97k
                const std::string x("x");
9521
7.97k
                const std::string y("y");
9522
7.97k
                const std::string z("z");
9523
7.97k
                if (leftParamsMap.find(x) != leftParamsMap.end() &&
9524
7.86k
                    leftParamsMap.find(y) != leftParamsMap.end() &&
9525
7.73k
                    leftParamsMap.find(z) != leftParamsMap.end() &&
9526
7.29k
                    rightParamsMap.find(x) != rightParamsMap.end() &&
9527
7.24k
                    rightParamsMap.find(y) != rightParamsMap.end() &&
9528
7.11k
                    rightParamsMap.find(z) != rightParamsMap.end()) {
9529
9530
7.07k
                    const double signLeft = prevStep.inverted ? -1 : 1;
9531
7.07k
                    const double signRight = curStep.inverted ? -1 : 1;
9532
7.07k
                    const double xSum = signLeft * leftParamsMap[x] +
9533
7.07k
                                        signRight * rightParamsMap[x];
9534
7.07k
                    const double ySum = signLeft * leftParamsMap[y] +
9535
7.07k
                                        signRight * rightParamsMap[y];
9536
7.07k
                    const double zSum = signLeft * leftParamsMap[z] +
9537
7.07k
                                        signRight * rightParamsMap[z];
9538
7.07k
                    if (xSum == 0.0 && ySum == 0.0 && zSum == 0.0) {
9539
547
                        deletePrevAndCurIter();
9540
6.52k
                    } else {
9541
6.52k
                        prevStep.inverted = false;
9542
6.52k
                        prevStep.paramValues[0] =
9543
6.52k
                            Step::KeyValue("x", internal::toString(xSum));
9544
6.52k
                        prevStep.paramValues[1] =
9545
6.52k
                            Step::KeyValue("y", internal::toString(ySum));
9546
6.52k
                        prevStep.paramValues[2] =
9547
6.52k
                            Step::KeyValue("z", internal::toString(zSum));
9548
9549
                        // Delete this iter
9550
6.52k
                        iterCur = steps.erase(iterCur);
9551
6.52k
                    }
9552
7.07k
                    continue;
9553
7.07k
                }
9554
7.97k
            }
9555
9556
            // Helmert followed by its inverse is a no-op
9557
2.75M
            if (curStep.name == "helmert" && prevStep.name == "helmert" &&
9558
4.45k
                (curStep.inverted == prevStep.inverted) &&
9559
2.85k
                curStepParamCount == prevStepParamCount) {
9560
1.33k
                std::set<std::string> leftParamsSet;
9561
1.33k
                std::set<std::string> rightParamsSet;
9562
1.33k
                std::map<std::string, std::string> leftParamsMap;
9563
1.33k
                std::map<std::string, std::string> rightParamsMap;
9564
5.16k
                for (const auto &kv : prevStep.paramValues) {
9565
5.16k
                    leftParamsSet.insert(kv.key);
9566
5.16k
                    leftParamsMap[kv.key] = kv.value;
9567
5.16k
                }
9568
5.16k
                for (const auto &kv : curStep.paramValues) {
9569
5.16k
                    rightParamsSet.insert(kv.key);
9570
5.16k
                    rightParamsMap[kv.key] = kv.value;
9571
5.16k
                }
9572
1.33k
                if (leftParamsSet == rightParamsSet) {
9573
327
                    bool doErase = true;
9574
327
                    try {
9575
710
                        for (const auto &param : leftParamsSet) {
9576
710
                            if (param == "convention" || param == "t_epoch" ||
9577
566
                                param == "t_obs") {
9578
144
                                if (leftParamsMap[param] !=
9579
144
                                    rightParamsMap[param]) {
9580
107
                                    doErase = false;
9581
107
                                    break;
9582
107
                                }
9583
566
                            } else if (c_locale_stod(leftParamsMap[param]) !=
9584
566
                                       -c_locale_stod(rightParamsMap[param])) {
9585
179
                                doErase = false;
9586
179
                                break;
9587
179
                            }
9588
710
                        }
9589
327
                    } catch (const std::invalid_argument &) {
9590
29
                        break;
9591
29
                    }
9592
298
                    if (doErase) {
9593
12
                        deletePrevAndCurIter();
9594
12
                        continue;
9595
12
                    }
9596
298
                }
9597
1.33k
            }
9598
9599
            // The following should be optimized as a no-op
9600
            // +step +proj=helmert +x=25 +y=-141 +z=-78.5 +rx=0 +ry=-0.35
9601
            // +rz=-0.736 +s=0 +convention=coordinate_frame
9602
            // +step +inv +proj=helmert +x=25 +y=-141 +z=-78.5 +rx=0 +ry=0.35
9603
            // +rz=0.736 +s=0 +convention=position_vector
9604
2.75M
            if (curStep.name == "helmert" && prevStep.name == "helmert" &&
9605
4.41k
                ((curStep.inverted && !prevStep.inverted) ||
9606
3.52k
                 (!curStep.inverted && prevStep.inverted)) &&
9607
1.59k
                curStepParamCount == prevStepParamCount) {
9608
869
                std::set<std::string> leftParamsSet;
9609
869
                std::set<std::string> rightParamsSet;
9610
869
                std::map<std::string, std::string> leftParamsMap;
9611
869
                std::map<std::string, std::string> rightParamsMap;
9612
9.91k
                for (const auto &kv : prevStep.paramValues) {
9613
9.91k
                    leftParamsSet.insert(kv.key);
9614
9.91k
                    leftParamsMap[kv.key] = kv.value;
9615
9.91k
                }
9616
9.91k
                for (const auto &kv : curStep.paramValues) {
9617
9.91k
                    rightParamsSet.insert(kv.key);
9618
9.91k
                    rightParamsMap[kv.key] = kv.value;
9619
9.91k
                }
9620
869
                if (leftParamsSet == rightParamsSet) {
9621
645
                    bool doErase = true;
9622
645
                    try {
9623
3.11k
                        for (const auto &param : leftParamsSet) {
9624
3.11k
                            if (param == "convention") {
9625
                                // Convention must be different
9626
334
                                if (leftParamsMap[param] ==
9627
334
                                    rightParamsMap[param]) {
9628
310
                                    doErase = false;
9629
310
                                    break;
9630
310
                                }
9631
2.77k
                            } else if (param == "rx" || param == "ry" ||
9632
2.55k
                                       param == "rz" || param == "drx" ||
9633
2.55k
                                       param == "dry" || param == "drz") {
9634
                                // Rotational parameters should have opposite
9635
                                // value
9636
224
                                if (c_locale_stod(leftParamsMap[param]) !=
9637
224
                                    -c_locale_stod(rightParamsMap[param])) {
9638
99
                                    doErase = false;
9639
99
                                    break;
9640
99
                                }
9641
2.55k
                            } else {
9642
                                // Non rotational parameters should have the
9643
                                // same value
9644
2.55k
                                if (leftParamsMap[param] !=
9645
2.55k
                                    rightParamsMap[param]) {
9646
48
                                    doErase = false;
9647
48
                                    break;
9648
48
                                }
9649
2.55k
                            }
9650
3.11k
                        }
9651
645
                    } catch (const std::invalid_argument &) {
9652
77
                        break;
9653
77
                    }
9654
568
                    if (doErase) {
9655
111
                        deletePrevAndCurIter();
9656
111
                        continue;
9657
111
                    }
9658
568
                }
9659
869
            }
9660
9661
            // Optimize patterns like Krovak (South West) to Krovak East North
9662
            // (also applies to Modified Krovak)
9663
            //   +step +inv +proj=krovak +axis=swu +lat_0=49.5
9664
            //   +lon_0=24.8333333333333
9665
            //     +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel
9666
            //   +step +proj=krovak +lat_0=49.5 +lon_0=24.8333333333333
9667
            //     +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel
9668
            // as:
9669
            //   +step +proj=axisswap +order=-2,-1
9670
            // Also applies for the symmetrical case where +axis=swu is on the
9671
            // second step.
9672
2.75M
            if (curStep.inverted != prevStep.inverted &&
9673
1.16M
                curStep.name == prevStep.name &&
9674
81.8k
                ((curStepParamCount + 1 == prevStepParamCount &&
9675
1.01k
                  prevStep.paramValues[0].equals("axis", "swu")) ||
9676
81.6k
                 (prevStepParamCount + 1 == curStepParamCount &&
9677
1.51k
                  curStep.paramValues[0].equals("axis", "swu")))) {
9678
395
                const auto &swStep = (curStepParamCount < prevStepParamCount)
9679
395
                                         ? prevStep
9680
395
                                         : curStep;
9681
395
                const auto &enStep = (curStepParamCount < prevStepParamCount)
9682
395
                                         ? curStep
9683
395
                                         : prevStep;
9684
                // Check if all remaining parameters (except leading axis=swu
9685
                // in swStep) are identical.
9686
395
                bool allSame = true;
9687
395
                for (size_t j = 0;
9688
435
                     j < std::min(curStepParamCount, prevStepParamCount); j++) {
9689
202
                    if (enStep.paramValues[j] != swStep.paramValues[j + 1]) {
9690
162
                        allSame = false;
9691
162
                        break;
9692
162
                    }
9693
202
                }
9694
395
                if (allSame) {
9695
233
                    iterCur->inverted = false;
9696
233
                    iterCur->name = "axisswap";
9697
233
                    iterCur->paramValues.clear();
9698
233
                    iterCur->paramValues.emplace_back(
9699
233
                        Step::KeyValue("order", "-2,-1"));
9700
9701
233
                    deletePrevIter();
9702
233
                    continue;
9703
233
                }
9704
395
            }
9705
9706
            // detect a step and its inverse
9707
2.75M
            if (curStep.inverted != prevStep.inverted &&
9708
1.16M
                curStep.name == prevStep.name &&
9709
81.5k
                curStepParamCount == prevStepParamCount) {
9710
73.2k
                bool allSame = true;
9711
133k
                for (size_t j = 0; j < curStepParamCount; j++) {
9712
99.7k
                    if (curStep.paramValues[j] != prevStep.paramValues[j]) {
9713
39.7k
                        allSame = false;
9714
39.7k
                        break;
9715
39.7k
                    }
9716
99.7k
                }
9717
73.2k
                if (allSame) {
9718
33.5k
                    deletePrevAndCurIter();
9719
33.5k
                    continue;
9720
33.5k
                }
9721
73.2k
            }
9722
9723
2.71M
            ++iterCur;
9724
2.71M
        }
9725
255k
    }
9726
9727
255k
    {
9728
255k
        auto iterCur = steps.begin();
9729
255k
        if (iterCur != steps.end()) {
9730
246k
            ++iterCur;
9731
246k
        }
9732
1.63M
        while (iterCur != steps.end()) {
9733
9734
1.38M
            assert(iterCur != steps.begin());
9735
1.38M
            auto iterPrev = std::prev(iterCur);
9736
1.38M
            auto &prevStep = *iterPrev;
9737
1.38M
            auto &curStep = *iterCur;
9738
9739
1.38M
            const auto curStepParamCount = curStep.paramValues.size();
9740
1.38M
            const auto prevStepParamCount = prevStep.paramValues.size();
9741
9742
            // +step +proj=hgridshift +grids=grid_A
9743
            // +step +proj=vgridshift [...] <== curStep
9744
            // +step +inv +proj=hgridshift +grids=grid_A
9745
            // ==>
9746
            // +step +proj=push +v_1 +v_2
9747
            // +step +proj=hgridshift +grids=grid_A +omit_inv
9748
            // +step +proj=vgridshift [...]
9749
            // +step +inv +proj=hgridshift +grids=grid_A +omit_fwd
9750
            // +step +proj=pop +v_1 +v_2
9751
1.38M
            if (std::next(iterCur) != steps.end() &&
9752
1.16M
                prevStep.name == "hgridshift" && prevStepParamCount == 1 &&
9753
9.13k
                curStep.name == "vgridshift") {
9754
3.11k
                auto iterNext = std::next(iterCur);
9755
3.11k
                auto &nextStep = *iterNext;
9756
3.11k
                if (nextStep.name == "hgridshift" &&
9757
688
                    nextStep.inverted != prevStep.inverted &&
9758
637
                    nextStep.paramValues.size() == 1 &&
9759
620
                    prevStep.paramValues[0] == nextStep.paramValues[0]) {
9760
452
                    Step pushStep;
9761
452
                    pushStep.name = "push";
9762
452
                    pushStep.paramValues.emplace_back("v_1");
9763
452
                    pushStep.paramValues.emplace_back("v_2");
9764
452
                    steps.insert(iterPrev, pushStep);
9765
9766
452
                    prevStep.paramValues.emplace_back("omit_inv");
9767
9768
452
                    nextStep.paramValues.emplace_back("omit_fwd");
9769
9770
452
                    Step popStep;
9771
452
                    popStep.name = "pop";
9772
452
                    popStep.paramValues.emplace_back("v_1");
9773
452
                    popStep.paramValues.emplace_back("v_2");
9774
452
                    steps.insert(std::next(iterNext), popStep);
9775
9776
452
                    continue;
9777
452
                }
9778
3.11k
            }
9779
9780
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9781
            // +step +proj=axisswap +order=2,1
9782
            // +step +proj=push +v_1 +v_2
9783
            // +step +proj=axisswap +order=2,1
9784
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9785
            // +step +proj=vgridshift ...
9786
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9787
            // +step +proj=axisswap +order=2,1
9788
            // +step +proj=pop +v_1 +v_2
9789
            // ==>
9790
            // +step +proj=vgridshift ...
9791
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9792
            // +step +proj=axisswap +order=2,1
9793
1.38M
            if (prevStep.name == "unitconvert" && prevStepParamCount == 2 &&
9794
187k
                prevStep.paramValues[0].equals("xy_in", "rad") &&
9795
48.1k
                prevStep.paramValues[1].equals("xy_out", "deg") &&
9796
48.1k
                curStep.name == "axisswap" && curStepParamCount == 1 &&
9797
34.3k
                curStep.paramValues[0].equals("order", "2,1")) {
9798
25.6k
                auto iterNext = std::next(iterCur);
9799
25.6k
                bool ok = false;
9800
25.6k
                if (iterNext != steps.end()) {
9801
2.10k
                    auto &nextStep = *iterNext;
9802
2.10k
                    if (nextStep.name == "push" &&
9803
211
                        nextStep.paramValues.size() == 2 &&
9804
0
                        nextStep.paramValues[0].keyEquals("v_1") &&
9805
0
                        nextStep.paramValues[1].keyEquals("v_2")) {
9806
0
                        ok = true;
9807
0
                        iterNext = std::next(iterNext);
9808
0
                    }
9809
2.10k
                }
9810
25.6k
                ok &= iterNext != steps.end();
9811
25.6k
                if (ok) {
9812
0
                    ok = false;
9813
0
                    auto &nextStep = *iterNext;
9814
0
                    if (nextStep.name == "axisswap" &&
9815
0
                        nextStep.paramValues.size() == 1 &&
9816
0
                        nextStep.paramValues[0].equals("order", "2,1")) {
9817
0
                        ok = true;
9818
0
                        iterNext = std::next(iterNext);
9819
0
                    }
9820
0
                }
9821
25.6k
                ok &= iterNext != steps.end();
9822
25.6k
                if (ok) {
9823
0
                    ok = false;
9824
0
                    auto &nextStep = *iterNext;
9825
0
                    if (nextStep.name == "unitconvert" &&
9826
0
                        nextStep.paramValues.size() == 2 &&
9827
0
                        nextStep.paramValues[0].equals("xy_in", "deg") &&
9828
0
                        nextStep.paramValues[1].equals("xy_out", "rad")) {
9829
0
                        ok = true;
9830
0
                        iterNext = std::next(iterNext);
9831
0
                    }
9832
0
                }
9833
25.6k
                auto iterVgridshift = iterNext;
9834
25.6k
                ok &= iterNext != steps.end();
9835
25.6k
                if (ok) {
9836
0
                    ok = false;
9837
0
                    auto &nextStep = *iterNext;
9838
0
                    if (nextStep.name == "vgridshift") {
9839
0
                        ok = true;
9840
0
                        iterNext = std::next(iterNext);
9841
0
                    }
9842
0
                }
9843
25.6k
                ok &= iterNext != steps.end();
9844
25.6k
                if (ok) {
9845
0
                    ok = false;
9846
0
                    auto &nextStep = *iterNext;
9847
0
                    if (nextStep.name == "unitconvert" &&
9848
0
                        nextStep.paramValues.size() == 2 &&
9849
0
                        nextStep.paramValues[0].equals("xy_in", "rad") &&
9850
0
                        nextStep.paramValues[1].equals("xy_out", "deg")) {
9851
0
                        ok = true;
9852
0
                        iterNext = std::next(iterNext);
9853
0
                    }
9854
0
                }
9855
25.6k
                ok &= iterNext != steps.end();
9856
25.6k
                if (ok) {
9857
0
                    ok = false;
9858
0
                    auto &nextStep = *iterNext;
9859
0
                    if (nextStep.name == "axisswap" &&
9860
0
                        nextStep.paramValues.size() == 1 &&
9861
0
                        nextStep.paramValues[0].equals("order", "2,1")) {
9862
0
                        ok = true;
9863
0
                        iterNext = std::next(iterNext);
9864
0
                    }
9865
0
                }
9866
25.6k
                ok &= iterNext != steps.end();
9867
25.6k
                if (ok) {
9868
0
                    ok = false;
9869
0
                    auto &nextStep = *iterNext;
9870
0
                    if (nextStep.name == "pop" &&
9871
0
                        nextStep.paramValues.size() == 2 &&
9872
0
                        nextStep.paramValues[0].keyEquals("v_1") &&
9873
0
                        nextStep.paramValues[1].keyEquals("v_2")) {
9874
0
                        ok = true;
9875
                        // iterNext = std::next(iterNext);
9876
0
                    }
9877
0
                }
9878
25.6k
                if (ok) {
9879
0
                    steps.erase(iterPrev, iterVgridshift);
9880
0
                    steps.erase(iterNext, std::next(iterNext));
9881
0
                    iterPrev = std::prev(iterVgridshift);
9882
0
                    iterCur = iterVgridshift;
9883
0
                    continue;
9884
0
                }
9885
25.6k
            }
9886
9887
            // +step +proj=axisswap +order=2,1
9888
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9889
            // +step +proj=vgridshift ...
9890
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9891
            // +step +proj=axisswap +order=2,1
9892
            // +step +proj=push +v_1 +v_2
9893
            // +step +proj=axisswap +order=2,1
9894
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9895
            // ==>
9896
            // +step +proj=push +v_1 +v_2
9897
            // +step +proj=axisswap +order=2,1
9898
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9899
            // +step +proj=vgridshift ...
9900
9901
1.38M
            if (prevStep.name == "axisswap" && prevStepParamCount == 1 &&
9902
56.9k
                prevStep.paramValues[0].equals("order", "2,1") &&
9903
40.8k
                curStep.name == "unitconvert" && curStepParamCount == 2 &&
9904
36.0k
                !curStep.inverted &&
9905
36.0k
                curStep.paramValues[0].equals("xy_in", "deg") &&
9906
35.2k
                curStep.paramValues[1].equals("xy_out", "rad")) {
9907
35.2k
                auto iterNext = std::next(iterCur);
9908
35.2k
                bool ok = false;
9909
35.2k
                auto iterVgridshift = iterNext;
9910
35.2k
                if (iterNext != steps.end()) {
9911
35.2k
                    auto &nextStep = *iterNext;
9912
35.2k
                    if (nextStep.name == "vgridshift") {
9913
11.9k
                        ok = true;
9914
11.9k
                        iterNext = std::next(iterNext);
9915
11.9k
                    }
9916
35.2k
                }
9917
35.2k
                ok &= iterNext != steps.end();
9918
35.2k
                if (ok) {
9919
11.9k
                    ok = false;
9920
11.9k
                    auto &nextStep = *iterNext;
9921
11.9k
                    if (nextStep.name == "unitconvert" && !nextStep.inverted &&
9922
1.25k
                        nextStep.paramValues.size() == 2 &&
9923
1.21k
                        nextStep.paramValues[0].equals("xy_in", "rad") &&
9924
1.05k
                        nextStep.paramValues[1].equals("xy_out", "deg")) {
9925
1.05k
                        ok = true;
9926
1.05k
                        iterNext = std::next(iterNext);
9927
1.05k
                    }
9928
11.9k
                }
9929
35.2k
                ok &= iterNext != steps.end();
9930
35.2k
                if (ok) {
9931
192
                    ok = false;
9932
192
                    auto &nextStep = *iterNext;
9933
192
                    if (nextStep.name == "axisswap" &&
9934
179
                        nextStep.paramValues.size() == 1 &&
9935
179
                        nextStep.paramValues[0].equals("order", "2,1")) {
9936
147
                        ok = true;
9937
147
                        iterNext = std::next(iterNext);
9938
147
                    }
9939
192
                }
9940
35.2k
                auto iterPush = iterNext;
9941
35.2k
                ok &= iterNext != steps.end();
9942
35.2k
                if (ok) {
9943
95
                    ok = false;
9944
95
                    auto &nextStep = *iterNext;
9945
95
                    if (nextStep.name == "push" &&
9946
3
                        nextStep.paramValues.size() == 2 &&
9947
0
                        nextStep.paramValues[0].keyEquals("v_1") &&
9948
0
                        nextStep.paramValues[1].keyEquals("v_2")) {
9949
0
                        ok = true;
9950
0
                        iterNext = std::next(iterNext);
9951
0
                    }
9952
95
                }
9953
35.2k
                ok &= iterNext != steps.end();
9954
35.2k
                if (ok) {
9955
0
                    ok = false;
9956
0
                    auto &nextStep = *iterNext;
9957
0
                    if (nextStep.name == "axisswap" &&
9958
0
                        nextStep.paramValues.size() == 1 &&
9959
0
                        nextStep.paramValues[0].equals("order", "2,1")) {
9960
0
                        ok = true;
9961
0
                        iterNext = std::next(iterNext);
9962
0
                    }
9963
0
                }
9964
35.2k
                ok &= iterNext != steps.end();
9965
35.2k
                if (ok) {
9966
0
                    ok = false;
9967
0
                    auto &nextStep = *iterNext;
9968
0
                    if (nextStep.name == "unitconvert" &&
9969
0
                        nextStep.paramValues.size() == 2 &&
9970
0
                        !nextStep.inverted &&
9971
0
                        nextStep.paramValues[0].equals("xy_in", "deg") &&
9972
0
                        nextStep.paramValues[1].equals("xy_out", "rad")) {
9973
0
                        ok = true;
9974
                        // iterNext = std::next(iterNext);
9975
0
                    }
9976
0
                }
9977
9978
35.2k
                if (ok) {
9979
0
                    Step stepVgridshift(*iterVgridshift);
9980
0
                    steps.erase(iterPrev, iterPush);
9981
0
                    steps.insert(std::next(iterNext),
9982
0
                                 std::move(stepVgridshift));
9983
0
                    iterPrev = iterPush;
9984
0
                    iterCur = std::next(iterPush);
9985
0
                    continue;
9986
0
                }
9987
35.2k
            }
9988
9989
1.38M
            ++iterCur;
9990
1.38M
        }
9991
255k
    }
9992
9993
255k
    {
9994
255k
        auto iterCur = steps.begin();
9995
255k
        if (iterCur != steps.end()) {
9996
246k
            ++iterCur;
9997
246k
        }
9998
1.63M
        while (iterCur != steps.end()) {
9999
10000
1.38M
            assert(iterCur != steps.begin());
10001
1.38M
            auto iterPrev = std::prev(iterCur);
10002
1.38M
            auto &prevStep = *iterPrev;
10003
1.38M
            auto &curStep = *iterCur;
10004
10005
1.38M
            const auto curStepParamCount = curStep.paramValues.size();
10006
1.38M
            const auto prevStepParamCount = prevStep.paramValues.size();
10007
10008
1.38M
            const auto deletePrevAndCurIter = [&steps, &iterPrev, &iterCur]() {
10009
247
                iterCur = steps.erase(iterPrev, std::next(iterCur));
10010
247
                if (iterCur != steps.begin())
10011
247
                    iterCur = std::prev(iterCur);
10012
247
                if (iterCur == steps.begin() && iterCur != steps.end())
10013
63
                    ++iterCur;
10014
247
            };
10015
10016
            // axisswap order=2,1 followed by itself is a no-op
10017
1.38M
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
10018
1.10k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
10019
547
                curStep.paramValues[0].equals("order", "2,1") &&
10020
56
                prevStep.paramValues[0].equals("order", "2,1")) {
10021
45
                deletePrevAndCurIter();
10022
45
                continue;
10023
45
            }
10024
10025
            // detect a step and its inverse
10026
1.38M
            if (curStep.inverted != prevStep.inverted &&
10027
610k
                curStep.name == prevStep.name &&
10028
26.3k
                curStepParamCount == prevStepParamCount) {
10029
19.6k
                bool allSame = true;
10030
23.0k
                for (size_t j = 0; j < curStepParamCount; j++) {
10031
22.8k
                    if (curStep.paramValues[j] != prevStep.paramValues[j]) {
10032
19.4k
                        allSame = false;
10033
19.4k
                        break;
10034
19.4k
                    }
10035
22.8k
                }
10036
19.6k
                if (allSame) {
10037
202
                    deletePrevAndCurIter();
10038
202
                    continue;
10039
202
                }
10040
19.6k
            }
10041
10042
1.38M
            ++iterCur;
10043
1.38M
        }
10044
255k
    }
10045
10046
255k
    if (steps.size() > 1 ||
10047
38.4k
        (steps.size() == 1 &&
10048
29.5k
         (steps.front().inverted || steps.front().hasKey("omit_inv") ||
10049
27.4k
          steps.front().hasKey("omit_fwd") ||
10050
219k
          !d->globalParamValues_.empty()))) {
10051
219k
        d->appendToResult("+proj=pipeline");
10052
10053
684k
        for (const auto &paramValue : d->globalParamValues_) {
10054
684k
            d->appendToResult("+");
10055
684k
            d->result_ += paramValue.key;
10056
684k
            if (!paramValue.value.empty()) {
10057
381k
                d->result_ += '=';
10058
381k
                d->result_ +=
10059
381k
                    pj_double_quote_string_param_if_needed(paramValue.value);
10060
381k
            }
10061
684k
        }
10062
10063
219k
        if (d->multiLine_) {
10064
0
            d->indentLevel_++;
10065
0
        }
10066
219k
    }
10067
10068
1.62M
    for (const auto &step : steps) {
10069
1.62M
        std::string curLine;
10070
1.62M
        if (!d->result_.empty()) {
10071
1.60M
            if (d->multiLine_) {
10072
0
                curLine = std::string(static_cast<size_t>(d->indentLevel_) *
10073
0
                                          d->indentWidth_,
10074
0
                                      ' ');
10075
0
                curLine += "+step";
10076
1.60M
            } else {
10077
1.60M
                curLine = " +step";
10078
1.60M
            }
10079
1.60M
        }
10080
1.62M
        if (step.inverted) {
10081
488k
            curLine += " +inv";
10082
488k
        }
10083
1.62M
        if (!step.name.empty()) {
10084
1.61M
            if (!curLine.empty())
10085
1.58M
                curLine += ' ';
10086
1.61M
            curLine += step.isInit ? "+init=" : "+proj=";
10087
1.61M
            curLine += step.name;
10088
1.61M
        }
10089
4.88M
        for (const auto &paramValue : step.paramValues) {
10090
4.88M
            std::string newKV = "+";
10091
4.88M
            newKV += paramValue.key;
10092
4.88M
            if (!paramValue.value.empty()) {
10093
3.09M
                newKV += '=';
10094
3.09M
                newKV +=
10095
3.09M
                    pj_double_quote_string_param_if_needed(paramValue.value);
10096
3.09M
            }
10097
4.88M
            if (d->maxLineLength_ > 0 && d->multiLine_ &&
10098
0
                curLine.size() + newKV.size() >
10099
0
                    static_cast<size_t>(d->maxLineLength_)) {
10100
0
                if (!d->result_.empty())
10101
0
                    d->result_ += '\n';
10102
0
                d->result_ += curLine;
10103
0
                curLine = std::string(static_cast<size_t>(d->indentLevel_) *
10104
0
                                              d->indentWidth_ +
10105
0
                                          strlen("+step "),
10106
0
                                      ' ');
10107
4.88M
            } else {
10108
4.88M
                if (!curLine.empty())
10109
4.88M
                    curLine += ' ';
10110
4.88M
            }
10111
4.88M
            curLine += newKV;
10112
4.88M
        }
10113
1.62M
        if (d->multiLine_ && !d->result_.empty())
10114
0
            d->result_ += '\n';
10115
1.62M
        d->result_ += curLine;
10116
1.62M
    }
10117
10118
255k
    if (d->result_.empty()) {
10119
8.93k
        d->appendToResult("+proj=noop");
10120
8.93k
    }
10121
10122
255k
    return d->result_;
10123
255k
}
10124
10125
// ---------------------------------------------------------------------------
10126
10127
//! @cond Doxygen_Suppress
10128
10129
578k
PROJStringFormatter::Convention PROJStringFormatter::convention() const {
10130
578k
    return d->convention_;
10131
578k
}
10132
10133
// ---------------------------------------------------------------------------
10134
10135
// Return the number of steps in the pipeline.
10136
// Note: this value will change after calling toString() that will run
10137
// optimizations.
10138
34.6k
size_t PROJStringFormatter::getStepCount() const { return d->steps_.size(); }
10139
10140
// ---------------------------------------------------------------------------
10141
10142
11.1k
bool PROJStringFormatter::getUseApproxTMerc() const {
10143
11.1k
    return d->useApproxTMerc_;
10144
11.1k
}
10145
10146
// ---------------------------------------------------------------------------
10147
10148
380k
void PROJStringFormatter::setCoordinateOperationOptimizations(bool enable) {
10149
380k
    d->coordOperationOptimizations_ = enable;
10150
380k
}
10151
10152
// ---------------------------------------------------------------------------
10153
10154
912k
void PROJStringFormatter::Private::appendToResult(const char *str) {
10155
912k
    if (!result_.empty()) {
10156
684k
        result_ += ' ';
10157
684k
    }
10158
912k
    result_ += str;
10159
912k
}
10160
10161
// ---------------------------------------------------------------------------
10162
10163
static void
10164
PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps,
10165
                       std::vector<Step::KeyValue> &globalParamValues,
10166
305k
                       std::string &title) {
10167
305k
    std::vector<std::string> tokens;
10168
10169
305k
    bool hasProj = false;
10170
305k
    bool hasInit = false;
10171
305k
    bool hasPipeline = false;
10172
10173
305k
    std::string projStringModified(projString);
10174
10175
    // Special case for "+title=several words +foo=bar"
10176
305k
    if (starts_with(projStringModified, "+title=") &&
10177
188
        projStringModified.size() > 7 && projStringModified[7] != '"') {
10178
180
        const auto plusPos = projStringModified.find(" +", 1);
10179
180
        const auto spacePos = projStringModified.find(' ');
10180
180
        if (plusPos != std::string::npos && spacePos != std::string::npos &&
10181
139
            spacePos < plusPos) {
10182
100
            std::string tmp("+title=");
10183
100
            tmp += pj_double_quote_string_param_if_needed(
10184
100
                projStringModified.substr(7, plusPos - 7));
10185
100
            tmp += projStringModified.substr(plusPos);
10186
100
            projStringModified = std::move(tmp);
10187
100
        }
10188
180
    }
10189
10190
305k
    size_t argc = pj_trim_argc(&projStringModified[0]);
10191
305k
    char **argv = pj_trim_argv(argc, &projStringModified[0]);
10192
9.88M
    for (size_t i = 0; i < argc; i++) {
10193
9.57M
        std::string token(argv[i]);
10194
9.57M
        if (!hasPipeline && token == "proj=pipeline") {
10195
252k
            hasPipeline = true;
10196
9.32M
        } else if (!hasProj && starts_with(token, "proj=")) {
10197
297k
            hasProj = true;
10198
9.02M
        } else if (!hasInit && starts_with(token, "init=")) {
10199
6.43k
            hasInit = true;
10200
6.43k
        }
10201
9.57M
        tokens.emplace_back(token);
10202
9.57M
    }
10203
305k
    free(argv);
10204
10205
305k
    if (!hasPipeline) {
10206
53.0k
        if (hasProj || hasInit) {
10207
46.1k
            steps.push_back(Step());
10208
46.1k
        }
10209
10210
608k
        for (auto &word : tokens) {
10211
608k
            if (starts_with(word, "proj=") && !hasInit &&
10212
65.0k
                steps.back().name.empty()) {
10213
44.7k
                assert(hasProj);
10214
44.7k
                auto stepName = word.substr(strlen("proj="));
10215
44.7k
                steps.back().name = std::move(stepName);
10216
563k
            } else if (starts_with(word, "init=")) {
10217
1.74k
                assert(hasInit);
10218
1.74k
                auto initName = word.substr(strlen("init="));
10219
1.74k
                steps.back().name = std::move(initName);
10220
1.74k
                steps.back().isInit = true;
10221
561k
            } else if (word == "inv") {
10222
2.96k
                if (!steps.empty()) {
10223
2.96k
                    steps.back().inverted = true;
10224
2.96k
                }
10225
559k
            } else if (starts_with(word, "title=")) {
10226
1.22k
                title = word.substr(strlen("title="));
10227
557k
            } else if (word != "step") {
10228
549k
                const auto pos = word.find('=');
10229
549k
                const auto key = word.substr(0, pos);
10230
10231
549k
                Step::KeyValue pair(
10232
549k
                    (pos != std::string::npos)
10233
549k
                        ? Step::KeyValue(key, word.substr(pos + 1))
10234
549k
                        : Step::KeyValue(key));
10235
549k
                if (steps.empty()) {
10236
382
                    globalParamValues.push_back(std::move(pair));
10237
549k
                } else {
10238
549k
                    steps.back().paramValues.push_back(std::move(pair));
10239
549k
                }
10240
549k
            }
10241
608k
        }
10242
53.0k
        return;
10243
53.0k
    }
10244
10245
252k
    bool inPipeline = false;
10246
252k
    bool invGlobal = false;
10247
8.96M
    for (auto &word : tokens) {
10248
8.96M
        if (word == "proj=pipeline") {
10249
252k
            if (inPipeline) {
10250
41
                throw ParsingException("nested pipeline not supported");
10251
41
            }
10252
251k
            inPipeline = true;
10253
8.71M
        } else if (word == "step") {
10254
1.26M
            if (!inPipeline) {
10255
10
                throw ParsingException("+step found outside pipeline");
10256
10
            }
10257
1.26M
            steps.push_back(Step());
10258
7.44M
        } else if (word == "inv") {
10259
372k
            if (steps.empty()) {
10260
3.94k
                invGlobal = true;
10261
368k
            } else {
10262
368k
                steps.back().inverted = true;
10263
368k
            }
10264
7.07M
        } else if (inPipeline && !steps.empty() && starts_with(word, "proj=") &&
10265
1.41M
                   steps.back().name.empty()) {
10266
1.23M
            auto stepName = word.substr(strlen("proj="));
10267
1.23M
            steps.back().name = std::move(stepName);
10268
5.84M
        } else if (inPipeline && !steps.empty() && starts_with(word, "init=") &&
10269
2.96k
                   steps.back().name.empty()) {
10270
620
            auto initName = word.substr(strlen("init="));
10271
620
            steps.back().name = std::move(initName);
10272
620
            steps.back().isInit = true;
10273
5.84M
        } else if (!inPipeline && starts_with(word, "title=")) {
10274
223
            title = word.substr(strlen("title="));
10275
5.84M
        } else {
10276
5.84M
            const auto pos = word.find('=');
10277
5.84M
            auto key = word.substr(0, pos);
10278
5.84M
            Step::KeyValue pair((pos != std::string::npos)
10279
5.84M
                                    ? Step::KeyValue(key, word.substr(pos + 1))
10280
5.84M
                                    : Step::KeyValue(key));
10281
5.84M
            if (steps.empty()) {
10282
1.13M
                globalParamValues.emplace_back(std::move(pair));
10283
4.71M
            } else {
10284
4.71M
                steps.back().paramValues.emplace_back(std::move(pair));
10285
4.71M
            }
10286
5.84M
        }
10287
8.96M
    }
10288
251k
    if (invGlobal && !steps.empty()) {
10289
29.8k
        for (auto &step : steps) {
10290
29.8k
            step.inverted = !step.inverted;
10291
29.8k
        }
10292
3.26k
        std::reverse(steps.begin(), steps.end());
10293
3.26k
    }
10294
251k
}
10295
10296
// ---------------------------------------------------------------------------
10297
10298
void PROJStringFormatter::ingestPROJString(
10299
    const std::string &str) // throw ParsingException
10300
291k
{
10301
291k
    std::vector<Step> steps;
10302
291k
    std::string title;
10303
291k
    PROJStringSyntaxParser(str, steps, d->globalParamValues_, title);
10304
291k
    d->steps_.insert(d->steps_.end(), steps.begin(), steps.end());
10305
291k
}
10306
10307
// ---------------------------------------------------------------------------
10308
10309
13.9k
void PROJStringFormatter::setCRSExport(bool b) { d->crsExport_ = b; }
10310
10311
// ---------------------------------------------------------------------------
10312
10313
1.95M
bool PROJStringFormatter::getCRSExport() const { return d->crsExport_; }
10314
10315
// ---------------------------------------------------------------------------
10316
10317
680k
void PROJStringFormatter::startInversion() {
10318
680k
    PROJStringFormatter::Private::InversionStackElt elt;
10319
680k
    elt.startIter = d->steps_.end();
10320
680k
    if (elt.startIter != d->steps_.begin()) {
10321
457k
        elt.iterValid = true;
10322
457k
        --elt.startIter; // point to the last valid element
10323
457k
    } else {
10324
223k
        elt.iterValid = false;
10325
223k
    }
10326
680k
    elt.currentInversionState =
10327
680k
        !d->inversionStack_.back().currentInversionState;
10328
680k
    d->inversionStack_.push_back(elt);
10329
680k
}
10330
10331
// ---------------------------------------------------------------------------
10332
10333
680k
void PROJStringFormatter::stopInversion() {
10334
680k
    assert(!d->inversionStack_.empty());
10335
680k
    auto startIter = d->inversionStack_.back().startIter;
10336
680k
    if (!d->inversionStack_.back().iterValid) {
10337
223k
        startIter = d->steps_.begin();
10338
456k
    } else {
10339
456k
        ++startIter; // advance after the last valid element we marked above
10340
456k
    }
10341
    // Invert the inversion status of the steps between the start point and
10342
    // the current end of steps
10343
3.03M
    for (auto iter = startIter; iter != d->steps_.end(); ++iter) {
10344
2.35M
        iter->inverted = !iter->inverted;
10345
4.64M
        for (auto &paramValue : iter->paramValues) {
10346
4.64M
            if (paramValue.key == "omit_fwd")
10347
300
                paramValue.key = "omit_inv";
10348
4.64M
            else if (paramValue.key == "omit_inv")
10349
229
                paramValue.key = "omit_fwd";
10350
4.64M
        }
10351
2.35M
    }
10352
    // And reverse the order of steps in that range as well.
10353
680k
    std::reverse(startIter, d->steps_.end());
10354
680k
    d->inversionStack_.pop_back();
10355
680k
}
10356
10357
// ---------------------------------------------------------------------------
10358
10359
0
bool PROJStringFormatter::isInverted() const {
10360
0
    return d->inversionStack_.back().currentInversionState;
10361
0
}
10362
10363
// ---------------------------------------------------------------------------
10364
10365
2.97M
void PROJStringFormatter::Private::addStep() { steps_.emplace_back(Step()); }
10366
10367
// ---------------------------------------------------------------------------
10368
10369
2.93M
void PROJStringFormatter::addStep(const char *stepName) {
10370
2.93M
    d->addStep();
10371
2.93M
    d->steps_.back().name.assign(stepName);
10372
2.93M
}
10373
10374
// ---------------------------------------------------------------------------
10375
10376
43.0k
void PROJStringFormatter::addStep(const std::string &stepName) {
10377
43.0k
    d->addStep();
10378
43.0k
    d->steps_.back().name = stepName;
10379
43.0k
}
10380
10381
// ---------------------------------------------------------------------------
10382
10383
149k
void PROJStringFormatter::setCurrentStepInverted(bool inverted) {
10384
149k
    assert(!d->steps_.empty());
10385
149k
    d->steps_.back().inverted = inverted;
10386
149k
}
10387
10388
// ---------------------------------------------------------------------------
10389
10390
0
bool PROJStringFormatter::hasParam(const char *paramName) const {
10391
0
    if (!d->steps_.empty()) {
10392
0
        for (const auto &paramValue : d->steps_.back().paramValues) {
10393
0
            if (paramValue.keyEquals(paramName)) {
10394
0
                return true;
10395
0
            }
10396
0
        }
10397
0
    }
10398
0
    return false;
10399
0
}
10400
10401
// ---------------------------------------------------------------------------
10402
10403
25.9k
void PROJStringFormatter::addNoDefs(bool b) { d->addNoDefs_ = b; }
10404
10405
// ---------------------------------------------------------------------------
10406
10407
123k
bool PROJStringFormatter::getAddNoDefs() const { return d->addNoDefs_; }
10408
10409
// ---------------------------------------------------------------------------
10410
10411
365k
void PROJStringFormatter::addParam(const std::string &paramName) {
10412
365k
    if (d->steps_.empty()) {
10413
0
        d->addStep();
10414
0
    }
10415
365k
    d->steps_.back().paramValues.push_back(Step::KeyValue(paramName));
10416
365k
}
10417
10418
// ---------------------------------------------------------------------------
10419
10420
16
void PROJStringFormatter::addParam(const char *paramName, int val) {
10421
16
    addParam(std::string(paramName), val);
10422
16
}
10423
10424
16
void PROJStringFormatter::addParam(const std::string &paramName, int val) {
10425
16
    addParam(paramName, internal::toString(val));
10426
16
}
10427
10428
// ---------------------------------------------------------------------------
10429
10430
935k
static std::string formatToString(double val, double precision) {
10431
935k
    if (std::abs(val * 10 - std::round(val * 10)) < precision) {
10432
        // For the purpose of
10433
        // https://www.epsg-registry.org/export.htm?wkt=urn:ogc:def:crs:EPSG::27561
10434
        // Latitude of natural of origin to be properly rounded from 55 grad
10435
        // to
10436
        // 49.5 deg
10437
759k
        val = std::round(val * 10) / 10;
10438
759k
    }
10439
935k
    return normalizeSerializedString(internal::toString(val));
10440
935k
}
10441
10442
// ---------------------------------------------------------------------------
10443
10444
925k
void PROJStringFormatter::addParam(const char *paramName, double val) {
10445
925k
    addParam(std::string(paramName), val);
10446
925k
}
10447
10448
935k
void PROJStringFormatter::addParam(const std::string &paramName, double val) {
10449
935k
    if (paramName == "dt") {
10450
0
        addParam(paramName,
10451
0
                 normalizeSerializedString(internal::toString(val, 7)));
10452
935k
    } else {
10453
935k
        addParam(paramName, formatToString(val, 1e-8));
10454
935k
    }
10455
935k
}
10456
10457
// ---------------------------------------------------------------------------
10458
10459
void PROJStringFormatter::addParam(const char *paramName,
10460
42
                                   const std::vector<double> &vals) {
10461
42
    std::string paramValue;
10462
336
    for (size_t i = 0; i < vals.size(); ++i) {
10463
294
        if (i > 0) {
10464
252
            paramValue += ',';
10465
252
        }
10466
294
        paramValue += formatToString(vals[i], 1e-8);
10467
294
    }
10468
42
    addParam(paramName, paramValue);
10469
42
}
10470
10471
// ---------------------------------------------------------------------------
10472
10473
1.76M
void PROJStringFormatter::addParam(const char *paramName, const char *val) {
10474
1.76M
    addParam(std::string(paramName), val);
10475
1.76M
}
10476
10477
void PROJStringFormatter::addParam(const char *paramName,
10478
1.86M
                                   const std::string &val) {
10479
1.86M
    addParam(std::string(paramName), val);
10480
1.86M
}
10481
10482
void PROJStringFormatter::addParam(const std::string &paramName,
10483
1.76M
                                   const char *val) {
10484
1.76M
    addParam(paramName, std::string(val));
10485
1.76M
}
10486
10487
// ---------------------------------------------------------------------------
10488
10489
void PROJStringFormatter::addParam(const std::string &paramName,
10490
4.62M
                                   const std::string &val) {
10491
4.62M
    if (d->steps_.empty()) {
10492
669
        d->addStep();
10493
669
    }
10494
4.62M
    d->steps_.back().paramValues.push_back(Step::KeyValue(paramName, val));
10495
4.62M
}
10496
10497
// ---------------------------------------------------------------------------
10498
10499
void PROJStringFormatter::setTOWGS84Parameters(
10500
634
    const std::vector<double> &params) {
10501
634
    d->toWGS84Parameters_ = params;
10502
634
}
10503
10504
// ---------------------------------------------------------------------------
10505
10506
467k
const std::vector<double> &PROJStringFormatter::getTOWGS84Parameters() const {
10507
467k
    return d->toWGS84Parameters_;
10508
467k
}
10509
10510
// ---------------------------------------------------------------------------
10511
10512
32.7k
std::set<std::string> PROJStringFormatter::getUsedGridNames() const {
10513
32.7k
    std::set<std::string> res;
10514
203k
    for (const auto &step : d->steps_) {
10515
556k
        for (const auto &param : step.paramValues) {
10516
556k
            if (param.keyEquals("grids") || param.keyEquals("file")) {
10517
20.4k
                const auto gridNames = split(param.value, ",");
10518
47.1k
                for (const auto &gridName : gridNames) {
10519
47.1k
                    res.insert(gridName);
10520
47.1k
                }
10521
20.4k
            }
10522
556k
        }
10523
203k
    }
10524
32.7k
    return res;
10525
32.7k
}
10526
10527
// ---------------------------------------------------------------------------
10528
10529
133k
bool PROJStringFormatter::requiresPerCoordinateInputTime() const {
10530
757k
    for (const auto &step : d->steps_) {
10531
757k
        if (step.name == "set" && !step.inverted) {
10532
46.6k
            for (const auto &param : step.paramValues) {
10533
46.6k
                if (param.keyEquals("v_4")) {
10534
1
                    return false;
10535
1
                }
10536
46.6k
            }
10537
746k
        } else if (step.name == "helmert") {
10538
159k
            for (const auto &param : step.paramValues) {
10539
159k
                if (param.keyEquals("t_epoch")) {
10540
43
                    return true;
10541
43
                }
10542
159k
            }
10543
702k
        } else if (step.name == "deformation") {
10544
12.3k
            for (const auto &param : step.paramValues) {
10545
12.3k
                if (param.keyEquals("t_epoch")) {
10546
692
                    return true;
10547
692
                }
10548
12.3k
            }
10549
698k
        } else if (step.name == "defmodel") {
10550
0
            return true;
10551
0
        }
10552
757k
    }
10553
133k
    return false;
10554
133k
}
10555
10556
// ---------------------------------------------------------------------------
10557
10558
void PROJStringFormatter::setVDatumExtension(const std::string &filename,
10559
1.03k
                                             const std::string &geoidCRSValue) {
10560
1.03k
    d->vDatumExtension_ = filename;
10561
1.03k
    d->geoidCRSValue_ = geoidCRSValue;
10562
1.03k
}
10563
10564
// ---------------------------------------------------------------------------
10565
10566
874
const std::string &PROJStringFormatter::getVDatumExtension() const {
10567
874
    return d->vDatumExtension_;
10568
874
}
10569
10570
// ---------------------------------------------------------------------------
10571
10572
874
const std::string &PROJStringFormatter::getGeoidCRSValue() const {
10573
874
    return d->geoidCRSValue_;
10574
874
}
10575
10576
// ---------------------------------------------------------------------------
10577
10578
412
void PROJStringFormatter::setHDatumExtension(const std::string &filename) {
10579
412
    d->hDatumExtension_ = filename;
10580
412
}
10581
10582
// ---------------------------------------------------------------------------
10583
10584
467k
const std::string &PROJStringFormatter::getHDatumExtension() const {
10585
467k
    return d->hDatumExtension_;
10586
467k
}
10587
10588
// ---------------------------------------------------------------------------
10589
10590
void PROJStringFormatter::setGeogCRSOfCompoundCRS(
10591
387
    const crs::GeographicCRSPtr &crs) {
10592
387
    d->geogCRSOfCompoundCRS_ = crs;
10593
387
}
10594
10595
// ---------------------------------------------------------------------------
10596
10597
const crs::GeographicCRSPtr &
10598
1.20k
PROJStringFormatter::getGeogCRSOfCompoundCRS() const {
10599
1.20k
    return d->geogCRSOfCompoundCRS_;
10600
1.20k
}
10601
10602
// ---------------------------------------------------------------------------
10603
10604
204k
void PROJStringFormatter::setOmitProjLongLatIfPossible(bool omit) {
10605
204k
    assert(d->omitProjLongLatIfPossible_ ^ omit);
10606
204k
    d->omitProjLongLatIfPossible_ = omit;
10607
204k
}
10608
10609
// ---------------------------------------------------------------------------
10610
10611
427k
bool PROJStringFormatter::omitProjLongLatIfPossible() const {
10612
427k
    return d->omitProjLongLatIfPossible_;
10613
427k
}
10614
10615
// ---------------------------------------------------------------------------
10616
10617
268k
void PROJStringFormatter::pushOmitZUnitConversion() {
10618
268k
    d->omitZUnitConversion_.push_back(true);
10619
268k
}
10620
10621
// ---------------------------------------------------------------------------
10622
10623
268k
void PROJStringFormatter::popOmitZUnitConversion() {
10624
268k
    assert(d->omitZUnitConversion_.size() > 1);
10625
268k
    d->omitZUnitConversion_.pop_back();
10626
268k
}
10627
10628
// ---------------------------------------------------------------------------
10629
10630
561k
bool PROJStringFormatter::omitZUnitConversion() const {
10631
561k
    return d->omitZUnitConversion_.back();
10632
561k
}
10633
10634
// ---------------------------------------------------------------------------
10635
10636
102k
void PROJStringFormatter::pushOmitHorizontalConversionInVertTransformation() {
10637
102k
    d->omitHorizontalConversionInVertTransformation_.push_back(true);
10638
102k
}
10639
10640
// ---------------------------------------------------------------------------
10641
10642
102k
void PROJStringFormatter::popOmitHorizontalConversionInVertTransformation() {
10643
102k
    assert(d->omitHorizontalConversionInVertTransformation_.size() > 1);
10644
102k
    d->omitHorizontalConversionInVertTransformation_.pop_back();
10645
102k
}
10646
10647
// ---------------------------------------------------------------------------
10648
10649
238k
bool PROJStringFormatter::omitHorizontalConversionInVertTransformation() const {
10650
238k
    return d->omitHorizontalConversionInVertTransformation_.back();
10651
238k
}
10652
10653
// ---------------------------------------------------------------------------
10654
10655
13.9k
void PROJStringFormatter::setLegacyCRSToCRSContext(bool legacyContext) {
10656
13.9k
    d->legacyCRSToCRSContext_ = legacyContext;
10657
13.9k
}
10658
10659
// ---------------------------------------------------------------------------
10660
10661
359k
bool PROJStringFormatter::getLegacyCRSToCRSContext() const {
10662
359k
    return d->legacyCRSToCRSContext_;
10663
359k
}
10664
10665
// ---------------------------------------------------------------------------
10666
10667
/** Asks for a "normalized" output during toString(), aimed at comparing two
10668
 * strings for equivalence.
10669
 *
10670
 * This consists for now in sorting the +key=value option in lexicographic
10671
 * order.
10672
 */
10673
13.4k
PROJStringFormatter &PROJStringFormatter::setNormalizeOutput() {
10674
13.4k
    d->normalizeOutput_ = true;
10675
13.4k
    return *this;
10676
13.4k
}
10677
10678
// ---------------------------------------------------------------------------
10679
10680
556k
const DatabaseContextPtr &PROJStringFormatter::databaseContext() const {
10681
556k
    return d->dbContext_;
10682
556k
}
10683
10684
//! @endcond
10685
10686
// ---------------------------------------------------------------------------
10687
10688
//! @cond Doxygen_Suppress
10689
10690
struct PROJStringParser::Private {
10691
    DatabaseContextPtr dbContext_{};
10692
    PJ_CONTEXT *ctx_{};
10693
    bool usePROJ4InitRules_ = false;
10694
    std::vector<std::string> warningList_{};
10695
10696
    std::string projString_{};
10697
10698
    std::vector<Step> steps_{};
10699
    std::vector<Step::KeyValue> globalParamValues_{};
10700
    std::string title_{};
10701
10702
    bool ignoreNadgrids_ = false;
10703
10704
    template <class T>
10705
    // cppcheck-suppress functionStatic
10706
89.6k
    bool hasParamValue(Step &step, const T key) {
10707
166k
        for (auto &pair : globalParamValues_) {
10708
166k
            if (ci_equal(pair.key, key)) {
10709
1.50k
                pair.usedByParser = true;
10710
1.50k
                return true;
10711
1.50k
            }
10712
166k
        }
10713
207k
        for (auto &pair : step.paramValues) {
10714
207k
            if (ci_equal(pair.key, key)) {
10715
7.85k
                pair.usedByParser = true;
10716
7.85k
                return true;
10717
7.85k
            }
10718
207k
        }
10719
80.2k
        return false;
10720
88.1k
    }
10721
10722
    template <class T>
10723
    // cppcheck-suppress functionStatic
10724
2.22k
    const std::string &getGlobalParamValue(T key) {
10725
13.8k
        for (auto &pair : globalParamValues_) {
10726
13.8k
            if (ci_equal(pair.key, key)) {
10727
1.36k
                pair.usedByParser = true;
10728
1.36k
                return pair.value;
10729
1.36k
            }
10730
13.8k
        }
10731
861
        return emptyString;
10732
2.22k
    }
10733
10734
    template <class T>
10735
    // cppcheck-suppress functionStatic
10736
433k
    const std::string &getParamValue(Step &step, const T key) {
10737
625k
        for (auto &pair : globalParamValues_) {
10738
625k
            if (ci_equal(pair.key, key)) {
10739
7.44k
                pair.usedByParser = true;
10740
7.44k
                return pair.value;
10741
7.44k
            }
10742
625k
        }
10743
935k
        for (auto &pair : step.paramValues) {
10744
935k
            if (ci_equal(pair.key, key)) {
10745
39.1k
                pair.usedByParser = true;
10746
39.1k
                return pair.value;
10747
39.1k
            }
10748
935k
        }
10749
387k
        return emptyString;
10750
426k
    }
std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const& osgeo::proj::io::PROJStringParser::Private::getParamValue<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >(osgeo::proj::io::Step&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >)
Line
Count
Source
10736
91.4k
    const std::string &getParamValue(Step &step, const T key) {
10737
142k
        for (auto &pair : globalParamValues_) {
10738
142k
            if (ci_equal(pair.key, key)) {
10739
252
                pair.usedByParser = true;
10740
252
                return pair.value;
10741
252
            }
10742
142k
        }
10743
226k
        for (auto &pair : step.paramValues) {
10744
226k
            if (ci_equal(pair.key, key)) {
10745
3.06k
                pair.usedByParser = true;
10746
3.06k
                return pair.value;
10747
3.06k
            }
10748
226k
        }
10749
88.1k
        return emptyString;
10750
91.2k
    }
std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const& osgeo::proj::io::PROJStringParser::Private::getParamValue<char const*>(osgeo::proj::io::Step&, char const*)
Line
Count
Source
10736
342k
    const std::string &getParamValue(Step &step, const T key) {
10737
483k
        for (auto &pair : globalParamValues_) {
10738
483k
            if (ci_equal(pair.key, key)) {
10739
7.19k
                pair.usedByParser = true;
10740
7.19k
                return pair.value;
10741
7.19k
            }
10742
483k
        }
10743
709k
        for (auto &pair : step.paramValues) {
10744
709k
            if (ci_equal(pair.key, key)) {
10745
36.0k
                pair.usedByParser = true;
10746
36.0k
                return pair.value;
10747
36.0k
            }
10748
709k
        }
10749
299k
        return emptyString;
10750
335k
    }
10751
10752
2.09k
    static const std::string &getParamValueK(Step &step) {
10753
4.20k
        for (auto &pair : step.paramValues) {
10754
4.20k
            if (ci_equal(pair.key, "k") || ci_equal(pair.key, "k_0")) {
10755
74
                pair.usedByParser = true;
10756
74
                return pair.value;
10757
74
            }
10758
4.20k
        }
10759
2.02k
        return emptyString;
10760
2.09k
    }
10761
10762
    // cppcheck-suppress functionStatic
10763
18.1k
    bool hasUnusedParameters(const Step &step) const {
10764
18.1k
        if (steps_.size() == 1) {
10765
15.9k
            for (const auto &pair : step.paramValues) {
10766
15.9k
                if (pair.key != "no_defs" && !pair.usedByParser) {
10767
4.82k
                    return true;
10768
4.82k
                }
10769
15.9k
            }
10770
15.2k
        }
10771
13.3k
        return false;
10772
18.1k
    }
10773
10774
    // cppcheck-suppress functionStatic
10775
    std::string guessBodyName(double a);
10776
10777
    PrimeMeridianNNPtr buildPrimeMeridian(Step &step);
10778
    GeodeticReferenceFrameNNPtr buildDatum(Step &step,
10779
                                           const std::string &title);
10780
    GeodeticCRSNNPtr buildGeodeticCRS(int iStep, int iUnitConvert,
10781
                                      int iAxisSwap, bool ignorePROJAxis);
10782
    GeodeticCRSNNPtr buildGeocentricCRS(int iStep, int iUnitConvert);
10783
    CRSNNPtr buildProjectedCRS(int iStep, const GeodeticCRSNNPtr &geogCRS,
10784
                               int iUnitConvert, int iAxisSwap);
10785
    CRSNNPtr buildBoundOrCompoundCRSIfNeeded(int iStep, CRSNNPtr crs);
10786
    UnitOfMeasure buildUnit(Step &step, const std::string &unitsParamName,
10787
                            const std::string &toMeterParamName);
10788
10789
    enum class AxisType { REGULAR, NORTH_POLE, SOUTH_POLE };
10790
10791
    std::vector<CoordinateSystemAxisNNPtr>
10792
    processAxisSwap(Step &step, const UnitOfMeasure &unit, int iAxisSwap,
10793
                    AxisType axisType, bool ignorePROJAxis);
10794
10795
    EllipsoidalCSNNPtr buildEllipsoidalCS(int iStep, int iUnitConvert,
10796
                                          int iAxisSwap, bool ignorePROJAxis);
10797
10798
    SphericalCSNNPtr buildSphericalCS(int iStep, int iUnitConvert,
10799
                                      int iAxisSwap, bool ignorePROJAxis);
10800
};
10801
10802
//! @endcond
10803
10804
// ---------------------------------------------------------------------------
10805
10806
12.9k
PROJStringParser::PROJStringParser() : d(std::make_unique<Private>()) {}
10807
10808
// ---------------------------------------------------------------------------
10809
10810
//! @cond Doxygen_Suppress
10811
12.9k
PROJStringParser::~PROJStringParser() = default;
10812
//! @endcond
10813
10814
// ---------------------------------------------------------------------------
10815
10816
/** \brief Attach a database context, to allow queries in it if needed.
10817
 */
10818
PROJStringParser &
10819
12.8k
PROJStringParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) {
10820
12.8k
    d->dbContext_ = dbContext;
10821
12.8k
    return *this;
10822
12.8k
}
10823
10824
// ---------------------------------------------------------------------------
10825
10826
//! @cond Doxygen_Suppress
10827
12.8k
PROJStringParser &PROJStringParser::attachContext(PJ_CONTEXT *ctx) {
10828
12.8k
    d->ctx_ = ctx;
10829
12.8k
    return *this;
10830
12.8k
}
10831
//! @endcond
10832
10833
// ---------------------------------------------------------------------------
10834
10835
/** \brief Set how init=epsg:XXXX syntax should be interpreted.
10836
 *
10837
 * @param enable When set to true,
10838
 * init=epsg:XXXX syntax will be allowed and will be interpreted according to
10839
 * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
10840
 * order and will expect/output coordinates in radians. ProjectedCRS will have
10841
 * easting, northing axis order (except the ones with Transverse Mercator South
10842
 * Orientated projection).
10843
 */
10844
12.8k
PROJStringParser &PROJStringParser::setUsePROJ4InitRules(bool enable) {
10845
12.8k
    d->usePROJ4InitRules_ = enable;
10846
12.8k
    return *this;
10847
12.8k
}
10848
10849
// ---------------------------------------------------------------------------
10850
10851
/** \brief Return the list of warnings found during parsing.
10852
 */
10853
0
std::vector<std::string> PROJStringParser::warningList() const {
10854
0
    return d->warningList_;
10855
0
}
10856
10857
// ---------------------------------------------------------------------------
10858
10859
//! @cond Doxygen_Suppress
10860
10861
// ---------------------------------------------------------------------------
10862
10863
static const struct LinearUnitDesc {
10864
    const char *projName;
10865
    const char *convToMeter;
10866
    const char *name;
10867
    int epsgCode;
10868
} linearUnitDescs[] = {
10869
    {"mm", "0.001", "millimetre", 1025},
10870
    {"cm", "0.01", "centimetre", 1033},
10871
    {"m", "1.0", "metre", 9001},
10872
    {"meter", "1.0", "metre", 9001}, // alternative
10873
    {"metre", "1.0", "metre", 9001}, // alternative
10874
    {"ft", "0.3048", "foot", 9002},
10875
    {"us-ft", "0.3048006096012192", "US survey foot", 9003},
10876
    {"fath", "1.8288", "fathom", 9014},
10877
    {"kmi", "1852", "nautical mile", 9030},
10878
    {"us-ch", "20.11684023368047", "US survey chain", 9033},
10879
    {"us-mi", "1609.347218694437", "US survey mile", 9035},
10880
    {"km", "1000.0", "kilometre", 9036},
10881
    {"ind-ft", "0.30479841", "Indian foot (1937)", 9081},
10882
    {"ind-yd", "0.91439523", "Indian yard (1937)", 9085},
10883
    {"mi", "1609.344", "Statute mile", 9093},
10884
    {"yd", "0.9144", "yard", 9096},
10885
    {"ch", "20.1168", "chain", 9097},
10886
    {"link", "0.201168", "link", 9098},
10887
    {"dm", "0.1", "decimetre", 0},                       // no EPSG equivalent
10888
    {"in", "0.0254", "inch", 0},                         // no EPSG equivalent
10889
    {"us-in", "0.025400050800101", "US survey inch", 0}, // no EPSG equivalent
10890
    {"us-yd", "0.914401828803658", "US survey yard", 0}, // no EPSG equivalent
10891
    {"ind-ch", "20.11669506", "Indian chain", 0},        // no EPSG equivalent
10892
};
10893
10894
2.54k
static const LinearUnitDesc *getLinearUnits(const std::string &projName) {
10895
13.1k
    for (const auto &desc : linearUnitDescs) {
10896
13.1k
        if (desc.projName == projName)
10897
2.52k
            return &desc;
10898
13.1k
    }
10899
22
    return nullptr;
10900
2.54k
}
10901
10902
243
static const LinearUnitDesc *getLinearUnits(double toMeter) {
10903
5.42k
    for (const auto &desc : linearUnitDescs) {
10904
5.42k
        if (std::fabs(c_locale_stod(desc.convToMeter) - toMeter) <
10905
5.42k
            1e-10 * toMeter) {
10906
8
            return &desc;
10907
8
        }
10908
5.42k
    }
10909
235
    return nullptr;
10910
243
}
10911
10912
// ---------------------------------------------------------------------------
10913
10914
2.52k
static UnitOfMeasure _buildUnit(const LinearUnitDesc *unitsMatch) {
10915
2.52k
    std::string unitsCode;
10916
2.52k
    if (unitsMatch->epsgCode) {
10917
2.51k
        std::ostringstream buffer;
10918
2.51k
        buffer.imbue(std::locale::classic());
10919
2.51k
        buffer << unitsMatch->epsgCode;
10920
2.51k
        unitsCode = buffer.str();
10921
2.51k
    }
10922
2.52k
    return UnitOfMeasure(
10923
2.52k
        unitsMatch->name, c_locale_stod(unitsMatch->convToMeter),
10924
2.52k
        UnitOfMeasure::Type::LINEAR,
10925
2.52k
        unitsMatch->epsgCode ? Identifier::EPSG : std::string(), unitsCode);
10926
2.52k
}
10927
10928
// ---------------------------------------------------------------------------
10929
10930
235
static UnitOfMeasure _buildUnit(double to_meter_value) {
10931
    // TODO: look-up in EPSG catalog
10932
235
    if (to_meter_value == 0) {
10933
1
        throw ParsingException("invalid unit value");
10934
1
    }
10935
234
    return UnitOfMeasure("unknown", to_meter_value,
10936
234
                         UnitOfMeasure::Type::LINEAR);
10937
235
}
10938
10939
// ---------------------------------------------------------------------------
10940
10941
UnitOfMeasure
10942
PROJStringParser::Private::buildUnit(Step &step,
10943
                                     const std::string &unitsParamName,
10944
36.3k
                                     const std::string &toMeterParamName) {
10945
36.3k
    UnitOfMeasure unit = UnitOfMeasure::METRE;
10946
36.3k
    const LinearUnitDesc *unitsMatch = nullptr;
10947
36.3k
    const auto &projUnits = getParamValue(step, unitsParamName);
10948
36.3k
    if (!projUnits.empty()) {
10949
2.53k
        unitsMatch = getLinearUnits(projUnits);
10950
2.53k
        if (unitsMatch == nullptr) {
10951
18
            throw ParsingException("unhandled " + unitsParamName + "=" +
10952
18
                                   projUnits);
10953
18
        }
10954
2.53k
    }
10955
10956
36.3k
    const auto &toMeter = getParamValue(step, toMeterParamName);
10957
36.3k
    if (!toMeter.empty()) {
10958
257
        double to_meter_value;
10959
257
        try {
10960
257
            to_meter_value = c_locale_stod(toMeter);
10961
257
        } catch (const std::invalid_argument &) {
10962
14
            throw ParsingException("invalid value for " + toMeterParamName);
10963
14
        }
10964
243
        unitsMatch = getLinearUnits(to_meter_value);
10965
243
        if (unitsMatch == nullptr) {
10966
235
            unit = _buildUnit(to_meter_value);
10967
235
        }
10968
243
    }
10969
10970
36.3k
    if (unitsMatch) {
10971
2.52k
        unit = _buildUnit(unitsMatch);
10972
2.52k
    }
10973
10974
36.3k
    return unit;
10975
36.3k
}
10976
10977
// ---------------------------------------------------------------------------
10978
10979
static const struct DatumDesc {
10980
    const char *projName;
10981
    const char *gcsName;
10982
    int gcsCode;
10983
    const char *datumName;
10984
    int datumCode;
10985
    const char *ellipsoidName;
10986
    int ellipsoidCode;
10987
    double a;
10988
    double rf;
10989
} datumDescs[] = {
10990
    {"GGRS87", "GGRS87", 4121, "Greek Geodetic Reference System 1987", 6121,
10991
     "GRS 1980", 7019, 6378137, 298.257222101},
10992
    {"potsdam", "DHDN", 4314, "Deutsches Hauptdreiecksnetz", 6314,
10993
     "Bessel 1841", 7004, 6377397.155, 299.1528128},
10994
    {"carthage", "Carthage", 4223, "Carthage", 6223, "Clarke 1880 (IGN)", 7011,
10995
     6378249.2, 293.4660213},
10996
    {"hermannskogel", "MGI", 4312, "Militar-Geographische Institut", 6312,
10997
     "Bessel 1841", 7004, 6377397.155, 299.1528128},
10998
    {"ire65", "TM65", 4299, "TM65", 6299, "Airy Modified 1849", 7002,
10999
     6377340.189, 299.3249646},
11000
    {"nzgd49", "NZGD49", 4272, "New Zealand Geodetic Datum 1949", 6272,
11001
     "International 1924", 7022, 6378388, 297},
11002
    {"OSGB36", "OSGB 1936", 4277, "OSGB 1936", 6277, "Airy 1830", 7001,
11003
     6377563.396, 299.3249646},
11004
};
11005
11006
// ---------------------------------------------------------------------------
11007
11008
30.0k
static bool isGeographicStep(const std::string &name) {
11009
30.0k
    return name == "longlat" || name == "lonlat" || name == "latlong" ||
11010
18.7k
           name == "latlon";
11011
30.0k
}
11012
11013
// ---------------------------------------------------------------------------
11014
11015
9.09k
static bool isGeocentricStep(const std::string &name) {
11016
9.09k
    return name == "geocent" || name == "cart";
11017
9.09k
}
11018
11019
// ---------------------------------------------------------------------------
11020
11021
19.9k
static bool isTopocentricStep(const std::string &name) {
11022
19.9k
    return name == "topocentric";
11023
19.9k
}
11024
11025
// ---------------------------------------------------------------------------
11026
11027
9.92k
static bool isProjectedStep(const std::string &name) {
11028
9.92k
    if (name == "etmerc" || name == "utm" ||
11029
9.84k
        !getMappingsFromPROJName(name).empty()) {
11030
3.16k
        return true;
11031
3.16k
    }
11032
    // IMPROVE ME: have a better way of distinguishing projections from
11033
    // other
11034
    // transformations.
11035
6.75k
    if (name == "pipeline" || name == "geoc" || name == "deformation" ||
11036
6.73k
        name == "helmert" || name == "hgridshift" || name == "molodensky" ||
11037
6.59k
        name == "vgridshift") {
11038
223
        return false;
11039
223
    }
11040
6.53k
    const auto *operations = proj_list_operations();
11041
599k
    for (int i = 0; operations[i].id != nullptr; ++i) {
11042
599k
        if (name == operations[i].id) {
11043
6.09k
            return true;
11044
6.09k
        }
11045
599k
    }
11046
443
    return false;
11047
6.53k
}
11048
11049
// ---------------------------------------------------------------------------
11050
11051
34.1k
static PropertyMap createMapWithUnknownName() {
11052
34.1k
    return PropertyMap().set(common::IdentifiedObject::NAME_KEY, "unknown");
11053
34.1k
}
11054
11055
// ---------------------------------------------------------------------------
11056
11057
28.5k
PrimeMeridianNNPtr PROJStringParser::Private::buildPrimeMeridian(Step &step) {
11058
11059
28.5k
    PrimeMeridianNNPtr pm = PrimeMeridian::GREENWICH;
11060
28.5k
    const auto &pmStr = getParamValue(step, "pm");
11061
28.5k
    if (!pmStr.empty()) {
11062
3.02k
        char *end;
11063
3.02k
        double pmValue = dmstor(pmStr.c_str(), &end) * RAD_TO_DEG;
11064
3.02k
        if (pmValue != HUGE_VAL && *end == '\0') {
11065
2.88k
            pm = PrimeMeridian::create(createMapWithUnknownName(),
11066
2.88k
                                       Angle(pmValue));
11067
2.88k
        } else {
11068
137
            bool found = false;
11069
137
            if (pmStr == "paris") {
11070
37
                found = true;
11071
37
                pm = PrimeMeridian::PARIS;
11072
37
            }
11073
137
            auto proj_prime_meridians = proj_list_prime_meridians();
11074
1.16k
            for (int i = 0; !found && proj_prime_meridians[i].id != nullptr;
11075
1.13k
                 i++) {
11076
1.13k
                if (pmStr == proj_prime_meridians[i].id) {
11077
98
                    found = true;
11078
98
                    std::string name = static_cast<char>(::toupper(pmStr[0])) +
11079
98
                                       pmStr.substr(1);
11080
98
                    pmValue = dmstor(proj_prime_meridians[i].defn, nullptr) *
11081
98
                              RAD_TO_DEG;
11082
98
                    pm = PrimeMeridian::create(
11083
98
                        PropertyMap().set(IdentifiedObject::NAME_KEY, name),
11084
98
                        Angle(pmValue));
11085
98
                    break;
11086
98
                }
11087
1.13k
            }
11088
137
            if (!found) {
11089
2
                throw ParsingException("unknown pm " + pmStr);
11090
2
            }
11091
137
        }
11092
3.02k
    }
11093
28.5k
    return pm;
11094
28.5k
}
11095
11096
// ---------------------------------------------------------------------------
11097
11098
885
std::string PROJStringParser::Private::guessBodyName(double a) {
11099
11100
885
    auto ret = Ellipsoid::guessBodyName(dbContext_, a);
11101
885
    if (ret == NON_EARTH_BODY && dbContext_ == nullptr && ctx_ != nullptr) {
11102
77
        dbContext_ =
11103
77
            ctx_->get_cpp_context()->getDatabaseContext().as_nullable();
11104
77
        if (dbContext_) {
11105
77
            ret = Ellipsoid::guessBodyName(dbContext_, a);
11106
77
        }
11107
77
    }
11108
885
    return ret;
11109
885
}
11110
11111
// ---------------------------------------------------------------------------
11112
11113
GeodeticReferenceFrameNNPtr
11114
18.1k
PROJStringParser::Private::buildDatum(Step &step, const std::string &title) {
11115
11116
18.1k
    std::string ellpsStr = getParamValue(step, "ellps");
11117
18.1k
    const auto &datumStr = getParamValue(step, "datum");
11118
18.1k
    const auto &RStr = getParamValue(step, "R");
11119
18.1k
    const auto &aStr = getParamValue(step, "a");
11120
18.1k
    const auto &bStr = getParamValue(step, "b");
11121
18.1k
    const auto &rfStr = getParamValue(step, "rf");
11122
18.1k
    const auto &fStr = getParamValue(step, "f");
11123
18.1k
    const auto &esStr = getParamValue(step, "es");
11124
18.1k
    const auto &eStr = getParamValue(step, "e");
11125
18.1k
    double a = -1.0;
11126
18.1k
    double b = -1.0;
11127
18.1k
    double rf = -1.0;
11128
18.1k
    const util::optional<std::string> optionalEmptyString{};
11129
18.1k
    const bool numericParamPresent =
11130
18.1k
        !RStr.empty() || !aStr.empty() || !bStr.empty() || !rfStr.empty() ||
11131
17.4k
        !fStr.empty() || !esStr.empty() || !eStr.empty();
11132
11133
18.1k
    if (!numericParamPresent && ellpsStr.empty() && datumStr.empty() &&
11134
14.4k
        (step.name == "krovak" || step.name == "mod_krovak")) {
11135
768
        ellpsStr = "bessel";
11136
768
    }
11137
11138
18.1k
    PrimeMeridianNNPtr pm(buildPrimeMeridian(step));
11139
18.1k
    PropertyMap grfMap;
11140
11141
18.1k
    const auto &nadgrids = getParamValue(step, "nadgrids");
11142
18.1k
    const auto &towgs84 = getParamValue(step, "towgs84");
11143
18.1k
    std::string datumNameSuffix;
11144
18.1k
    if (!nadgrids.empty()) {
11145
1.49k
        datumNameSuffix = " using nadgrids=" + nadgrids;
11146
16.7k
    } else if (!towgs84.empty()) {
11147
1.22k
        datumNameSuffix = " using towgs84=" + towgs84;
11148
1.22k
    }
11149
11150
    // It is arguable that we allow the prime meridian of a datum defined by
11151
    // its name to be overridden, but this is found at least in a regression
11152
    // test
11153
    // of GDAL. So let's keep the ellipsoid part of the datum in that case and
11154
    // use the specified prime meridian.
11155
18.1k
    const auto overridePmIfNeeded =
11156
18.1k
        [&pm, &datumNameSuffix](const GeodeticReferenceFrameNNPtr &grf) {
11157
15.0k
            if (pm->_isEquivalentTo(PrimeMeridian::GREENWICH.get())) {
11158
13.9k
                return grf;
11159
13.9k
            } else {
11160
1.08k
                return GeodeticReferenceFrame::create(
11161
1.08k
                    PropertyMap().set(IdentifiedObject::NAME_KEY,
11162
1.08k
                                      UNKNOWN_BASED_ON +
11163
1.08k
                                          grf->ellipsoid()->nameStr() +
11164
1.08k
                                          " ellipsoid" + datumNameSuffix),
11165
1.08k
                    grf->ellipsoid(), grf->anchorDefinition(), pm);
11166
1.08k
            }
11167
15.0k
        };
11168
11169
    // R take precedence
11170
18.1k
    if (!RStr.empty()) {
11171
132
        double R;
11172
132
        try {
11173
132
            R = c_locale_stod(RStr);
11174
132
        } catch (const std::invalid_argument &) {
11175
24
            throw ParsingException("Invalid R value");
11176
24
        }
11177
108
        auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(),
11178
108
                                                 Length(R), guessBodyName(R));
11179
108
        return GeodeticReferenceFrame::create(
11180
108
            grfMap.set(IdentifiedObject::NAME_KEY,
11181
108
                       title.empty() ? "unknown" + datumNameSuffix : title),
11182
108
            ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
11183
132
    }
11184
11185
18.0k
    if (!datumStr.empty()) {
11186
2.53k
        auto l_datum = [&datumStr, &overridePmIfNeeded, &grfMap,
11187
2.53k
                        &optionalEmptyString, &pm]() {
11188
2.53k
            if (datumStr == "WGS84") {
11189
3
                return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
11190
2.52k
            } else if (datumStr == "NAD83") {
11191
467
                return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6269);
11192
2.06k
            } else if (datumStr == "NAD27") {
11193
920
                return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6267);
11194
1.14k
            } else {
11195
11196
6.21k
                for (const auto &datumDesc : datumDescs) {
11197
6.21k
                    if (datumStr == datumDesc.projName) {
11198
1.13k
                        (void)datumDesc.gcsName; // to please cppcheck
11199
1.13k
                        (void)datumDesc.gcsCode; // to please cppcheck
11200
1.13k
                        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11201
1.13k
                            grfMap
11202
1.13k
                                .set(IdentifiedObject::NAME_KEY,
11203
1.13k
                                     datumDesc.ellipsoidName)
11204
1.13k
                                .set(Identifier::CODESPACE_KEY,
11205
1.13k
                                     Identifier::EPSG)
11206
1.13k
                                .set(Identifier::CODE_KEY,
11207
1.13k
                                     datumDesc.ellipsoidCode),
11208
1.13k
                            Length(datumDesc.a), Scale(datumDesc.rf));
11209
1.13k
                        return GeodeticReferenceFrame::create(
11210
1.13k
                            grfMap
11211
1.13k
                                .set(IdentifiedObject::NAME_KEY,
11212
1.13k
                                     datumDesc.datumName)
11213
1.13k
                                .set(Identifier::CODESPACE_KEY,
11214
1.13k
                                     Identifier::EPSG)
11215
1.13k
                                .set(Identifier::CODE_KEY, datumDesc.datumCode),
11216
1.13k
                            ellipsoid, optionalEmptyString, pm);
11217
1.13k
                    }
11218
6.21k
                }
11219
1.14k
            }
11220
12
            throw ParsingException("unknown datum " + datumStr);
11221
2.53k
        }();
11222
2.53k
        if (!numericParamPresent) {
11223
2.10k
            return l_datum;
11224
2.10k
        }
11225
425
        a = l_datum->ellipsoid()->semiMajorAxis().getSIValue();
11226
425
        rf = l_datum->ellipsoid()->computedInverseFlattening();
11227
425
    }
11228
11229
15.5k
    else if (!ellpsStr.empty()) {
11230
1.48k
        auto l_datum = [&ellpsStr, &title, &grfMap, &optionalEmptyString, &pm,
11231
1.48k
                        &datumNameSuffix]() {
11232
1.48k
            if (ellpsStr == "WGS84") {
11233
25
                return GeodeticReferenceFrame::create(
11234
25
                    grfMap.set(IdentifiedObject::NAME_KEY,
11235
25
                               title.empty() ? std::string(UNKNOWN_BASED_ON)
11236
22
                                                   .append("WGS 84 ellipsoid")
11237
22
                                                   .append(datumNameSuffix)
11238
25
                                             : title),
11239
25
                    Ellipsoid::WGS84, optionalEmptyString, pm);
11240
1.46k
            } else if (ellpsStr == "GRS80") {
11241
32
                return GeodeticReferenceFrame::create(
11242
32
                    grfMap.set(IdentifiedObject::NAME_KEY,
11243
32
                               title.empty() ? std::string(UNKNOWN_BASED_ON)
11244
12
                                                   .append("GRS 1980 ellipsoid")
11245
12
                                                   .append(datumNameSuffix)
11246
32
                                             : title),
11247
32
                    Ellipsoid::GRS1980, optionalEmptyString, pm);
11248
1.43k
            } else {
11249
1.43k
                auto proj_ellps = proj_list_ellps();
11250
20.7k
                for (int i = 0; proj_ellps[i].id != nullptr; i++) {
11251
20.7k
                    if (ellpsStr == proj_ellps[i].id) {
11252
1.41k
                        assert(strncmp(proj_ellps[i].major, "a=", 2) == 0);
11253
1.41k
                        const double a_iter =
11254
1.41k
                            c_locale_stod(proj_ellps[i].major + 2);
11255
1.41k
                        EllipsoidPtr ellipsoid;
11256
1.41k
                        PropertyMap ellpsMap;
11257
1.41k
                        if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) {
11258
53
                            const double b_iter =
11259
53
                                c_locale_stod(proj_ellps[i].ell + 2);
11260
53
                            ellipsoid =
11261
53
                                Ellipsoid::createTwoAxis(
11262
53
                                    ellpsMap.set(IdentifiedObject::NAME_KEY,
11263
53
                                                 proj_ellps[i].name),
11264
53
                                    Length(a_iter), Length(b_iter))
11265
53
                                    .as_nullable();
11266
1.36k
                        } else {
11267
1.36k
                            assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0);
11268
1.36k
                            const double rf_iter =
11269
1.36k
                                c_locale_stod(proj_ellps[i].ell + 3);
11270
1.36k
                            ellipsoid =
11271
1.36k
                                Ellipsoid::createFlattenedSphere(
11272
1.36k
                                    ellpsMap.set(IdentifiedObject::NAME_KEY,
11273
1.36k
                                                 proj_ellps[i].name),
11274
1.36k
                                    Length(a_iter), Scale(rf_iter))
11275
1.36k
                                    .as_nullable();
11276
1.36k
                        }
11277
1.41k
                        return GeodeticReferenceFrame::create(
11278
1.41k
                            grfMap.set(IdentifiedObject::NAME_KEY,
11279
1.41k
                                       title.empty()
11280
1.41k
                                           ? std::string(UNKNOWN_BASED_ON)
11281
1.24k
                                                 .append(proj_ellps[i].name)
11282
1.24k
                                                 .append(" ellipsoid")
11283
1.24k
                                                 .append(datumNameSuffix)
11284
1.41k
                                           : title),
11285
1.41k
                            NN_NO_CHECK(ellipsoid), optionalEmptyString, pm);
11286
1.41k
                    }
11287
20.7k
                }
11288
17
                throw ParsingException("unknown ellipsoid " + ellpsStr);
11289
1.43k
            }
11290
1.48k
        }();
11291
1.48k
        if (!numericParamPresent) {
11292
1.40k
            return l_datum;
11293
1.40k
        }
11294
89
        a = l_datum->ellipsoid()->semiMajorAxis().getSIValue();
11295
89
        if (l_datum->ellipsoid()->semiMinorAxis().has_value()) {
11296
21
            b = l_datum->ellipsoid()->semiMinorAxis()->getSIValue();
11297
68
        } else {
11298
68
            rf = l_datum->ellipsoid()->computedInverseFlattening();
11299
68
        }
11300
89
    }
11301
11302
14.5k
    if (!aStr.empty()) {
11303
574
        try {
11304
574
            a = c_locale_stod(aStr);
11305
574
        } catch (const std::invalid_argument &) {
11306
35
            throw ParsingException("Invalid a value");
11307
35
        }
11308
574
    }
11309
11310
14.5k
    const auto createGRF = [&grfMap, &title, &optionalEmptyString,
11311
14.5k
                            &datumNameSuffix,
11312
14.5k
                            &pm](const EllipsoidNNPtr &ellipsoid) {
11313
777
        std::string datumName(title);
11314
777
        if (title.empty()) {
11315
638
            if (ellipsoid->nameStr() != "unknown") {
11316
106
                datumName = UNKNOWN_BASED_ON;
11317
106
                datumName += ellipsoid->nameStr();
11318
106
                datumName += " ellipsoid";
11319
532
            } else {
11320
532
                datumName = "unknown";
11321
532
            }
11322
638
            datumName += datumNameSuffix;
11323
638
        }
11324
777
        return GeodeticReferenceFrame::create(
11325
777
            grfMap.set(IdentifiedObject::NAME_KEY, datumName), ellipsoid,
11326
777
            optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
11327
777
    };
11328
11329
14.5k
    if (a > 0 && (b > 0 || !bStr.empty())) {
11330
181
        if (!bStr.empty()) {
11331
160
            try {
11332
160
                b = c_locale_stod(bStr);
11333
160
            } catch (const std::invalid_argument &) {
11334
7
                throw ParsingException("Invalid b value");
11335
7
            }
11336
160
        }
11337
174
        auto ellipsoid =
11338
174
            Ellipsoid::createTwoAxis(createMapWithUnknownName(), Length(a),
11339
174
                                     Length(b), guessBodyName(a))
11340
174
                ->identify();
11341
174
        return createGRF(ellipsoid);
11342
181
    }
11343
11344
14.3k
    else if (a > 0 && (rf >= 0 || !rfStr.empty())) {
11345
416
        if (!rfStr.empty()) {
11346
7
            try {
11347
7
                rf = c_locale_stod(rfStr);
11348
7
            } catch (const std::invalid_argument &) {
11349
5
                throw ParsingException("Invalid rf value");
11350
5
            }
11351
7
        }
11352
411
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11353
411
                             createMapWithUnknownName(), Length(a), Scale(rf),
11354
411
                             guessBodyName(a))
11355
411
                             ->identify();
11356
411
        return createGRF(ellipsoid);
11357
416
    }
11358
11359
13.9k
    else if (a > 0 && !fStr.empty()) {
11360
10
        double f;
11361
10
        try {
11362
10
            f = c_locale_stod(fStr);
11363
10
        } catch (const std::invalid_argument &) {
11364
1
            throw ParsingException("Invalid f value");
11365
1
        }
11366
9
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11367
9
                             createMapWithUnknownName(), Length(a),
11368
9
                             Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
11369
9
                             ->identify();
11370
9
        return createGRF(ellipsoid);
11371
10
    }
11372
11373
13.9k
    else if (a > 0 && !eStr.empty()) {
11374
12
        double e;
11375
12
        try {
11376
12
            e = c_locale_stod(eStr);
11377
12
        } catch (const std::invalid_argument &) {
11378
8
            throw ParsingException("Invalid e value");
11379
8
        }
11380
4
        double alpha = asin(e);    /* angular eccentricity */
11381
4
        double f = 1 - cos(alpha); /* = 1 - sqrt (1 - es); */
11382
4
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11383
4
                             createMapWithUnknownName(), Length(a),
11384
4
                             Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
11385
4
                             ->identify();
11386
4
        return createGRF(ellipsoid);
11387
12
    }
11388
11389
13.9k
    else if (a > 0 && !esStr.empty()) {
11390
0
        double es;
11391
0
        try {
11392
0
            es = c_locale_stod(esStr);
11393
0
        } catch (const std::invalid_argument &) {
11394
0
            throw ParsingException("Invalid es value");
11395
0
        }
11396
0
        double f = 1 - sqrt(1 - es);
11397
0
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11398
0
                             createMapWithUnknownName(), Length(a),
11399
0
                             Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
11400
0
                             ->identify();
11401
0
        return createGRF(ellipsoid);
11402
0
    }
11403
11404
    // If only a is specified, create a sphere
11405
13.9k
    if (a > 0 && bStr.empty() && rfStr.empty() && eStr.empty() &&
11406
179
        esStr.empty()) {
11407
179
        auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(),
11408
179
                                                 Length(a), guessBodyName(a));
11409
179
        return createGRF(ellipsoid);
11410
179
    }
11411
11412
13.7k
    if (!bStr.empty() && aStr.empty()) {
11413
6
        throw ParsingException("b found, but a missing");
11414
6
    }
11415
11416
13.7k
    if (!rfStr.empty() && aStr.empty()) {
11417
5
        throw ParsingException("rf found, but a missing");
11418
5
    }
11419
11420
13.7k
    if (!fStr.empty() && aStr.empty()) {
11421
6
        throw ParsingException("f found, but a missing");
11422
6
    }
11423
11424
13.7k
    if (!eStr.empty() && aStr.empty()) {
11425
4
        throw ParsingException("e found, but a missing");
11426
4
    }
11427
11428
13.7k
    if (!esStr.empty() && aStr.empty()) {
11429
2
        throw ParsingException("es found, but a missing");
11430
2
    }
11431
11432
13.7k
    return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
11433
13.7k
}
11434
11435
// ---------------------------------------------------------------------------
11436
11437
static const MeridianPtr nullMeridian{};
11438
11439
static CoordinateSystemAxisNNPtr
11440
createAxis(const std::string &name, const std::string &abbreviation,
11441
           const AxisDirection &direction, const common::UnitOfMeasure &unit,
11442
102k
           const MeridianPtr &meridian = nullMeridian) {
11443
102k
    return CoordinateSystemAxis::create(
11444
102k
        PropertyMap().set(IdentifiedObject::NAME_KEY, name), abbreviation,
11445
102k
        direction, unit, meridian);
11446
102k
}
11447
11448
std::vector<CoordinateSystemAxisNNPtr>
11449
PROJStringParser::Private::processAxisSwap(Step &step,
11450
                                           const UnitOfMeasure &unit,
11451
                                           int iAxisSwap, AxisType axisType,
11452
25.7k
                                           bool ignorePROJAxis) {
11453
25.7k
    assert(iAxisSwap < 0 || ci_equal(steps_[iAxisSwap].name, "axisswap"));
11454
11455
25.7k
    const bool isGeographic = unit.type() == UnitOfMeasure::Type::ANGULAR;
11456
25.7k
    const bool isSpherical = isGeographic && hasParamValue(step, "geoc");
11457
25.7k
    const auto &eastName = isSpherical    ? "Planetocentric longitude"
11458
25.7k
                           : isGeographic ? AxisName::Longitude
11459
25.5k
                                          : AxisName::Easting;
11460
25.7k
    const auto &eastAbbev = isSpherical    ? "V"
11461
25.7k
                            : isGeographic ? AxisAbbreviation::lon
11462
25.5k
                                           : AxisAbbreviation::E;
11463
25.7k
    const auto &eastDir =
11464
25.7k
        isGeographic                         ? AxisDirection::EAST
11465
25.7k
        : (axisType == AxisType::NORTH_POLE) ? AxisDirection::SOUTH
11466
8.60k
        : (axisType == AxisType::SOUTH_POLE) ? AxisDirection::NORTH
11467
8.57k
                                             : AxisDirection::EAST;
11468
25.7k
    CoordinateSystemAxisNNPtr east = createAxis(
11469
25.7k
        eastName, eastAbbev, eastDir, unit,
11470
25.7k
        (!isGeographic &&
11471
8.60k
         (axisType == AxisType::NORTH_POLE || axisType == AxisType::SOUTH_POLE))
11472
25.7k
            ? Meridian::create(Angle(90, UnitOfMeasure::DEGREE)).as_nullable()
11473
25.7k
            : nullMeridian);
11474
11475
25.7k
    const auto &northName = isSpherical    ? "Planetocentric latitude"
11476
25.7k
                            : isGeographic ? AxisName::Latitude
11477
25.5k
                                           : AxisName::Northing;
11478
25.7k
    const auto &northAbbev = isSpherical    ? "U"
11479
25.7k
                             : isGeographic ? AxisAbbreviation::lat
11480
25.5k
                                            : AxisAbbreviation::N;
11481
25.7k
    const auto &northDir = isGeographic ? AxisDirection::NORTH
11482
25.7k
                           : (axisType == AxisType::NORTH_POLE)
11483
8.60k
                               ? AxisDirection::SOUTH
11484
                               /*: (axisType == AxisType::SOUTH_POLE)
11485
                                     ? AxisDirection::NORTH*/
11486
8.60k
                               : AxisDirection::NORTH;
11487
25.7k
    const CoordinateSystemAxisNNPtr north = createAxis(
11488
25.7k
        northName, northAbbev, northDir, unit,
11489
25.7k
        isGeographic ? nullMeridian
11490
25.7k
        : (axisType == AxisType::NORTH_POLE)
11491
8.60k
            ? Meridian::create(Angle(180, UnitOfMeasure::DEGREE)).as_nullable()
11492
8.60k
        : (axisType == AxisType::SOUTH_POLE)
11493
8.57k
            ? Meridian::create(Angle(0, UnitOfMeasure::DEGREE)).as_nullable()
11494
8.57k
            : nullMeridian);
11495
11496
25.7k
    CoordinateSystemAxisNNPtr west =
11497
25.7k
        createAxis(isSpherical    ? "Planetocentric longitude"
11498
25.7k
                   : isGeographic ? AxisName::Longitude
11499
25.5k
                                  : AxisName::Westing,
11500
25.7k
                   isSpherical    ? "V"
11501
25.7k
                   : isGeographic ? AxisAbbreviation::lon
11502
25.5k
                                  : std::string(),
11503
25.7k
                   AxisDirection::WEST, unit);
11504
11505
25.7k
    CoordinateSystemAxisNNPtr south =
11506
25.7k
        createAxis(isSpherical    ? "Planetocentric latitude"
11507
25.7k
                   : isGeographic ? AxisName::Latitude
11508
25.5k
                                  : AxisName::Southing,
11509
25.7k
                   isSpherical    ? "U"
11510
25.7k
                   : isGeographic ? AxisAbbreviation::lat
11511
25.5k
                                  : std::string(),
11512
25.7k
                   AxisDirection::SOUTH, unit);
11513
11514
25.7k
    std::vector<CoordinateSystemAxisNNPtr> axis{east, north};
11515
11516
25.7k
    const auto &axisStr = getParamValue(step, "axis");
11517
25.7k
    if (!ignorePROJAxis && !axisStr.empty()) {
11518
329
        if (axisStr.size() == 3) {
11519
969
            for (int i = 0; i < 2; i++) {
11520
657
                if (axisStr[i] == 'n') {
11521
152
                    axis[i] = north;
11522
505
                } else if (axisStr[i] == 's') {
11523
168
                    axis[i] = south;
11524
337
                } else if (axisStr[i] == 'e') {
11525
130
                    axis[i] = east;
11526
207
                } else if (axisStr[i] == 'w') {
11527
190
                    axis[i] = west;
11528
190
                } else {
11529
17
                    throw ParsingException("Unhandled axis=" + axisStr);
11530
17
                }
11531
657
            }
11532
329
        } else {
11533
0
            throw ParsingException("Unhandled axis=" + axisStr);
11534
0
        }
11535
25.3k
    } else if (iAxisSwap >= 0) {
11536
76
        auto &stepAxisSwap = steps_[iAxisSwap];
11537
76
        const auto &orderStr = getParamValue(stepAxisSwap, "order");
11538
76
        auto orderTab = split(orderStr, ',');
11539
76
        if (orderTab.size() != 2) {
11540
20
            throw ParsingException("Unhandled order=" + orderStr);
11541
20
        }
11542
56
        if (stepAxisSwap.inverted) {
11543
0
            throw ParsingException("Unhandled +inv for +proj=axisswap");
11544
0
        }
11545
11546
149
        for (size_t i = 0; i < 2; i++) {
11547
109
            if (orderTab[i] == "1") {
11548
10
                axis[i] = east;
11549
99
            } else if (orderTab[i] == "-1") {
11550
42
                axis[i] = west;
11551
57
            } else if (orderTab[i] == "2") {
11552
6
                axis[i] = north;
11553
51
            } else if (orderTab[i] == "-2") {
11554
35
                axis[i] = south;
11555
35
            } else {
11556
16
                throw ParsingException("Unhandled order=" + orderStr);
11557
16
            }
11558
109
        }
11559
25.3k
    } else if ((step.name == "krovak" || step.name == "mod_krovak") &&
11560
1.90k
               hasParamValue(step, "czech")) {
11561
121
        axis[0] = std::move(west);
11562
121
        axis[1] = std::move(south);
11563
121
    }
11564
25.6k
    return axis;
11565
25.7k
}
11566
11567
// ---------------------------------------------------------------------------
11568
11569
EllipsoidalCSNNPtr PROJStringParser::Private::buildEllipsoidalCS(
11570
16.9k
    int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) {
11571
16.9k
    auto &step = steps_[iStep];
11572
16.9k
    assert(iUnitConvert < 0 ||
11573
16.9k
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11574
11575
16.9k
    UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE;
11576
16.9k
    if (iUnitConvert >= 0) {
11577
49
        auto &stepUnitConvert = steps_[iUnitConvert];
11578
49
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
11579
49
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
11580
49
        if (stepUnitConvert.inverted) {
11581
0
            std::swap(xy_in, xy_out);
11582
0
        }
11583
49
        if (iUnitConvert < iStep) {
11584
33
            std::swap(xy_in, xy_out);
11585
33
        }
11586
49
        if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" ||
11587
37
            (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) {
11588
37
            throw ParsingException("unhandled values for xy_in and/or xy_out");
11589
37
        }
11590
12
        if (*xy_out == "rad") {
11591
10
            angularUnit = UnitOfMeasure::RADIAN;
11592
10
        } else if (*xy_out == "grad") {
11593
2
            angularUnit = UnitOfMeasure::GRAD;
11594
2
        }
11595
12
    }
11596
11597
16.9k
    std::vector<CoordinateSystemAxisNNPtr> axis = processAxisSwap(
11598
16.9k
        step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis);
11599
16.9k
    CoordinateSystemAxisNNPtr up = CoordinateSystemAxis::create(
11600
16.9k
        util::PropertyMap().set(IdentifiedObject::NAME_KEY,
11601
16.9k
                                AxisName::Ellipsoidal_height),
11602
16.9k
        AxisAbbreviation::h, AxisDirection::UP,
11603
16.9k
        buildUnit(step, "vunits", "vto_meter"));
11604
11605
16.9k
    return (!hasParamValue(step, "geoidgrids") &&
11606
10.0k
            (hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter")))
11607
16.9k
               ? EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1], up)
11608
16.9k
               : EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1]);
11609
16.9k
}
11610
11611
// ---------------------------------------------------------------------------
11612
11613
SphericalCSNNPtr PROJStringParser::Private::buildSphericalCS(
11614
221
    int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) {
11615
221
    auto &step = steps_[iStep];
11616
221
    assert(iUnitConvert < 0 ||
11617
221
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11618
11619
221
    UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE;
11620
221
    if (iUnitConvert >= 0) {
11621
28
        auto &stepUnitConvert = steps_[iUnitConvert];
11622
28
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
11623
28
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
11624
28
        if (stepUnitConvert.inverted) {
11625
0
            std::swap(xy_in, xy_out);
11626
0
        }
11627
28
        if (iUnitConvert < iStep) {
11628
26
            std::swap(xy_in, xy_out);
11629
26
        }
11630
28
        if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" ||
11631
25
            (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) {
11632
25
            throw ParsingException("unhandled values for xy_in and/or xy_out");
11633
25
        }
11634
3
        if (*xy_out == "rad") {
11635
0
            angularUnit = UnitOfMeasure::RADIAN;
11636
3
        } else if (*xy_out == "grad") {
11637
3
            angularUnit = UnitOfMeasure::GRAD;
11638
3
        }
11639
3
    }
11640
11641
196
    std::vector<CoordinateSystemAxisNNPtr> axis = processAxisSwap(
11642
196
        step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis);
11643
11644
196
    return SphericalCS::create(emptyPropertyMap, axis[0], axis[1]);
11645
221
}
11646
11647
// ---------------------------------------------------------------------------
11648
11649
static double getNumericValue(const std::string &paramValue,
11650
4.35k
                              bool *pHasError = nullptr) {
11651
4.35k
    bool success;
11652
4.35k
    double value = c_locale_stod(paramValue, success);
11653
4.35k
    if (pHasError)
11654
460
        *pHasError = !success;
11655
4.35k
    return value;
11656
4.35k
}
11657
11658
// ---------------------------------------------------------------------------
11659
namespace {
11660
15.8k
template <class T> inline void ignoreRetVal(T) {}
11661
} // namespace
11662
11663
GeodeticCRSNNPtr PROJStringParser::Private::buildGeodeticCRS(
11664
15.8k
    int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) {
11665
15.8k
    auto &step = steps_[iStep];
11666
11667
15.8k
    const bool l_isGeographicStep = isGeographicStep(step.name);
11668
15.8k
    const auto &title = l_isGeographicStep ? title_ : emptyString;
11669
11670
    // units=m is often found in the wild.
11671
    // No need to create a extension string for this
11672
15.8k
    ignoreRetVal(hasParamValue(step, "units"));
11673
11674
15.8k
    auto datum = buildDatum(step, title);
11675
11676
15.8k
    auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
11677
15.8k
                                   title.empty() ? "unknown" : title);
11678
11679
15.8k
    if (l_isGeographicStep &&
11680
7.27k
        (hasUnusedParameters(step) ||
11681
5.10k
         getNumericValue(getParamValue(step, "lon_0")) != 0.0)) {
11682
5.10k
        props.set("EXTENSION_PROJ4", projString_);
11683
5.10k
    }
11684
15.8k
    props.set("IMPLICIT_CS", true);
11685
11686
15.8k
    if (!hasParamValue(step, "geoc")) {
11687
15.4k
        auto cs =
11688
15.4k
            buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, ignorePROJAxis);
11689
11690
15.4k
        return GeographicCRS::create(props, datum, cs);
11691
15.4k
    } else {
11692
350
        auto cs =
11693
350
            buildSphericalCS(iStep, iUnitConvert, iAxisSwap, ignorePROJAxis);
11694
11695
350
        return GeodeticCRS::create(props, datum, cs);
11696
350
    }
11697
15.8k
}
11698
11699
// ---------------------------------------------------------------------------
11700
11701
GeodeticCRSNNPtr
11702
2.38k
PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) {
11703
2.38k
    auto &step = steps_[iStep];
11704
11705
2.38k
    assert(isGeocentricStep(step.name) || isTopocentricStep(step.name));
11706
2.38k
    assert(iUnitConvert < 0 ||
11707
2.38k
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11708
11709
2.38k
    const auto &title = title_;
11710
11711
2.38k
    auto datum = buildDatum(step, title);
11712
11713
2.38k
    UnitOfMeasure unit = buildUnit(step, "units", "");
11714
2.38k
    if (iUnitConvert >= 0) {
11715
29
        auto &stepUnitConvert = steps_[iUnitConvert];
11716
29
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
11717
29
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
11718
29
        const std::string *z_in = &getParamValue(stepUnitConvert, "z_in");
11719
29
        const std::string *z_out = &getParamValue(stepUnitConvert, "z_out");
11720
29
        if (stepUnitConvert.inverted) {
11721
0
            std::swap(xy_in, xy_out);
11722
0
            std::swap(z_in, z_out);
11723
0
        }
11724
29
        if (xy_in->empty() || xy_out->empty() || *xy_in != "m" ||
11725
29
            *z_in != "m" || *xy_out != *z_out) {
11726
29
            throw ParsingException(
11727
29
                "unhandled values for xy_in, z_in, xy_out or z_out");
11728
29
        }
11729
11730
0
        const LinearUnitDesc *unitsMatch = nullptr;
11731
0
        try {
11732
0
            double to_meter_value = c_locale_stod(*xy_out);
11733
0
            unitsMatch = getLinearUnits(to_meter_value);
11734
0
            if (unitsMatch == nullptr) {
11735
0
                unit = _buildUnit(to_meter_value);
11736
0
            }
11737
0
        } catch (const std::invalid_argument &) {
11738
0
            unitsMatch = getLinearUnits(*xy_out);
11739
0
            if (!unitsMatch) {
11740
0
                throw ParsingException(
11741
0
                    "unhandled values for xy_in, z_in, xy_out or z_out");
11742
0
            }
11743
0
            unit = _buildUnit(unitsMatch);
11744
0
        }
11745
0
    }
11746
11747
2.35k
    auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
11748
2.35k
                                   title.empty() ? "unknown" : title);
11749
2.35k
    auto cs = CartesianCS::createGeocentric(unit);
11750
11751
2.35k
    if (hasUnusedParameters(step)) {
11752
70
        props.set("EXTENSION_PROJ4", projString_);
11753
70
    }
11754
11755
2.35k
    return GeodeticCRS::create(props, datum, cs);
11756
2.38k
}
11757
11758
// ---------------------------------------------------------------------------
11759
11760
CRSNNPtr
11761
PROJStringParser::Private::buildBoundOrCompoundCRSIfNeeded(int iStep,
11762
17.6k
                                                           CRSNNPtr crs) {
11763
17.6k
    auto &step = steps_[iStep];
11764
17.6k
    const auto &nadgrids = getParamValue(step, "nadgrids");
11765
17.6k
    const auto &towgs84 = getParamValue(step, "towgs84");
11766
    // nadgrids has the priority over towgs84
11767
17.6k
    if (!ignoreNadgrids_ && !nadgrids.empty()) {
11768
1.56k
        crs = BoundCRS::createFromNadgrids(crs, nadgrids);
11769
16.0k
    } else if (!towgs84.empty()) {
11770
1.15k
        std::vector<double> towgs84Values;
11771
1.15k
        const auto tokens = split(towgs84, ',');
11772
3.63k
        for (const auto &str : tokens) {
11773
3.63k
            try {
11774
3.63k
                towgs84Values.push_back(c_locale_stod(str));
11775
3.63k
            } catch (const std::invalid_argument &) {
11776
117
                throw ParsingException("Non numerical value in towgs84 clause");
11777
117
            }
11778
3.63k
        }
11779
11780
1.04k
        if (towgs84Values.size() == 7 && dbContext_) {
11781
2
            if (dbContext_->toWGS84AutocorrectWrongValues(
11782
2
                    towgs84Values[0], towgs84Values[1], towgs84Values[2],
11783
2
                    towgs84Values[3], towgs84Values[4], towgs84Values[5],
11784
2
                    towgs84Values[6])) {
11785
0
                for (auto &pair : step.paramValues) {
11786
0
                    if (ci_equal(pair.key, "towgs84")) {
11787
0
                        pair.value.clear();
11788
0
                        for (int i = 0; i < 7; ++i) {
11789
0
                            if (i > 0)
11790
0
                                pair.value += ',';
11791
0
                            pair.value += internal::toString(towgs84Values[i]);
11792
0
                        }
11793
0
                        break;
11794
0
                    }
11795
0
                }
11796
0
            }
11797
2
        }
11798
11799
1.04k
        crs = BoundCRS::createFromTOWGS84(crs, towgs84Values);
11800
1.04k
    }
11801
11802
17.5k
    const auto &geoidgrids = getParamValue(step, "geoidgrids");
11803
17.5k
    if (!geoidgrids.empty()) {
11804
6.65k
        auto vdatum = VerticalReferenceFrame::create(
11805
6.65k
            PropertyMap().set(common::IdentifiedObject::NAME_KEY,
11806
6.65k
                              "unknown using geoidgrids=" + geoidgrids));
11807
11808
6.65k
        const UnitOfMeasure unit = buildUnit(step, "vunits", "vto_meter");
11809
11810
6.65k
        auto vcrs =
11811
6.65k
            VerticalCRS::create(createMapWithUnknownName(), vdatum,
11812
6.65k
                                VerticalCS::createGravityRelatedHeight(unit));
11813
11814
6.65k
        CRSNNPtr geogCRS = GeographicCRS::EPSG_4979; // default
11815
6.65k
        const auto &geoid_crs = getParamValue(step, "geoid_crs");
11816
6.65k
        if (!geoid_crs.empty()) {
11817
1.97k
            if (geoid_crs == "WGS84") {
11818
                // nothing to do
11819
1.97k
            } else if (geoid_crs == "horizontal_crs") {
11820
1.92k
                auto geogCRSOfCompoundCRS = crs->extractGeographicCRS();
11821
1.92k
                if (geogCRSOfCompoundCRS &&
11822
1.92k
                    geogCRSOfCompoundCRS->primeMeridian()
11823
1.92k
                            ->longitude()
11824
1.92k
                            .getSIValue() == 0 &&
11825
1.85k
                    geogCRSOfCompoundCRS->coordinateSystem()
11826
1.85k
                            ->axisList()[0]
11827
1.85k
                            ->unit() == UnitOfMeasure::DEGREE) {
11828
1.85k
                    geogCRS = geogCRSOfCompoundCRS->promoteTo3D(std::string(),
11829
1.85k
                                                                nullptr);
11830
1.85k
                } else if (geogCRSOfCompoundCRS) {
11831
68
                    auto geogCRSOfCompoundCRSDatum =
11832
68
                        geogCRSOfCompoundCRS->datumNonNull(nullptr);
11833
68
                    geogCRS = GeographicCRS::create(
11834
68
                        createMapWithUnknownName(),
11835
68
                        datum::GeodeticReferenceFrame::create(
11836
68
                            util::PropertyMap().set(
11837
68
                                common::IdentifiedObject::NAME_KEY,
11838
68
                                geogCRSOfCompoundCRSDatum->nameStr() +
11839
68
                                    " (with Greenwich prime meridian)"),
11840
68
                            geogCRSOfCompoundCRSDatum->ellipsoid(),
11841
68
                            util::optional<std::string>(),
11842
68
                            datum::PrimeMeridian::GREENWICH),
11843
68
                        EllipsoidalCS::createLongitudeLatitudeEllipsoidalHeight(
11844
68
                            UnitOfMeasure::DEGREE, UnitOfMeasure::METRE));
11845
68
                }
11846
1.92k
            } else {
11847
43
                throw ParsingException("Unsupported value for geoid_crs: "
11848
43
                                       "should be 'WGS84' or 'horizontal_crs'");
11849
43
            }
11850
1.97k
        }
11851
6.60k
        auto transformation =
11852
6.60k
            Transformation::createGravityRelatedHeightToGeographic3D(
11853
6.60k
                PropertyMap().set(IdentifiedObject::NAME_KEY,
11854
6.60k
                                  "unknown to " + geogCRS->nameStr() +
11855
6.60k
                                      " ellipsoidal height"),
11856
6.60k
                VerticalCRS::create(createMapWithUnknownName(), vdatum,
11857
6.60k
                                    VerticalCS::createGravityRelatedHeight(
11858
6.60k
                                        common::UnitOfMeasure::METRE)),
11859
6.60k
                geogCRS, nullptr, geoidgrids,
11860
6.60k
                std::vector<PositionalAccuracyNNPtr>());
11861
6.60k
        auto boundvcrs = BoundCRS::create(vcrs, geogCRS, transformation);
11862
11863
6.60k
        crs = CompoundCRS::create(createMapWithUnknownName(),
11864
6.60k
                                  std::vector<CRSNNPtr>{crs, boundvcrs});
11865
6.60k
    }
11866
11867
17.4k
    return crs;
11868
17.5k
}
11869
11870
// ---------------------------------------------------------------------------
11871
11872
static double getAngularValue(const std::string &paramValue,
11873
2.92k
                              bool *pHasError = nullptr) {
11874
2.92k
    char *endptr = nullptr;
11875
2.92k
    double value = dmstor(paramValue.c_str(), &endptr) * RAD_TO_DEG;
11876
2.92k
    if (value == HUGE_VAL || endptr != paramValue.c_str() + paramValue.size()) {
11877
194
        if (pHasError)
11878
158
            *pHasError = true;
11879
194
        return 0.0;
11880
194
    }
11881
2.73k
    if (pHasError)
11882
2.59k
        *pHasError = false;
11883
2.73k
    return value;
11884
2.92k
}
11885
11886
// ---------------------------------------------------------------------------
11887
11888
29.6k
static bool is_in_stringlist(const std::string &str, const char *stringlist) {
11889
29.6k
    if (str.empty())
11890
796
        return false;
11891
28.8k
    const char *haystack = stringlist;
11892
294k
    while (true) {
11893
294k
        const char *res = strstr(haystack, str.c_str());
11894
294k
        if (res == nullptr)
11895
25.1k
            return false;
11896
269k
        if ((res == stringlist || res[-1] == ',') &&
11897
96.8k
            (res[str.size()] == ',' || res[str.size()] == '\0'))
11898
3.75k
            return true;
11899
265k
        haystack += str.size();
11900
265k
    }
11901
28.8k
}
11902
11903
// ---------------------------------------------------------------------------
11904
11905
CRSNNPtr
11906
PROJStringParser::Private::buildProjectedCRS(int iStep,
11907
                                             const GeodeticCRSNNPtr &geodCRS,
11908
10.3k
                                             int iUnitConvert, int iAxisSwap) {
11909
10.3k
    auto &step = steps_[iStep];
11910
10.3k
    const auto mappings = getMappingsFromPROJName(step.name);
11911
10.3k
    const MethodMapping *mapping = mappings.empty() ? nullptr : mappings[0];
11912
11913
10.3k
    bool foundStrictlyMatchingMapping = false;
11914
10.3k
    if (mappings.size() >= 2) {
11915
        // To distinguish for example +ortho from +ortho +f=0
11916
2.45k
        bool allMappingsHaveAuxParam = true;
11917
6.04k
        for (const auto *mappingIter : mappings) {
11918
6.04k
            if (mappingIter->proj_name_aux == nullptr) {
11919
3.87k
                allMappingsHaveAuxParam = false;
11920
3.87k
            }
11921
6.04k
            if (mappingIter->proj_name_aux != nullptr &&
11922
2.16k
                strchr(mappingIter->proj_name_aux, '=') == nullptr &&
11923
662
                hasParamValue(step, mappingIter->proj_name_aux)) {
11924
36
                foundStrictlyMatchingMapping = true;
11925
36
                mapping = mappingIter;
11926
36
                break;
11927
6.00k
            } else if (mappingIter->proj_name_aux != nullptr &&
11928
2.13k
                       strchr(mappingIter->proj_name_aux, '=') != nullptr) {
11929
1.50k
                const auto tokens = split(mappingIter->proj_name_aux, '=');
11930
1.50k
                if (tokens.size() == 2 &&
11931
1.50k
                    getParamValue(step, tokens[0]) == tokens[1]) {
11932
57
                    foundStrictlyMatchingMapping = true;
11933
57
                    mapping = mappingIter;
11934
57
                    break;
11935
57
                }
11936
1.50k
            }
11937
6.04k
        }
11938
2.45k
        if (allMappingsHaveAuxParam && !foundStrictlyMatchingMapping) {
11939
42
            mapping = nullptr;
11940
42
        }
11941
2.45k
    }
11942
11943
10.3k
    if (mapping && !foundStrictlyMatchingMapping) {
11944
4.14k
        mapping = selectSphericalOrEllipsoidal(mapping, geodCRS);
11945
4.14k
    }
11946
11947
10.3k
    assert(isProjectedStep(step.name));
11948
10.3k
    assert(iUnitConvert < 0 ||
11949
10.3k
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11950
11951
10.3k
    const auto &title = title_;
11952
11953
10.3k
    if (!buildPrimeMeridian(step)->longitude()._isEquivalentTo(
11954
10.3k
            geodCRS->primeMeridian()->longitude(),
11955
10.3k
            util::IComparable::Criterion::EQUIVALENT)) {
11956
3
        throw ParsingException("inconsistent pm values between projectedCRS "
11957
3
                               "and its base geographicalCRS");
11958
3
    }
11959
11960
10.3k
    auto axisType = AxisType::REGULAR;
11961
10.3k
    bool bWebMercator = false;
11962
10.3k
    std::string webMercatorName("WGS 84 / Pseudo-Mercator");
11963
11964
10.3k
    if (step.name == "tmerc" &&
11965
350
        ((getParamValue(step, "axis") == "wsu" && iAxisSwap < 0) ||
11966
340
         (iAxisSwap > 0 &&
11967
72
          getParamValue(steps_[iAxisSwap], "order") == "-1,-2"))) {
11968
22
        mapping =
11969
22
            getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED);
11970
10.3k
    } else if (step.name == "etmerc") {
11971
4
        mapping = getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR);
11972
10.3k
    } else if (step.name == "lcc") {
11973
70
        const auto &lat_0 = getParamValue(step, "lat_0");
11974
70
        const auto &lat_1 = getParamValue(step, "lat_1");
11975
70
        const auto &lat_2 = getParamValue(step, "lat_2");
11976
70
        const auto &k = getParamValueK(step);
11977
70
        if (lat_2.empty() && !lat_0.empty() && !lat_1.empty()) {
11978
7
            if (lat_0 == lat_1 ||
11979
                // For some reason with gcc 5.3.1-14ubuntu2 32bit, the following
11980
                // comparison returns false even if lat_0 == lat_1. Smells like
11981
                // a compiler bug
11982
7
                getAngularValue(lat_0) == getAngularValue(lat_1)) {
11983
1
                mapping =
11984
1
                    getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
11985
6
            } else {
11986
6
                mapping = getMapping(
11987
6
                    EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP_VARIANT_B);
11988
6
            }
11989
63
        } else if (!k.empty() && getNumericValue(k) != 1.0) {
11990
2
            mapping = getMapping(
11991
2
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN);
11992
61
        } else {
11993
61
            mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
11994
61
        }
11995
10.2k
    } else if (step.name == "aeqd" && hasParamValue(step, "guam")) {
11996
7
        mapping = getMapping(EPSG_CODE_METHOD_GUAM_PROJECTION);
11997
10.2k
    } else if (step.name == "geos" && getParamValue(step, "sweep") == "x") {
11998
15
        mapping =
11999
15
            getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X);
12000
10.2k
    } else if (step.name == "geos") {
12001
10
        mapping =
12002
10
            getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y);
12003
10.2k
    } else if (step.name == "omerc") {
12004
63
        if (hasParamValue(step, "no_rot")) {
12005
0
            mapping = nullptr;
12006
63
        } else if (hasParamValue(step, "no_uoff") ||
12007
63
                   hasParamValue(step, "no_off")) {
12008
5
            mapping =
12009
5
                getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A);
12010
58
        } else if (hasParamValue(step, "lat_1") &&
12011
50
                   hasParamValue(step, "lon_1") &&
12012
14
                   hasParamValue(step, "lat_2") &&
12013
3
                   hasParamValue(step, "lon_2")) {
12014
0
            mapping = getMapping(
12015
0
                PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN);
12016
58
        } else {
12017
58
            mapping =
12018
58
                getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B);
12019
58
        }
12020
10.1k
    } else if (step.name == "somerc") {
12021
30
        mapping =
12022
30
            getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B);
12023
30
        if (!hasParamValue(step, "alpha") && !hasParamValue(step, "gamma") &&
12024
18
            !hasParamValue(step, "lonc")) {
12025
18
            step.paramValues.emplace_back(Step::KeyValue("alpha", "90"));
12026
18
            step.paramValues.emplace_back(Step::KeyValue("gamma", "90"));
12027
18
            step.paramValues.emplace_back(
12028
18
                Step::KeyValue("lonc", getParamValue(step, "lon_0")));
12029
18
        }
12030
10.1k
    } else if (step.name == "krovak" &&
12031
827
               ((iAxisSwap < 0 && getParamValue(step, "axis") == "swu" &&
12032
12
                 !hasParamValue(step, "czech")) ||
12033
815
                (iAxisSwap > 0 &&
12034
0
                 getParamValue(steps_[iAxisSwap], "order") == "-2,-1" &&
12035
12
                 !hasParamValue(step, "czech")))) {
12036
12
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
12037
10.1k
    } else if (step.name == "krovak" && iAxisSwap < 0 &&
12038
815
               hasParamValue(step, "czech") && !hasParamValue(step, "axis")) {
12039
2
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
12040
10.1k
    } else if (step.name == "mod_krovak" &&
12041
146
               ((iAxisSwap < 0 && getParamValue(step, "axis") == "swu" &&
12042
4
                 !hasParamValue(step, "czech")) ||
12043
144
                (iAxisSwap > 0 &&
12044
0
                 getParamValue(steps_[iAxisSwap], "order") == "-2,-1" &&
12045
2
                 !hasParamValue(step, "czech")))) {
12046
2
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK_MODIFIED);
12047
10.1k
    } else if (step.name == "mod_krovak" && iAxisSwap < 0 &&
12048
144
               hasParamValue(step, "czech") && !hasParamValue(step, "axis")) {
12049
55
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK_MODIFIED);
12050
10.0k
    } else if (step.name == "merc") {
12051
154
        if (hasParamValue(step, "a") && hasParamValue(step, "b") &&
12052
6
            getParamValue(step, "a") == getParamValue(step, "b") &&
12053
2
            (!hasParamValue(step, "lat_ts") ||
12054
0
             getAngularValue(getParamValue(step, "lat_ts")) == 0.0) &&
12055
2
            getNumericValue(getParamValueK(step)) == 1.0 &&
12056
0
            getParamValue(step, "nadgrids") == "@null") {
12057
0
            mapping = getMapping(
12058
0
                EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR);
12059
0
            for (size_t i = 0; i < step.paramValues.size(); ++i) {
12060
0
                if (ci_equal(step.paramValues[i].key, "nadgrids")) {
12061
0
                    ignoreNadgrids_ = true;
12062
0
                    break;
12063
0
                }
12064
0
            }
12065
0
            if (getNumericValue(getParamValue(step, "a")) == 6378137 &&
12066
0
                getAngularValue(getParamValue(step, "lon_0")) == 0.0 &&
12067
0
                getAngularValue(getParamValue(step, "lat_0")) == 0.0 &&
12068
0
                getAngularValue(getParamValue(step, "x_0")) == 0.0 &&
12069
0
                getAngularValue(getParamValue(step, "y_0")) == 0.0) {
12070
0
                bWebMercator = true;
12071
0
                if (hasParamValue(step, "units") &&
12072
0
                    getParamValue(step, "units") != "m") {
12073
0
                    webMercatorName +=
12074
0
                        " (unit " + getParamValue(step, "units") + ')';
12075
0
                }
12076
0
            }
12077
154
        } else if (hasParamValue(step, "lat_ts")) {
12078
16
            if (hasParamValue(step, "R_C") &&
12079
2
                !geodCRS->ellipsoid()->isSphere() &&
12080
2
                getAngularValue(getParamValue(step, "lat_ts")) != 0) {
12081
0
                throw ParsingException("lat_ts != 0 not supported for "
12082
0
                                       "spherical Mercator on an ellipsoid");
12083
0
            }
12084
16
            mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
12085
138
        } else if (hasParamValue(step, "R_C")) {
12086
24
            const auto &k = getParamValueK(step);
12087
24
            if (!k.empty() && getNumericValue(k) != 1.0) {
12088
3
                if (geodCRS->ellipsoid()->isSphere()) {
12089
0
                    mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A);
12090
3
                } else {
12091
3
                    throw ParsingException(
12092
3
                        "k_0 != 1 not supported for spherical Mercator on an "
12093
3
                        "ellipsoid");
12094
3
                }
12095
21
            } else {
12096
21
                mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_SPHERICAL);
12097
21
            }
12098
114
        } else {
12099
114
            mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A);
12100
114
        }
12101
9.91k
    } else if (step.name == "stere") {
12102
227
        if (hasParamValue(step, "lat_0") &&
12103
71
            std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) -
12104
71
                      90.0) < 1e-10) {
12105
48
            const double lat_0 = getAngularValue(getParamValue(step, "lat_0"));
12106
48
            if (lat_0 > 0) {
12107
44
                axisType = AxisType::NORTH_POLE;
12108
44
            } else {
12109
4
                axisType = AxisType::SOUTH_POLE;
12110
4
            }
12111
48
            const auto &lat_ts = getParamValue(step, "lat_ts");
12112
48
            const auto &k = getParamValueK(step);
12113
48
            if (!lat_ts.empty() &&
12114
27
                std::fabs(getAngularValue(lat_ts) - lat_0) > 1e-10 &&
12115
19
                !k.empty() && std::fabs(getNumericValue(k) - 1) > 1e-10) {
12116
6
                throw ParsingException("lat_ts != lat_0 and k != 1 not "
12117
6
                                       "supported for Polar Stereographic");
12118
6
            }
12119
42
            if (!lat_ts.empty() &&
12120
21
                (k.empty() || std::fabs(getNumericValue(k) - 1) < 1e-10)) {
12121
17
                mapping =
12122
17
                    getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B);
12123
25
            } else {
12124
25
                mapping =
12125
25
                    getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A);
12126
25
            }
12127
179
        } else {
12128
179
            mapping = getMapping(PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC);
12129
179
        }
12130
9.68k
    } else if (step.name == "laea") {
12131
211
        if (hasParamValue(step, "lat_0") &&
12132
10
            std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) -
12133
10
                      90.0) < 1e-10) {
12134
2
            const double lat_0 = getAngularValue(getParamValue(step, "lat_0"));
12135
2
            if (lat_0 > 0) {
12136
2
                axisType = AxisType::NORTH_POLE;
12137
2
            } else {
12138
0
                axisType = AxisType::SOUTH_POLE;
12139
0
            }
12140
2
        }
12141
9.47k
    } else if (step.name == "ortho") {
12142
41
        const std::string &k = getParamValueK(step);
12143
41
        if ((!k.empty() && getNumericValue(k) != 1.0) ||
12144
41
            (hasParamValue(step, "alpha") &&
12145
14
             getNumericValue(getParamValue(step, "alpha")) != 0.0)) {
12146
8
            mapping = getMapping(EPSG_CODE_METHOD_LOCAL_ORTHOGRAPHIC);
12147
8
        }
12148
41
    }
12149
12150
10.3k
    UnitOfMeasure unit = buildUnit(step, "units", "to_meter");
12151
10.3k
    if (iUnitConvert >= 0) {
12152
26
        auto &stepUnitConvert = steps_[iUnitConvert];
12153
26
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
12154
26
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
12155
26
        if (stepUnitConvert.inverted) {
12156
0
            std::swap(xy_in, xy_out);
12157
0
        }
12158
26
        if (xy_in->empty() || xy_out->empty() || *xy_in != "m") {
12159
26
            if (step.name != "ob_tran") {
12160
22
                throw ParsingException(
12161
22
                    "unhandled values for xy_in and/or xy_out");
12162
22
            }
12163
26
        }
12164
12165
4
        const LinearUnitDesc *unitsMatch = nullptr;
12166
4
        try {
12167
4
            double to_meter_value = c_locale_stod(*xy_out);
12168
4
            unitsMatch = getLinearUnits(to_meter_value);
12169
4
            if (unitsMatch == nullptr) {
12170
0
                unit = _buildUnit(to_meter_value);
12171
0
            }
12172
4
        } catch (const std::invalid_argument &) {
12173
4
            unitsMatch = getLinearUnits(*xy_out);
12174
4
            if (!unitsMatch) {
12175
4
                if (step.name != "ob_tran") {
12176
0
                    throw ParsingException(
12177
0
                        "unhandled values for xy_in and/or xy_out");
12178
0
                }
12179
4
            } else {
12180
0
                unit = _buildUnit(unitsMatch);
12181
0
            }
12182
4
        }
12183
4
    }
12184
12185
10.3k
    ConversionPtr conv;
12186
12187
10.3k
    auto mapWithUnknownName = createMapWithUnknownName();
12188
12189
10.3k
    if (step.name == "utm") {
12190
51
        const int zone = std::atoi(getParamValue(step, "zone").c_str());
12191
51
        const bool north = !hasParamValue(step, "south");
12192
51
        conv =
12193
51
            Conversion::createUTM(emptyPropertyMap, zone, north).as_nullable();
12194
10.2k
    } else if (mapping) {
12195
12196
4.24k
        auto methodMap =
12197
4.24k
            PropertyMap().set(IdentifiedObject::NAME_KEY, mapping->wkt2_name);
12198
4.24k
        if (mapping->epsg_code) {
12199
2.25k
            methodMap.set(Identifier::CODESPACE_KEY, Identifier::EPSG)
12200
2.25k
                .set(Identifier::CODE_KEY, mapping->epsg_code);
12201
2.25k
        }
12202
4.24k
        std::vector<OperationParameterNNPtr> parameters;
12203
4.24k
        std::vector<ParameterValueNNPtr> values;
12204
24.3k
        for (int i = 0; mapping->params[i] != nullptr; i++) {
12205
20.1k
            const auto *param = mapping->params[i];
12206
20.1k
            std::string proj_name(param->proj_name ? param->proj_name : "");
12207
20.1k
            const std::string *paramValue =
12208
20.1k
                (proj_name == "k" || proj_name == "k_0") ? &getParamValueK(step)
12209
20.1k
                : !proj_name.empty() ? &getParamValue(step, proj_name)
12210
18.3k
                                     : &emptyString;
12211
20.1k
            double value = 0;
12212
20.1k
            if (!paramValue->empty()) {
12213
390
                bool hasError = false;
12214
390
                if (param->unit_type == UnitOfMeasure::Type::ANGULAR) {
12215
300
                    value = getAngularValue(*paramValue, &hasError);
12216
300
                } else {
12217
90
                    value = getNumericValue(*paramValue, &hasError);
12218
90
                }
12219
390
                if (hasError) {
12220
87
                    throw ParsingException("invalid value for " + proj_name);
12221
87
                }
12222
390
            }
12223
            // For omerc, if gamma is missing, the default value is
12224
            // alpha
12225
19.7k
            else if (step.name == "omerc" && proj_name == "gamma") {
12226
58
                paramValue = &getParamValue(step, "alpha");
12227
58
                if (!paramValue->empty()) {
12228
0
                    value = getAngularValue(*paramValue);
12229
0
                }
12230
19.7k
            } else if (step.name == "krovak" || step.name == "mod_krovak") {
12231
                // Keep it in sync with defaults of krovak.cpp
12232
6.75k
                if (param->epsg_code ==
12233
6.75k
                    EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE) {
12234
971
                    value = 49.5;
12235
5.78k
                } else if (param->epsg_code ==
12236
5.78k
                           EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN) {
12237
963
                    value = 24.833333333333333333;
12238
4.81k
                } else if (param->epsg_code ==
12239
4.81k
                           EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS) {
12240
966
                    value = 30.28813975277777776;
12241
3.85k
                } else if (
12242
3.85k
                    param->epsg_code ==
12243
3.85k
                    EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL) {
12244
966
                    value = 78.5;
12245
2.88k
                } else if (
12246
2.88k
                    param->epsg_code ==
12247
2.88k
                    EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL) {
12248
963
                    value = 0.9999;
12249
963
                }
12250
12.9k
            } else if (step.name == "cea" && proj_name == "lat_ts") {
12251
119
                paramValue = &getParamValueK(step);
12252
119
                if (!paramValue->empty()) {
12253
13
                    bool hasError = false;
12254
13
                    const double k = getNumericValue(*paramValue, &hasError);
12255
13
                    if (hasError) {
12256
5
                        throw ParsingException("invalid value for k/k_0");
12257
5
                    }
12258
8
                    if (k >= 0 && k <= 1) {
12259
7
                        const double es =
12260
7
                            geodCRS->ellipsoid()->squaredEccentricity();
12261
7
                        if (es < 0 || es == 1) {
12262
0
                            throw ParsingException("Invalid flattening");
12263
0
                        }
12264
7
                        value =
12265
7
                            Angle(acos(k * sqrt((1 - es) / (1 - k * k * es))),
12266
7
                                  UnitOfMeasure::RADIAN)
12267
7
                                .convertToUnit(UnitOfMeasure::DEGREE);
12268
7
                    } else {
12269
1
                        throw ParsingException("k/k_0 should be in [0,1]");
12270
1
                    }
12271
8
                }
12272
12.8k
            } else if (param->unit_type == UnitOfMeasure::Type::SCALE) {
12273
791
                value = 1;
12274
12.0k
            } else if (step.name == "peirce_q" && proj_name == "lat_0") {
12275
16
                value = 90;
12276
16
            }
12277
12278
20.0k
            PropertyMap propertiesParameter;
12279
20.0k
            propertiesParameter.set(IdentifiedObject::NAME_KEY,
12280
20.0k
                                    param->wkt2_name);
12281
20.0k
            if (param->epsg_code) {
12282
19.4k
                propertiesParameter.set(Identifier::CODE_KEY, param->epsg_code);
12283
19.4k
                propertiesParameter.set(Identifier::CODESPACE_KEY,
12284
19.4k
                                        Identifier::EPSG);
12285
19.4k
            }
12286
20.0k
            parameters.push_back(
12287
20.0k
                OperationParameter::create(propertiesParameter));
12288
            // In PROJ convention, angular parameters are always in degree
12289
            // and linear parameters always in metre.
12290
20.0k
            double valRounded =
12291
20.0k
                param->unit_type == UnitOfMeasure::Type::LINEAR
12292
20.0k
                    ? Length(value, UnitOfMeasure::METRE).convertToUnit(unit)
12293
20.0k
                    : value;
12294
20.0k
            if (std::fabs(valRounded - std::round(valRounded)) < 1e-8) {
12295
15.2k
                valRounded = std::round(valRounded);
12296
15.2k
            }
12297
20.0k
            values.push_back(ParameterValue::create(
12298
20.0k
                Measure(valRounded,
12299
20.0k
                        param->unit_type == UnitOfMeasure::Type::ANGULAR
12300
20.0k
                            ? UnitOfMeasure::DEGREE
12301
20.0k
                        : param->unit_type == UnitOfMeasure::Type::LINEAR ? unit
12302
10.1k
                        : param->unit_type == UnitOfMeasure::Type::SCALE
12303
1.77k
                            ? UnitOfMeasure::SCALE_UNITY
12304
1.77k
                            : UnitOfMeasure::NONE)));
12305
20.0k
        }
12306
12307
4.15k
        if (step.name == "tmerc" && hasParamValue(step, "approx")) {
12308
0
            methodMap.set("proj_method", "tmerc approx");
12309
4.15k
        } else if (step.name == "utm" && hasParamValue(step, "approx")) {
12310
0
            methodMap.set("proj_method", "utm approx");
12311
0
        }
12312
12313
4.15k
        conv = Conversion::create(mapWithUnknownName, methodMap, parameters,
12314
4.15k
                                  values)
12315
4.15k
                   .as_nullable();
12316
6.02k
    } else {
12317
6.02k
        std::vector<OperationParameterNNPtr> parameters;
12318
6.02k
        std::vector<ParameterValueNNPtr> values;
12319
6.02k
        std::string methodName = "PROJ " + step.name;
12320
24.1k
        for (const auto &param : step.paramValues) {
12321
24.1k
            if (is_in_stringlist(param.key,
12322
24.1k
                                 "wktext,no_defs,datum,ellps,a,b,R,f,rf,"
12323
24.1k
                                 "towgs84,nadgrids,geoidgrids,"
12324
24.1k
                                 "units,to_meter,vunits,vto_meter,type")) {
12325
3.39k
                continue;
12326
3.39k
            }
12327
20.7k
            if (param.value.empty()) {
12328
10.1k
                methodName += " " + param.key;
12329
10.6k
            } else if (isalpha(param.value[0]) || param.key == "gores") {
12330
                // The value of gores can be a string or a number.
12331
                // See interrupted.cpp for more info.
12332
7.80k
                methodName += " " + param.key + "=" + param.value;
12333
7.80k
            } else {
12334
2.80k
                parameters.push_back(OperationParameter::create(
12335
2.80k
                    PropertyMap().set(IdentifiedObject::NAME_KEY, param.key)));
12336
2.80k
                bool hasError = false;
12337
2.80k
                if (is_in_stringlist(param.key, "x_0,y_0,h,h_0")) {
12338
112
                    double value = getNumericValue(param.value, &hasError);
12339
112
                    values.push_back(ParameterValue::create(
12340
112
                        Measure(value, UnitOfMeasure::METRE)));
12341
2.69k
                } else if (is_in_stringlist(
12342
2.69k
                               param.key,
12343
2.69k
                               "k,k_0,"
12344
2.69k
                               "north_square,south_square," // rhealpix
12345
2.69k
                               "n,m,"                       // sinu
12346
2.69k
                               "q,"                         // urm5
12347
2.69k
                               "path,lsat,"                 // lsat
12348
2.69k
                               "W,M,"                       // hammer
12349
2.69k
                               )) {
12350
245
                    double value = getNumericValue(param.value, &hasError);
12351
245
                    values.push_back(ParameterValue::create(
12352
245
                        Measure(value, UnitOfMeasure::SCALE_UNITY)));
12353
2.45k
                } else {
12354
2.45k
                    double value = getAngularValue(param.value, &hasError);
12355
2.45k
                    values.push_back(ParameterValue::create(
12356
2.45k
                        Measure(value, UnitOfMeasure::DEGREE)));
12357
2.45k
                }
12358
2.80k
                if (hasError) {
12359
115
                    throw ParsingException("invalid value for " + param.key);
12360
115
                }
12361
2.80k
            }
12362
20.7k
        }
12363
5.91k
        conv = Conversion::create(
12364
5.91k
                   mapWithUnknownName,
12365
5.91k
                   PropertyMap().set(IdentifiedObject::NAME_KEY, methodName),
12366
5.91k
                   parameters, values)
12367
5.91k
                   .as_nullable();
12368
12369
5.91k
        for (const char *substr :
12370
5.91k
             {"PROJ ob_tran o_proj=longlat", "PROJ ob_tran o_proj=lonlat",
12371
20.5k
              "PROJ ob_tran o_proj=latlon", "PROJ ob_tran o_proj=latlong"}) {
12372
20.5k
            if (starts_with(methodName, substr)) {
12373
1.49k
                auto geogCRS =
12374
1.49k
                    util::nn_dynamic_pointer_cast<GeographicCRS>(geodCRS);
12375
1.49k
                if (geogCRS) {
12376
1.48k
                    return DerivedGeographicCRS::create(
12377
1.48k
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
12378
1.48k
                                          "unknown"),
12379
1.48k
                        NN_NO_CHECK(geogCRS), NN_NO_CHECK(conv),
12380
1.48k
                        buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap,
12381
1.48k
                                           false));
12382
1.48k
                }
12383
1.49k
            }
12384
20.5k
        }
12385
5.91k
    }
12386
12387
8.62k
    std::vector<CoordinateSystemAxisNNPtr> axis =
12388
8.62k
        processAxisSwap(step, unit, iAxisSwap, axisType, false);
12389
12390
8.62k
    auto csGeodCRS = geodCRS->coordinateSystem();
12391
8.62k
    auto cs = csGeodCRS->axisList().size() == 2
12392
8.62k
                  ? CartesianCS::create(emptyPropertyMap, axis[0], axis[1],
12393
7.38k
                                        /* enforceSameUnit = */ false)
12394
8.62k
                  : CartesianCS::create(emptyPropertyMap, axis[0], axis[1],
12395
1.24k
                                        csGeodCRS->axisList()[2],
12396
1.24k
                                        /* enforceSameUnit = */ false);
12397
8.62k
    if (isTopocentricStep(step.name)) {
12398
8
        cs = CartesianCS::create(
12399
8
            emptyPropertyMap,
12400
8
            createAxis("topocentric East", "U", AxisDirection::EAST, unit),
12401
8
            createAxis("topocentric North", "V", AxisDirection::NORTH, unit),
12402
8
            createAxis("topocentric Up", "W", AxisDirection::UP, unit));
12403
8
    }
12404
12405
8.62k
    auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
12406
8.62k
                                   title.empty() ? "unknown" : title);
12407
8.62k
    if (hasUnusedParameters(step)) {
12408
1.33k
        props.set("EXTENSION_PROJ4", projString_);
12409
1.33k
    }
12410
12411
8.62k
    props.set("IMPLICIT_CS", true);
12412
12413
8.62k
    CRSNNPtr crs =
12414
8.62k
        bWebMercator
12415
8.62k
            ? createPseudoMercator(
12416
0
                  props.set(IdentifiedObject::NAME_KEY, webMercatorName), cs)
12417
8.62k
            : ProjectedCRS::create(props, geodCRS, NN_NO_CHECK(conv), cs);
12418
12419
8.62k
    return crs;
12420
10.3k
}
12421
12422
//! @endcond
12423
12424
// ---------------------------------------------------------------------------
12425
12426
//! @cond Doxygen_Suppress
12427
static const metadata::ExtentPtr nullExtent{};
12428
12429
0
static const metadata::ExtentPtr &getExtent(const crs::CRS *crs) {
12430
0
    const auto &domains = crs->domains();
12431
0
    if (!domains.empty()) {
12432
0
        return domains[0]->domainOfValidity();
12433
0
    }
12434
0
    return nullExtent;
12435
0
}
12436
12437
//! @endcond
12438
12439
namespace {
12440
struct PJContextHolder {
12441
    PJ_CONTEXT *ctx_;
12442
    bool bFree_;
12443
12444
569
    PJContextHolder(PJ_CONTEXT *ctx, bool bFree) : ctx_(ctx), bFree_(bFree) {}
12445
569
    ~PJContextHolder() {
12446
569
        if (bFree_)
12447
3
            proj_context_destroy(ctx_);
12448
569
    }
12449
    PJContextHolder(const PJContextHolder &) = delete;
12450
    PJContextHolder &operator=(const PJContextHolder &) = delete;
12451
};
12452
} // namespace
12453
12454
// ---------------------------------------------------------------------------
12455
12456
/** \brief Instantiate a sub-class of BaseObject from a PROJ string.
12457
 *
12458
 * The projString must contain +type=crs for the object to be detected as a
12459
 * CRS instead of a CoordinateOperation.
12460
 *
12461
 * @throw ParsingException if the string cannot be parsed.
12462
 */
12463
BaseObjectNNPtr
12464
13.3k
PROJStringParser::createFromPROJString(const std::string &projString) {
12465
12466
    // In some abnormal situations involving init=epsg:XXXX syntax, we could
12467
    // have infinite loop
12468
13.3k
    if (d->ctx_ &&
12469
13.2k
        d->ctx_->projStringParserCreateFromPROJStringRecursionCounter == 2) {
12470
0
        throw ParsingException(
12471
0
            "Infinite recursion in PROJStringParser::createFromPROJString()");
12472
0
    }
12473
12474
13.3k
    d->steps_.clear();
12475
13.3k
    d->title_.clear();
12476
13.3k
    d->globalParamValues_.clear();
12477
13.3k
    d->projString_ = projString;
12478
13.3k
    PROJStringSyntaxParser(projString, d->steps_, d->globalParamValues_,
12479
13.3k
                           d->title_);
12480
12481
13.3k
    if (d->steps_.empty()) {
12482
235
        const auto &vunits = d->getGlobalParamValue("vunits");
12483
235
        const auto &vto_meter = d->getGlobalParamValue("vto_meter");
12484
235
        if (!vunits.empty() || !vto_meter.empty()) {
12485
48
            Step fakeStep;
12486
48
            if (!vunits.empty()) {
12487
42
                fakeStep.paramValues.emplace_back(
12488
42
                    Step::KeyValue("vunits", vunits));
12489
42
            }
12490
48
            if (!vto_meter.empty()) {
12491
6
                fakeStep.paramValues.emplace_back(
12492
6
                    Step::KeyValue("vto_meter", vto_meter));
12493
6
            }
12494
48
            auto vdatum =
12495
48
                VerticalReferenceFrame::create(createMapWithUnknownName());
12496
48
            auto vcrs = VerticalCRS::create(
12497
48
                createMapWithUnknownName(), vdatum,
12498
48
                VerticalCS::createGravityRelatedHeight(
12499
48
                    d->buildUnit(fakeStep, "vunits", "vto_meter")));
12500
48
            return vcrs;
12501
48
        }
12502
235
    }
12503
12504
13.3k
    const bool isGeocentricCRS =
12505
13.3k
        ((d->steps_.size() == 1 &&
12506
11.3k
          d->getParamValue(d->steps_[0], "type") == "crs") ||
12507
4.24k
         (d->steps_.size() == 2 && d->steps_[1].name == "unitconvert")) &&
12508
9.15k
        !d->steps_[0].inverted && isGeocentricStep(d->steps_[0].name);
12509
12510
13.3k
    const bool isTopocentricCRS =
12511
13.3k
        (d->steps_.size() == 1 && isTopocentricStep(d->steps_[0].name) &&
12512
37
         d->getParamValue(d->steps_[0], "type") == "crs");
12513
12514
    // +init=xxxx:yyyy syntax
12515
13.3k
    if (d->steps_.size() == 1 && d->steps_[0].isInit &&
12516
583
        !d->steps_[0].inverted) {
12517
12518
569
        auto ctx = d->ctx_ ? d->ctx_ : proj_context_create();
12519
569
        if (!ctx) {
12520
0
            throw ParsingException("out of memory");
12521
0
        }
12522
569
        PJContextHolder contextHolder(ctx, ctx != d->ctx_);
12523
12524
        // Those used to come from a text init file
12525
        // We only support them in compatibility mode
12526
569
        const std::string &stepName = d->steps_[0].name;
12527
569
        if (ci_starts_with(stepName, "epsg:") ||
12528
569
            ci_starts_with(stepName, "IGNF:")) {
12529
12530
4
            struct BackupContextErrno {
12531
4
                PJ_CONTEXT *m_ctxt = nullptr;
12532
4
                int m_last_errno = 0;
12533
12534
4
                explicit BackupContextErrno(PJ_CONTEXT *ctxtIn)
12535
4
                    : m_ctxt(ctxtIn), m_last_errno(m_ctxt->last_errno) {
12536
4
                    m_ctxt->debug_level = PJ_LOG_ERROR;
12537
4
                }
12538
12539
4
                ~BackupContextErrno() { m_ctxt->last_errno = m_last_errno; }
12540
12541
4
                BackupContextErrno(const BackupContextErrno &) = delete;
12542
4
                BackupContextErrno &
12543
4
                operator=(const BackupContextErrno &) = delete;
12544
4
            };
12545
12546
4
            BackupContextErrno backupContextErrno(ctx);
12547
12548
4
            bool usePROJ4InitRules = d->usePROJ4InitRules_;
12549
4
            if (!usePROJ4InitRules) {
12550
4
                usePROJ4InitRules =
12551
4
                    proj_context_get_use_proj4_init_rules(ctx, FALSE) == TRUE;
12552
4
            }
12553
4
            if (!usePROJ4InitRules) {
12554
4
                throw ParsingException("init=epsg:/init=IGNF: syntax not "
12555
4
                                       "supported in non-PROJ4 emulation mode");
12556
4
            }
12557
12558
0
            char unused[256];
12559
0
            std::string initname(stepName);
12560
0
            initname.resize(initname.find(':'));
12561
0
            int file_found =
12562
0
                pj_find_file(ctx, initname.c_str(), unused, sizeof(unused),
12563
0
                             /* disable_network = */ true);
12564
12565
0
            if (!file_found) {
12566
0
                auto obj = createFromUserInput(stepName, d->dbContext_, true);
12567
0
                auto crs = dynamic_cast<CRS *>(obj.get());
12568
12569
0
                bool hasSignificantParamValues = false;
12570
0
                bool hasOver = false;
12571
0
                for (const auto &kv : d->steps_[0].paramValues) {
12572
0
                    if (kv.key == "over") {
12573
0
                        hasOver = true;
12574
0
                    } else if (!((kv.key == "type" && kv.value == "crs") ||
12575
0
                                 kv.key == "wktext" || kv.key == "no_defs")) {
12576
0
                        hasSignificantParamValues = true;
12577
0
                        break;
12578
0
                    }
12579
0
                }
12580
12581
0
                if (crs && !hasSignificantParamValues) {
12582
0
                    PropertyMap properties;
12583
0
                    properties.set(IdentifiedObject::NAME_KEY,
12584
0
                                   d->title_.empty() ? crs->nameStr()
12585
0
                                                     : d->title_);
12586
0
                    if (hasOver) {
12587
0
                        properties.set("OVER", true);
12588
0
                    }
12589
0
                    const auto &extent = getExtent(crs);
12590
0
                    if (extent) {
12591
0
                        properties.set(
12592
0
                            common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
12593
0
                            NN_NO_CHECK(extent));
12594
0
                    }
12595
0
                    auto geogCRS = dynamic_cast<GeographicCRS *>(crs);
12596
0
                    if (geogCRS) {
12597
0
                        const auto &cs = geogCRS->coordinateSystem();
12598
                        // Override with longitude latitude in degrees
12599
0
                        return GeographicCRS::create(
12600
0
                            properties, geogCRS->datum(),
12601
0
                            geogCRS->datumEnsemble(),
12602
0
                            cs->axisList().size() == 2
12603
0
                                ? EllipsoidalCS::createLongitudeLatitude(
12604
0
                                      UnitOfMeasure::DEGREE)
12605
0
                                : EllipsoidalCS::
12606
0
                                      createLongitudeLatitudeEllipsoidalHeight(
12607
0
                                          UnitOfMeasure::DEGREE,
12608
0
                                          cs->axisList()[2]->unit()));
12609
0
                    }
12610
0
                    auto projCRS = dynamic_cast<ProjectedCRS *>(crs);
12611
0
                    if (projCRS) {
12612
                        // Override with easting northing order
12613
0
                        const auto conv = projCRS->derivingConversion();
12614
0
                        if (conv->method()->getEPSGCode() !=
12615
0
                            EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
12616
0
                            return ProjectedCRS::create(
12617
0
                                properties, projCRS->baseCRS(), conv,
12618
0
                                CartesianCS::createEastingNorthing(
12619
0
                                    projCRS->coordinateSystem()
12620
0
                                        ->axisList()[0]
12621
0
                                        ->unit()));
12622
0
                        }
12623
0
                    }
12624
0
                    return obj;
12625
0
                }
12626
0
                auto projStringExportable =
12627
0
                    dynamic_cast<IPROJStringExportable *>(crs);
12628
0
                if (projStringExportable) {
12629
0
                    std::string expanded;
12630
0
                    if (!d->title_.empty()) {
12631
0
                        expanded = "title=";
12632
0
                        expanded +=
12633
0
                            pj_double_quote_string_param_if_needed(d->title_);
12634
0
                    }
12635
0
                    for (const auto &pair : d->steps_[0].paramValues) {
12636
0
                        if (!expanded.empty())
12637
0
                            expanded += ' ';
12638
0
                        expanded += '+';
12639
0
                        expanded += pair.key;
12640
0
                        if (!pair.value.empty()) {
12641
0
                            expanded += '=';
12642
0
                            expanded += pj_double_quote_string_param_if_needed(
12643
0
                                pair.value);
12644
0
                        }
12645
0
                    }
12646
0
                    expanded += ' ';
12647
0
                    expanded += projStringExportable->exportToPROJString(
12648
0
                        PROJStringFormatter::create().get());
12649
0
                    return createFromPROJString(expanded);
12650
0
                }
12651
0
            }
12652
0
        }
12653
12654
565
        paralist *init = pj_mkparam(("init=" + d->steps_[0].name).c_str());
12655
565
        if (!init) {
12656
0
            throw ParsingException("out of memory");
12657
0
        }
12658
565
        ctx->projStringParserCreateFromPROJStringRecursionCounter++;
12659
565
        paralist *list = pj_expand_init(ctx, init);
12660
565
        ctx->projStringParserCreateFromPROJStringRecursionCounter--;
12661
565
        if (!list) {
12662
133
            free(init);
12663
133
            throw ParsingException("cannot expand " + projString);
12664
133
        }
12665
432
        std::string expanded;
12666
432
        if (!d->title_.empty()) {
12667
141
            expanded =
12668
141
                "title=" + pj_double_quote_string_param_if_needed(d->title_);
12669
141
        }
12670
432
        bool first = true;
12671
432
        bool has_init_term = false;
12672
3.57k
        for (auto t = list; t;) {
12673
3.14k
            if (!expanded.empty()) {
12674
2.56k
                expanded += ' ';
12675
2.56k
            }
12676
3.14k
            if (first) {
12677
                // first parameter is the init= itself
12678
432
                first = false;
12679
2.71k
            } else if (starts_with(t->param, "init=")) {
12680
0
                has_init_term = true;
12681
2.71k
            } else {
12682
2.71k
                expanded += t->param;
12683
2.71k
            }
12684
12685
3.14k
            auto n = t->next;
12686
3.14k
            free(t);
12687
3.14k
            t = n;
12688
3.14k
        }
12689
8.16k
        for (const auto &pair : d->steps_[0].paramValues) {
12690
8.16k
            expanded += " +";
12691
8.16k
            expanded += pair.key;
12692
8.16k
            if (!pair.value.empty()) {
12693
5.68k
                expanded += '=';
12694
5.68k
                expanded += pj_double_quote_string_param_if_needed(pair.value);
12695
5.68k
            }
12696
8.16k
        }
12697
12698
432
        if (!has_init_term) {
12699
432
            return createFromPROJString(expanded);
12700
432
        }
12701
432
    }
12702
12703
12.7k
    int iFirstGeogStep = -1;
12704
12.7k
    int iSecondGeogStep = -1;
12705
12.7k
    int iProjStep = -1;
12706
12.7k
    int iFirstUnitConvert = -1;
12707
12.7k
    int iSecondUnitConvert = -1;
12708
12.7k
    int iFirstAxisSwap = -1;
12709
12.7k
    int iSecondAxisSwap = -1;
12710
12.7k
    bool unexpectedStructure = d->steps_.empty();
12711
26.1k
    for (int i = 0; i < static_cast<int>(d->steps_.size()); i++) {
12712
14.2k
        const auto &stepName = d->steps_[i].name;
12713
14.2k
        if (isGeographicStep(stepName)) {
12714
4.05k
            if (iFirstGeogStep < 0) {
12715
4.02k
                iFirstGeogStep = i;
12716
4.02k
            } else if (iSecondGeogStep < 0) {
12717
19
                iSecondGeogStep = i;
12718
19
            } else {
12719
3
                unexpectedStructure = true;
12720
3
                break;
12721
3
            }
12722
10.2k
        } else if (ci_equal(stepName, "unitconvert")) {
12723
198
            if (iFirstUnitConvert < 0) {
12724
158
                iFirstUnitConvert = i;
12725
158
            } else if (iSecondUnitConvert < 0) {
12726
39
                iSecondUnitConvert = i;
12727
39
            } else {
12728
1
                unexpectedStructure = true;
12729
1
                break;
12730
1
            }
12731
10.0k
        } else if (ci_equal(stepName, "axisswap")) {
12732
112
            if (iFirstAxisSwap < 0) {
12733
104
                iFirstAxisSwap = i;
12734
104
            } else if (iSecondAxisSwap < 0) {
12735
6
                iSecondAxisSwap = i;
12736
6
            } else {
12737
2
                unexpectedStructure = true;
12738
2
                break;
12739
2
            }
12740
9.92k
        } else if (isProjectedStep(stepName)) {
12741
9.25k
            if (iProjStep >= 0) {
12742
277
                unexpectedStructure = true;
12743
277
                break;
12744
277
            }
12745
8.97k
            iProjStep = i;
12746
8.97k
        } else {
12747
666
            unexpectedStructure = true;
12748
666
            break;
12749
666
        }
12750
14.2k
    }
12751
12752
12.7k
    if (!d->steps_.empty()) {
12753
        // CRS candidate
12754
12.5k
        if ((d->steps_.size() == 1 &&
12755
10.8k
             d->getParamValue(d->steps_[0], "type") != "crs") ||
12756
10.5k
            (d->steps_.size() > 1 && d->getGlobalParamValue("type") != "crs")) {
12757
2.47k
            unexpectedStructure = true;
12758
2.47k
        }
12759
12.5k
    }
12760
12761
12.7k
    struct Logger {
12762
12.7k
        std::string msg{};
12763
12764
        // cppcheck-suppress functionStatic
12765
12.7k
        void setMessage(const char *msgIn) noexcept {
12766
2.42k
            try {
12767
2.42k
                msg = msgIn;
12768
2.42k
            } catch (const std::exception &) {
12769
0
            }
12770
2.42k
        }
12771
12772
12.7k
        static void log(void *user_data, int level, const char *msg) {
12773
2.42k
            if (level == PJ_LOG_ERROR) {
12774
2.42k
                static_cast<Logger *>(user_data)->setMessage(msg);
12775
2.42k
            }
12776
2.42k
        }
12777
12.7k
    };
12778
12779
    // If the structure is not recognized, then try to instantiate the
12780
    // pipeline, and if successful, wrap it in a PROJBasedOperation
12781
12.7k
    Logger logger;
12782
12.7k
    bool valid;
12783
12784
12.7k
    auto pj_context = d->ctx_ ? d->ctx_ : proj_context_create();
12785
12.7k
    if (!pj_context) {
12786
0
        throw ParsingException("out of memory");
12787
0
    }
12788
12789
    // Backup error logger and level, and install temporary handler
12790
12.7k
    auto old_logger = pj_context->logger;
12791
12.7k
    auto old_logger_app_data = pj_context->logger_app_data;
12792
12.7k
    auto log_level = proj_log_level(pj_context, PJ_LOG_ERROR);
12793
12.7k
    proj_log_func(pj_context, &logger, Logger::log);
12794
12795
12.7k
    if (pj_context != d->ctx_) {
12796
135
        proj_context_use_proj4_init_rules(pj_context, d->usePROJ4InitRules_);
12797
135
    }
12798
12.7k
    pj_context->projStringParserCreateFromPROJStringRecursionCounter++;
12799
12.7k
    auto pj = pj_create_internal(
12800
12.7k
        pj_context, (projString.find("type=crs") != std::string::npos
12801
12.7k
                         ? projString + " +disable_grid_presence_check"
12802
12.7k
                         : projString)
12803
12.7k
                        .c_str());
12804
12.7k
    pj_context->projStringParserCreateFromPROJStringRecursionCounter--;
12805
12.7k
    valid = pj != nullptr;
12806
12807
    // Restore initial error logger and level
12808
12.7k
    proj_log_level(pj_context, log_level);
12809
12.7k
    pj_context->logger = old_logger;
12810
12.7k
    pj_context->logger_app_data = old_logger_app_data;
12811
12812
    // Remove parameters not understood by PROJ.
12813
12.7k
    if (valid && d->steps_.size() == 1) {
12814
9.74k
        std::vector<Step::KeyValue> newParamValues{};
12815
9.74k
        std::set<std::string> foundKeys;
12816
9.74k
        auto &step = d->steps_[0];
12817
12818
55.0k
        for (auto &kv : step.paramValues) {
12819
55.0k
            bool recognizedByPROJ = false;
12820
55.0k
            if (foundKeys.find(kv.key) != foundKeys.end()) {
12821
4.11k
                continue;
12822
4.11k
            }
12823
50.9k
            foundKeys.insert(kv.key);
12824
50.9k
            if ((step.name == "krovak" || step.name == "mod_krovak") &&
12825
3.34k
                kv.key == "alpha") {
12826
                // We recognize it in our CRS parsing code
12827
4
                recognizedByPROJ = true;
12828
50.9k
            } else {
12829
396k
                for (auto cur = pj->params; cur; cur = cur->next) {
12830
396k
                    const char *equal = strchr(cur->param, '=');
12831
396k
                    if (equal && static_cast<size_t>(equal - cur->param) ==
12832
208k
                                     kv.key.size()) {
12833
51.4k
                        if (memcmp(cur->param, kv.key.c_str(), kv.key.size()) ==
12834
51.4k
                            0) {
12835
29.1k
                            recognizedByPROJ = (cur->used == 1);
12836
29.1k
                            break;
12837
29.1k
                        }
12838
345k
                    } else if (strcmp(cur->param, kv.key.c_str()) == 0) {
12839
21.7k
                        recognizedByPROJ = (cur->used == 1);
12840
21.7k
                        break;
12841
21.7k
                    }
12842
396k
                }
12843
50.9k
            }
12844
50.9k
            if (!recognizedByPROJ && kv.key == "geoid_crs") {
12845
3.42k
                for (auto &pair : step.paramValues) {
12846
3.42k
                    if (ci_equal(pair.key, "geoidgrids")) {
12847
626
                        recognizedByPROJ = true;
12848
626
                        break;
12849
626
                    }
12850
3.42k
                }
12851
713
            }
12852
50.9k
            if (recognizedByPROJ) {
12853
15.4k
                newParamValues.emplace_back(kv);
12854
15.4k
            }
12855
50.9k
        }
12856
9.74k
        step.paramValues = std::move(newParamValues);
12857
12858
9.74k
        d->projString_.clear();
12859
9.74k
        if (!step.name.empty()) {
12860
9.73k
            d->projString_ += step.isInit ? "+init=" : "+proj=";
12861
9.73k
            d->projString_ += step.name;
12862
9.73k
        }
12863
15.4k
        for (const auto &paramValue : step.paramValues) {
12864
15.4k
            if (!d->projString_.empty()) {
12865
15.4k
                d->projString_ += ' ';
12866
15.4k
            }
12867
15.4k
            d->projString_ += '+';
12868
15.4k
            d->projString_ += paramValue.key;
12869
15.4k
            if (!paramValue.value.empty()) {
12870
12.8k
                d->projString_ += '=';
12871
12.8k
                d->projString_ +=
12872
12.8k
                    pj_double_quote_string_param_if_needed(paramValue.value);
12873
12.8k
            }
12874
15.4k
        }
12875
9.74k
    }
12876
12877
12.7k
    proj_destroy(pj);
12878
12879
12.7k
    if (!valid) {
12880
1.56k
        const int l_errno = proj_context_errno(pj_context);
12881
1.56k
        std::string msg("Error " + toString(l_errno) + " (" +
12882
1.56k
                        proj_errno_string(l_errno) + ")");
12883
1.56k
        if (!logger.msg.empty()) {
12884
1.49k
            msg += ": ";
12885
1.49k
            msg += logger.msg;
12886
1.49k
        }
12887
1.56k
        logger.msg = std::move(msg);
12888
1.56k
    }
12889
12890
12.7k
    if (pj_context != d->ctx_) {
12891
135
        proj_context_destroy(pj_context);
12892
135
    }
12893
12894
12.7k
    if (!valid) {
12895
1.56k
        throw ParsingException(logger.msg);
12896
1.56k
    }
12897
12898
11.2k
    if (isGeocentricCRS) {
12899
        // First run is dry run to mark all recognized/unrecognized tokens
12900
2.44k
        for (int iter = 0; iter < 2; iter++) {
12901
2.37k
            auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12902
2.37k
                0, d->buildGeocentricCRS(0, (d->steps_.size() == 2 &&
12903
29
                                             d->steps_[1].name == "unitconvert")
12904
2.37k
                                                ? 1
12905
2.37k
                                                : -1));
12906
2.37k
            if (iter == 1) {
12907
1.15k
                return nn_static_pointer_cast<BaseObject>(obj);
12908
1.15k
            }
12909
2.37k
        }
12910
1.22k
    }
12911
12912
10.0k
    if (isTopocentricCRS) {
12913
        // First run is dry run to mark all recognized/unrecognized tokens
12914
8
        for (int iter = 0; iter < 2; iter++) {
12915
8
            auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12916
8
                0,
12917
8
                d->buildProjectedCRS(0, d->buildGeocentricCRS(0, -1), -1, -1));
12918
8
            if (iter == 1) {
12919
4
                return nn_static_pointer_cast<BaseObject>(obj);
12920
4
            }
12921
8
        }
12922
4
    }
12923
12924
10.0k
    if (!unexpectedStructure) {
12925
8.30k
        if (iFirstGeogStep == 0 && !d->steps_[iFirstGeogStep].inverted &&
12926
3.73k
            iSecondGeogStep < 0 && iProjStep < 0 &&
12927
2.70k
            (iFirstUnitConvert < 0 || iSecondUnitConvert < 0) &&
12928
2.70k
            (iFirstAxisSwap < 0 || iSecondAxisSwap < 0)) {
12929
            // First run is dry run to mark all recognized/unrecognized tokens
12930
5.41k
            for (int iter = 0; iter < 2; iter++) {
12931
5.31k
                auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12932
5.31k
                    0, d->buildGeodeticCRS(iFirstGeogStep, iFirstUnitConvert,
12933
5.31k
                                           iFirstAxisSwap, false));
12934
5.31k
                if (iter == 1) {
12935
2.60k
                    return nn_static_pointer_cast<BaseObject>(obj);
12936
2.60k
                }
12937
5.31k
            }
12938
2.70k
        }
12939
5.70k
        if (iProjStep >= 0 && !d->steps_[iProjStep].inverted &&
12940
5.54k
            (iFirstGeogStep < 0 || iFirstGeogStep + 1 == iProjStep) &&
12941
5.54k
            iSecondGeogStep < 0) {
12942
5.53k
            if (iFirstGeogStep < 0)
12943
4.51k
                iFirstGeogStep = iProjStep;
12944
            // First run is dry run to mark all recognized/unrecognized tokens
12945
11.0k
            for (int iter = 0; iter < 2; iter++) {
12946
10.5k
                auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12947
10.5k
                    iProjStep,
12948
10.5k
                    d->buildProjectedCRS(
12949
10.5k
                        iProjStep,
12950
10.5k
                        d->buildGeodeticCRS(iFirstGeogStep,
12951
10.5k
                                            iFirstUnitConvert < iFirstGeogStep
12952
10.5k
                                                ? iFirstUnitConvert
12953
10.5k
                                                : -1,
12954
10.5k
                                            iFirstAxisSwap < iFirstGeogStep
12955
10.5k
                                                ? iFirstAxisSwap
12956
10.5k
                                                : -1,
12957
10.5k
                                            true),
12958
10.5k
                        iFirstUnitConvert < iFirstGeogStep ? iSecondUnitConvert
12959
10.5k
                                                           : iFirstUnitConvert,
12960
10.5k
                        iFirstAxisSwap < iFirstGeogStep ? iSecondAxisSwap
12961
10.5k
                                                        : iFirstAxisSwap));
12962
10.5k
                if (iter == 1) {
12963
4.96k
                    return nn_static_pointer_cast<BaseObject>(obj);
12964
4.96k
                }
12965
10.5k
            }
12966
5.53k
        }
12967
5.70k
    }
12968
12969
2.48k
    auto props = PropertyMap();
12970
2.48k
    if (!d->title_.empty()) {
12971
65
        props.set(IdentifiedObject::NAME_KEY, d->title_);
12972
65
    }
12973
2.48k
    return operation::SingleOperation::createPROJBased(props, projString,
12974
2.48k
                                                       nullptr, nullptr, {});
12975
10.0k
}
12976
12977
// ---------------------------------------------------------------------------
12978
12979
//! @cond Doxygen_Suppress
12980
struct JSONFormatter::Private {
12981
    CPLJSonStreamingWriter writer_{nullptr, nullptr};
12982
    DatabaseContextPtr dbContext_{};
12983
12984
    std::vector<bool> stackHasId_{false};
12985
    std::vector<bool> outputIdStack_{true};
12986
    bool allowIDInImmediateChild_ = false;
12987
    bool omitTypeInImmediateChild_ = false;
12988
    bool abridgedTransformation_ = false;
12989
    bool abridgedTransformationWriteSourceCRS_ = false;
12990
    std::string schema_ = PROJJSON_DEFAULT_VERSION;
12991
12992
    // cppcheck-suppress functionStatic
12993
0
    void pushOutputId(bool outputIdIn) { outputIdStack_.push_back(outputIdIn); }
12994
12995
    // cppcheck-suppress functionStatic
12996
0
    void popOutputId() { outputIdStack_.pop_back(); }
12997
};
12998
//! @endcond
12999
13000
// ---------------------------------------------------------------------------
13001
13002
/** \brief Constructs a new formatter.
13003
 *
13004
 * A formatter can be used only once (its internal state is mutated)
13005
 *
13006
 * @return new formatter.
13007
 */
13008
JSONFormatterNNPtr JSONFormatter::create( // cppcheck-suppress passedByValue
13009
0
    DatabaseContextPtr dbContext) {
13010
0
    auto ret = NN_NO_CHECK(JSONFormatter::make_unique<JSONFormatter>());
13011
0
    ret->d->dbContext_ = std::move(dbContext);
13012
0
    return ret;
13013
0
}
13014
13015
// ---------------------------------------------------------------------------
13016
13017
/** \brief Whether to use multi line output or not. */
13018
0
JSONFormatter &JSONFormatter::setMultiLine(bool multiLine) noexcept {
13019
0
    d->writer_.SetPrettyFormatting(multiLine);
13020
0
    return *this;
13021
0
}
13022
13023
// ---------------------------------------------------------------------------
13024
13025
/** \brief Set number of spaces for each indentation level (defaults to 4).
13026
 */
13027
0
JSONFormatter &JSONFormatter::setIndentationWidth(int width) noexcept {
13028
0
    d->writer_.SetIndentationSize(width);
13029
0
    return *this;
13030
0
}
13031
13032
// ---------------------------------------------------------------------------
13033
13034
/** \brief Set the value of the "$schema" key in the top level object.
13035
 *
13036
 * If set to empty string, it will not be written.
13037
 */
13038
0
JSONFormatter &JSONFormatter::setSchema(const std::string &schema) noexcept {
13039
0
    d->schema_ = schema;
13040
0
    return *this;
13041
0
}
13042
13043
// ---------------------------------------------------------------------------
13044
13045
//! @cond Doxygen_Suppress
13046
13047
0
JSONFormatter::JSONFormatter() : d(std::make_unique<Private>()) {}
13048
13049
// ---------------------------------------------------------------------------
13050
13051
0
JSONFormatter::~JSONFormatter() = default;
13052
13053
// ---------------------------------------------------------------------------
13054
13055
0
CPLJSonStreamingWriter *JSONFormatter::writer() const { return &(d->writer_); }
13056
13057
// ---------------------------------------------------------------------------
13058
13059
0
const DatabaseContextPtr &JSONFormatter::databaseContext() const {
13060
0
    return d->dbContext_;
13061
0
}
13062
13063
// ---------------------------------------------------------------------------
13064
13065
0
bool JSONFormatter::outputId() const { return d->outputIdStack_.back(); }
13066
13067
// ---------------------------------------------------------------------------
13068
13069
0
bool JSONFormatter::outputUsage(bool calledBeforeObjectContext) const {
13070
0
    return outputId() &&
13071
0
           d->outputIdStack_.size() == (calledBeforeObjectContext ? 1U : 2U);
13072
0
}
13073
13074
// ---------------------------------------------------------------------------
13075
13076
0
void JSONFormatter::setAllowIDInImmediateChild() {
13077
0
    d->allowIDInImmediateChild_ = true;
13078
0
}
13079
13080
// ---------------------------------------------------------------------------
13081
13082
0
void JSONFormatter::setOmitTypeInImmediateChild() {
13083
0
    d->omitTypeInImmediateChild_ = true;
13084
0
}
13085
13086
// ---------------------------------------------------------------------------
13087
13088
JSONFormatter::ObjectContext::ObjectContext(JSONFormatter &formatter,
13089
                                            const char *objectType, bool hasId)
13090
0
    : m_formatter(formatter) {
13091
0
    m_formatter.d->writer_.StartObj();
13092
0
    if (m_formatter.d->outputIdStack_.size() == 1 &&
13093
0
        !m_formatter.d->schema_.empty()) {
13094
0
        m_formatter.d->writer_.AddObjKey("$schema");
13095
0
        m_formatter.d->writer_.Add(m_formatter.d->schema_);
13096
0
    }
13097
0
    if (objectType && !m_formatter.d->omitTypeInImmediateChild_) {
13098
0
        m_formatter.d->writer_.AddObjKey("type");
13099
0
        m_formatter.d->writer_.Add(objectType);
13100
0
    }
13101
0
    m_formatter.d->omitTypeInImmediateChild_ = false;
13102
    // All intermediate nodes shouldn't have ID if a parent has an ID
13103
    // unless explicitly enabled.
13104
0
    if (m_formatter.d->allowIDInImmediateChild_) {
13105
0
        m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0]);
13106
0
        m_formatter.d->allowIDInImmediateChild_ = false;
13107
0
    } else {
13108
0
        m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0] &&
13109
0
                                    !m_formatter.d->stackHasId_.back());
13110
0
    }
13111
13112
0
    m_formatter.d->stackHasId_.push_back(hasId ||
13113
0
                                         m_formatter.d->stackHasId_.back());
13114
0
}
13115
13116
// ---------------------------------------------------------------------------
13117
13118
0
JSONFormatter::ObjectContext::~ObjectContext() {
13119
0
    m_formatter.d->writer_.EndObj();
13120
0
    m_formatter.d->stackHasId_.pop_back();
13121
0
    m_formatter.d->popOutputId();
13122
0
}
13123
13124
// ---------------------------------------------------------------------------
13125
13126
0
void JSONFormatter::setAbridgedTransformation(bool outputIn) {
13127
0
    d->abridgedTransformation_ = outputIn;
13128
0
}
13129
13130
// ---------------------------------------------------------------------------
13131
13132
0
bool JSONFormatter::abridgedTransformation() const {
13133
0
    return d->abridgedTransformation_;
13134
0
}
13135
13136
// ---------------------------------------------------------------------------
13137
13138
0
void JSONFormatter::setAbridgedTransformationWriteSourceCRS(bool writeCRS) {
13139
0
    d->abridgedTransformationWriteSourceCRS_ = writeCRS;
13140
0
}
13141
13142
// ---------------------------------------------------------------------------
13143
13144
0
bool JSONFormatter::abridgedTransformationWriteSourceCRS() const {
13145
0
    return d->abridgedTransformationWriteSourceCRS_;
13146
0
}
13147
13148
//! @endcond
13149
13150
// ---------------------------------------------------------------------------
13151
13152
/** \brief Return the serialized JSON.
13153
 */
13154
0
const std::string &JSONFormatter::toString() const {
13155
0
    return d->writer_.GetString();
13156
0
}
13157
13158
// ---------------------------------------------------------------------------
13159
13160
//! @cond Doxygen_Suppress
13161
11.7M
IJSONExportable::~IJSONExportable() = default;
13162
13163
// ---------------------------------------------------------------------------
13164
13165
0
std::string IJSONExportable::exportToJSON(JSONFormatter *formatter) const {
13166
0
    _exportToJSON(formatter);
13167
0
    return formatter->toString();
13168
0
}
13169
13170
//! @endcond
13171
13172
} // namespace io
13173
NS_PROJ_END