Coverage Report

Created: 2025-11-16 06:25

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
3.41M
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
39.5k
static inline std::string normalizeExponent(const std::string &in) {
581
39.5k
    return in;
582
39.5k
}
583
#endif
584
585
39.5k
static inline std::string normalizeSerializedString(const std::string &in) {
586
39.5k
    auto ret(normalizeExponent(in));
587
39.5k
    return ret;
588
39.5k
}
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
499k
static inline bool isNull(const WKTNodeNNPtr &node) {
901
499k
    return &node == &null_node;
902
499k
}
903
904
struct WKTNode::Private {
905
    std::string value_{};
906
    std::vector<WKTNodeNNPtr> children_{};
907
908
295k
    explicit Private(const std::string &valueIn) : value_(valueIn) {}
909
910
    // cppcheck-suppress functionStatic
911
3.57M
    inline const std::string &value() PROJ_PURE_DEFN { return value_; }
912
913
    // cppcheck-suppress functionStatic
914
175k
    inline const std::vector<WKTNodeNNPtr> &children() PROJ_PURE_DEFN {
915
175k
        return children_;
916
175k
    }
917
918
    // cppcheck-suppress functionStatic
919
36.5k
    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
3.79M
#define GP() getPrivate()
945
946
// ---------------------------------------------------------------------------
947
948
const WKTNodeNNPtr &
949
WKTNode::Private::lookForChild(const std::string &childName,
950
442
                               int occurrence) const noexcept {
951
442
    int occCount = 0;
952
3.33k
    for (const auto &child : children_) {
953
3.33k
        if (ci_equal(child->GP()->value(), childName)) {
954
691
            if (occurrence == occCount) {
955
442
                return child;
956
442
            }
957
249
            occCount++;
958
249
        }
959
3.33k
    }
960
0
    return null_node;
961
442
}
962
963
const WKTNodeNNPtr &
964
483k
WKTNode::Private::lookForChild(const std::string &name) const noexcept {
965
2.72M
    for (const auto &child : children_) {
966
2.72M
        const auto &v = child->GP()->value();
967
2.72M
        if (ci_equal(v, name)) {
968
5.61k
            return child;
969
5.61k
        }
970
2.72M
    }
971
478k
    return null_node;
972
483k
}
973
974
const WKTNodeNNPtr &
975
WKTNode::Private::lookForChild(const std::string &name,
976
12.4k
                               const std::string &name2) const noexcept {
977
53.6k
    for (const auto &child : children_) {
978
53.6k
        const auto &v = child->GP()->value();
979
53.6k
        if (ci_equal(v, name) || ci_equal(v, name2)) {
980
5.71k
            return child;
981
5.71k
        }
982
53.6k
    }
983
6.72k
    return null_node;
984
12.4k
}
985
986
const WKTNodeNNPtr &
987
WKTNode::Private::lookForChild(const std::string &name,
988
                               const std::string &name2,
989
4.27k
                               const std::string &name3) const noexcept {
990
18.4k
    for (const auto &child : children_) {
991
18.4k
        const auto &v = child->GP()->value();
992
18.4k
        if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3)) {
993
4.09k
            return child;
994
4.09k
        }
995
18.4k
    }
996
182
    return null_node;
997
4.27k
}
998
999
const WKTNodeNNPtr &WKTNode::Private::lookForChild(
1000
    const std::string &name, const std::string &name2, const std::string &name3,
1001
2.30k
    const std::string &name4) const noexcept {
1002
5.56k
    for (const auto &child : children_) {
1003
5.56k
        const auto &v = child->GP()->value();
1004
5.56k
        if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3) ||
1005
4.45k
            ci_equal(v, name4)) {
1006
2.07k
            return child;
1007
2.07k
        }
1008
5.56k
    }
1009
227
    return null_node;
1010
2.30k
}
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
295k
    : d(std::make_unique<Private>(valueIn)) {}
1022
1023
// ---------------------------------------------------------------------------
1024
1025
//! @cond Doxygen_Suppress
1026
295k
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
285k
void WKTNode::addChild(WKTNodeNNPtr &&child) {
1036
285k
    d->children_.push_back(std::move(child));
1037
285k
}
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
3.72k
                                        int occurrence) const noexcept {
1049
3.72k
    int occCount = 0;
1050
45.2k
    for (const auto &child : d->children_) {
1051
45.2k
        if (ci_equal(child->GP()->value(), childName)) {
1052
223
            if (occurrence == occCount) {
1053
223
                return child;
1054
223
            }
1055
0
            occCount++;
1056
0
        }
1057
45.2k
    }
1058
3.49k
    return null_node;
1059
3.72k
}
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
11.0k
int WKTNode::countChildrenOfName(const std::string &childName) const noexcept {
1069
11.0k
    int occCount = 0;
1070
83.0k
    for (const auto &child : d->children_) {
1071
83.0k
        if (ci_equal(child->GP()->value(), childName)) {
1072
475
            occCount++;
1073
475
        }
1074
83.0k
    }
1075
11.0k
    return occCount;
1076
11.0k
}
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
994k
static size_t skipSpace(const std::string &str, size_t start) {
1096
994k
    size_t i = start;
1097
1.00M
    while (i < str.size() && ::isspace(static_cast<unsigned char>(str[i]))) {
1098
8.86k
        ++i;
1099
8.86k
    }
1100
994k
    return i;
1101
994k
}
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
295k
                                 int recLevel, size_t &indexEnd) {
1114
295k
    if (recLevel == 16) {
1115
1
        throw ParsingException("too many nesting levels");
1116
1
    }
1117
295k
    std::string value;
1118
295k
    size_t i = skipSpace(wkt, indexStart);
1119
295k
    if (i == wkt.size()) {
1120
0
        throw ParsingException("whitespace only string");
1121
0
    }
1122
295k
    std::string closingStringMarker;
1123
295k
    bool inString = false;
1124
1125
3.34M
    for (; i < wkt.size() &&
1126
3.34M
           (inString ||
1127
3.01M
            (wkt[i] != '[' && wkt[i] != '(' && wkt[i] != ',' && wkt[i] != ']' &&
1128
2.79M
             wkt[i] != ')' && !::isspace(static_cast<unsigned char>(wkt[i]))));
1129
3.04M
         ++i) {
1130
3.04M
        if (wkt[i] == '"') {
1131
16.1k
            if (!inString) {
1132
7.65k
                inString = true;
1133
7.65k
                closingStringMarker = "\"";
1134
8.51k
            } else if (closingStringMarker == "\"") {
1135
8.46k
                if (i + 1 < wkt.size() && wkt[i + 1] == '"') {
1136
834
                    i++;
1137
7.63k
                } else {
1138
7.63k
                    inString = false;
1139
7.63k
                    closingStringMarker.clear();
1140
7.63k
                }
1141
8.46k
            }
1142
3.02M
        } else if (i + 3 <= wkt.size() &&
1143
3.02M
                   wkt.substr(i, 3) == startPrintedQuote) {
1144
45
            if (!inString) {
1145
37
                inString = true;
1146
37
                closingStringMarker = endPrintedQuote;
1147
37
                value += '"';
1148
37
                i += 2;
1149
37
                continue;
1150
37
            }
1151
3.02M
        } else if (i + 3 <= wkt.size() &&
1152
3.02M
                   closingStringMarker == endPrintedQuote &&
1153
4.04k
                   wkt.substr(i, 3) == endPrintedQuote) {
1154
18
            inString = false;
1155
18
            closingStringMarker.clear();
1156
18
            value += '"';
1157
18
            i += 2;
1158
18
            continue;
1159
18
        }
1160
3.04M
        value += wkt[i];
1161
3.04M
    }
1162
295k
    i = skipSpace(wkt, i);
1163
295k
    if (i == wkt.size()) {
1164
297
        if (indexStart == 0) {
1165
0
            throw ParsingException("missing [");
1166
297
        } else {
1167
297
            throw ParsingException("missing , or ]");
1168
297
        }
1169
297
    }
1170
1171
295k
    auto node = NN_NO_CHECK(std::make_unique<WKTNode>(value));
1172
1173
295k
    if (indexStart > 0) {
1174
286k
        if (wkt[i] == ',') {
1175
121k
            indexEnd = i + 1;
1176
121k
            return node;
1177
121k
        }
1178
164k
        if (wkt[i] == ']' || wkt[i] == ')') {
1179
79.8k
            indexEnd = i;
1180
79.8k
            return node;
1181
79.8k
        }
1182
164k
    }
1183
93.7k
    if (wkt[i] != '[' && wkt[i] != '(') {
1184
42
        throw ParsingException("missing [");
1185
42
    }
1186
93.7k
    ++i; // skip [
1187
93.7k
    i = skipSpace(wkt, i);
1188
380k
    while (i < wkt.size() && wkt[i] != ']' && wkt[i] != ')') {
1189
286k
        size_t indexEndChild;
1190
286k
        node->addChild(createFrom(wkt, i, recLevel + 1, indexEndChild));
1191
286k
        assert(indexEndChild > i);
1192
286k
        i = indexEndChild;
1193
286k
        i = skipSpace(wkt, i);
1194
286k
        if (i < wkt.size() && wkt[i] == ',') {
1195
23.0k
            ++i;
1196
23.0k
            i = skipSpace(wkt, i);
1197
23.0k
        }
1198
286k
    }
1199
93.7k
    if (i == wkt.size() || (wkt[i] != ']' && wkt[i] != ')')) {
1200
55
        throw ParsingException("missing ]");
1201
55
    }
1202
93.6k
    indexEnd = i + 1;
1203
93.6k
    return node;
1204
93.7k
}
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
28.4k
                        const std::string &rhs) const noexcept {
1262
28.4k
            return ci_less(lhs, rhs);
1263
28.4k
        }
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
8.97k
    Private() = default;
1281
8.97k
    ~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
8.97k
WKTParser::WKTParser() : d(std::make_unique<Private>()) {}
1475
1476
// ---------------------------------------------------------------------------
1477
1478
//! @cond Doxygen_Suppress
1479
8.97k
WKTParser::~WKTParser() = default;
1480
//! @endcond
1481
1482
// ---------------------------------------------------------------------------
1483
1484
/** \brief Set whether parsing should be done in strict mode.
1485
 */
1486
8.97k
WKTParser &WKTParser::setStrict(bool strict) {
1487
8.97k
    d->strict_ = strict;
1488
8.97k
    return *this;
1489
8.97k
}
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
8.74k
void WKTParser::Private::emitRecoverableWarning(const std::string &errorMsg) {
1533
8.74k
    if (strict_) {
1534
0
        throw ParsingException(errorMsg);
1535
8.74k
    } else {
1536
8.74k
        warningList_.push_back(errorMsg);
1537
8.74k
    }
1538
8.74k
}
1539
//! @endcond
1540
1541
// ---------------------------------------------------------------------------
1542
1543
//! @cond Doxygen_Suppress
1544
6.83k
void WKTParser::Private::emitGrammarError(const std::string &errorMsg) {
1545
6.83k
    if (strict_) {
1546
0
        throw ParsingException(errorMsg);
1547
6.83k
    } else {
1548
6.83k
        grammarErrorList_.push_back(errorMsg);
1549
6.83k
    }
1550
6.83k
}
1551
//! @endcond
1552
1553
// ---------------------------------------------------------------------------
1554
1555
25.9k
static double asDouble(const std::string &val) { return c_locale_stod(val); }
1556
1557
// ---------------------------------------------------------------------------
1558
1559
151
PROJ_NO_RETURN static void ThrowNotEnoughChildren(const std::string &nodeName) {
1560
151
    throw ParsingException(
1561
151
        concat("not enough children in ", nodeName, " node"));
1562
151
}
1563
1564
// ---------------------------------------------------------------------------
1565
1566
PROJ_NO_RETURN static void
1567
30
ThrowNotRequiredNumberOfChildren(const std::string &nodeName) {
1568
30
    throw ParsingException(
1569
30
        concat("not required number of children in ", nodeName, " node"));
1570
30
}
1571
1572
// ---------------------------------------------------------------------------
1573
1574
228
PROJ_NO_RETURN static void ThrowMissing(const std::string &nodeName) {
1575
228
    throw ParsingException(concat("missing ", nodeName, " node"));
1576
228
}
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
71
                                     const std::exception &e) {
1589
71
    std::string res(funcName);
1590
71
    res += ": ";
1591
71
    res += e.what();
1592
71
    return ParsingException(res);
1593
71
}
1594
1595
// ---------------------------------------------------------------------------
1596
1597
//! @cond Doxygen_Suppress
1598
80.5k
std::string WKTParser::Private::stripQuotes(const WKTNodeNNPtr &node) {
1599
80.5k
    return ::stripQuotes(node->GP()->value());
1600
80.5k
}
1601
1602
// ---------------------------------------------------------------------------
1603
1604
19.8k
double WKTParser::Private::asDouble(const WKTNodeNNPtr &node) {
1605
19.8k
    return io::asDouble(node->GP()->value());
1606
19.8k
}
1607
1608
// ---------------------------------------------------------------------------
1609
1610
IdentifierPtr WKTParser::Private::buildId(const WKTNodeNNPtr &parentNode,
1611
                                          const WKTNodeNNPtr &node,
1612
8.89k
                                          bool tolerant, bool removeInverseOf) {
1613
8.89k
    const auto *nodeP = node->GP();
1614
8.89k
    const auto &nodeChildren = nodeP->children();
1615
8.89k
    if (nodeChildren.size() >= 2) {
1616
8.02k
        auto codeSpace = stripQuotes(nodeChildren[0]);
1617
8.02k
        if (removeInverseOf && starts_with(codeSpace, "INVERSE(") &&
1618
0
            codeSpace.back() == ')') {
1619
0
            codeSpace = codeSpace.substr(strlen("INVERSE("));
1620
0
            codeSpace.resize(codeSpace.size() - 1);
1621
0
        }
1622
1623
8.02k
        PropertyMap propertiesId;
1624
8.02k
        if (nodeChildren.size() >= 3 &&
1625
2.90k
            nodeChildren[2]->GP()->childrenSize() == 0) {
1626
2.34k
            std::string version = stripQuotes(nodeChildren[2]);
1627
1628
            // IAU + 2015 -> IAU_2015
1629
2.34k
            if (dbContext_) {
1630
2.15k
                std::string codeSpaceOut;
1631
2.15k
                if (dbContext_->getVersionedAuthority(codeSpace, version,
1632
2.15k
                                                      codeSpaceOut)) {
1633
0
                    codeSpace = std::move(codeSpaceOut);
1634
0
                    version.clear();
1635
0
                }
1636
2.15k
            }
1637
1638
2.34k
            if (!version.empty()) {
1639
2.25k
                propertiesId.set(Identifier::VERSION_KEY, version);
1640
2.25k
            }
1641
2.34k
        }
1642
1643
8.02k
        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
8.02k
        const auto &parentNodeKeyword(parentNode->GP()->value());
1648
8.02k
        if (parentNodeKeyword == WKTConstants::CONVERSION &&
1649
531
            codeSpace == Identifier::EPSG) {
1650
99
            const auto &parentNodeChildren = parentNode->GP()->children();
1651
99
            if (!parentNodeChildren.empty()) {
1652
99
                const auto parentNodeName(stripQuotes(parentNodeChildren[0]));
1653
99
                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
99
            }
1662
99
        }
1663
1664
8.02k
        auto &citationNode = nodeP->lookForChild(WKTConstants::CITATION);
1665
8.02k
        auto &uriNode = nodeP->lookForChild(WKTConstants::URI);
1666
1667
8.02k
        propertiesId.set(Identifier::CODESPACE_KEY, codeSpace);
1668
8.02k
        bool authoritySet = false;
1669
8.02k
        /*if (!isNull(citationNode))*/ {
1670
8.02k
            const auto *citationNodeP = citationNode->GP();
1671
8.02k
            if (citationNodeP->childrenSize() == 1) {
1672
0
                authoritySet = true;
1673
0
                propertiesId.set(Identifier::AUTHORITY_KEY,
1674
0
                                 stripQuotes(citationNodeP->children()[0]));
1675
0
            }
1676
8.02k
        }
1677
8.02k
        if (!authoritySet) {
1678
8.02k
            propertiesId.set(Identifier::AUTHORITY_KEY, codeSpace);
1679
8.02k
        }
1680
8.02k
        /*if (!isNull(uriNode))*/ {
1681
8.02k
            const auto *uriNodeP = uriNode->GP();
1682
8.02k
            if (uriNodeP->childrenSize() == 1) {
1683
0
                propertiesId.set(Identifier::URI_KEY,
1684
0
                                 stripQuotes(uriNodeP->children()[0]));
1685
0
            }
1686
8.02k
        }
1687
8.02k
        return Identifier::create(code, propertiesId);
1688
8.02k
    } else if (strict_ || !tolerant) {
1689
5
        ThrowNotEnoughChildren(nodeP->value());
1690
868
    } else {
1691
868
        std::string msg("not enough children in ");
1692
868
        msg += nodeP->value();
1693
868
        msg += " node";
1694
868
        warningList_.emplace_back(std::move(msg));
1695
868
    }
1696
868
    return nullptr;
1697
8.89k
}
1698
1699
// ---------------------------------------------------------------------------
1700
1701
PropertyMap &WKTParser::Private::buildProperties(const WKTNodeNNPtr &node,
1702
                                                 bool removeInverseOf,
1703
34.6k
                                                 bool hasName) {
1704
1705
34.6k
    if (properties_.size() >= MAX_PROPERTY_SIZE) {
1706
0
        throw ParsingException("MAX_PROPERTY_SIZE reached");
1707
0
    }
1708
34.6k
    properties_.push_back(std::make_unique<PropertyMap>());
1709
34.6k
    auto properties = properties_.back().get();
1710
1711
34.6k
    std::string authNameFromAlias;
1712
34.6k
    std::string codeFromAlias;
1713
34.6k
    const auto *nodeP = node->GP();
1714
34.6k
    const auto &nodeChildren = nodeP->children();
1715
1716
34.6k
    auto identifiers = ArrayOfBaseObject::create();
1717
173k
    for (const auto &subNode : nodeChildren) {
1718
173k
        const auto &subNodeName(subNode->GP()->value());
1719
173k
        if (ci_equal(subNodeName, WKTConstants::ID) ||
1720
166k
            ci_equal(subNodeName, WKTConstants::AUTHORITY)) {
1721
7.52k
            auto id = buildId(node, subNode, true, removeInverseOf);
1722
7.52k
            if (id) {
1723
6.65k
                identifiers->add(NN_NO_CHECK(id));
1724
6.65k
            }
1725
7.52k
        }
1726
173k
    }
1727
1728
34.6k
    if (hasName && !nodeChildren.empty()) {
1729
33.2k
        const auto &nodeName(nodeP->value());
1730
33.2k
        auto name(stripQuotes(nodeChildren[0]));
1731
33.2k
        if (removeInverseOf && starts_with(name, "Inverse of ")) {
1732
0
            name = name.substr(strlen("Inverse of "));
1733
0
        }
1734
1735
33.2k
        if (ends_with(name, " (deprecated)")) {
1736
33
            name.resize(name.size() - strlen(" (deprecated)"));
1737
33
            properties->set(common::IdentifiedObject::DEPRECATED_KEY, true);
1738
33
        }
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
33.2k
        if (identifiers->empty()) {
1746
30.9k
            const auto pos = name.find(" (EPSG ID ");
1747
30.9k
            if (pos != std::string::npos && name.back() == ')') {
1748
38
                const auto code =
1749
38
                    name.substr(pos + strlen(" (EPSG ID "),
1750
38
                                name.size() - 1 - pos - strlen(" (EPSG ID "));
1751
38
                name.resize(pos);
1752
1753
38
                PropertyMap propertiesId;
1754
38
                propertiesId.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
1755
38
                propertiesId.set(Identifier::AUTHORITY_KEY, Identifier::EPSG);
1756
38
                identifiers->add(Identifier::create(code, propertiesId));
1757
38
            }
1758
30.9k
        }
1759
1760
33.2k
        const char *tableNameForAlias = nullptr;
1761
33.2k
        if (ci_equal(nodeName, WKTConstants::GEOGCS)) {
1762
3.06k
            if (starts_with(name, "GCS_")) {
1763
386
                esriStyle_ = true;
1764
386
                if (name == "GCS_WGS_1984") {
1765
35
                    name = "WGS 84";
1766
351
                } else if (name == "GCS_unknown") {
1767
0
                    name = "unknown";
1768
351
                } else {
1769
351
                    tableNameForAlias = "geodetic_crs";
1770
351
                }
1771
386
            }
1772
30.1k
        } else if (esriStyle_ && ci_equal(nodeName, WKTConstants::SPHEROID)) {
1773
65
            if (name == "WGS_1984") {
1774
35
                name = "WGS 84";
1775
35
                authNameFromAlias = Identifier::EPSG;
1776
35
                codeFromAlias = "7030";
1777
35
            } else {
1778
30
                tableNameForAlias = "ellipsoid";
1779
30
            }
1780
65
        }
1781
1782
33.2k
        if (dbContext_ && tableNameForAlias) {
1783
213
            std::string outTableName;
1784
213
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
1785
213
                                                        std::string());
1786
213
            auto officialName = authFactory->getOfficialNameFromAlias(
1787
213
                name, tableNameForAlias, "ESRI", false, outTableName,
1788
213
                authNameFromAlias, codeFromAlias);
1789
213
            if (!officialName.empty()) {
1790
28
                name = std::move(officialName);
1791
1792
                // Clearing authority for geodetic_crs because of
1793
                // potential axis order mismatch.
1794
28
                if (strcmp(tableNameForAlias, "geodetic_crs") == 0) {
1795
17
                    authNameFromAlias.clear();
1796
17
                    codeFromAlias.clear();
1797
17
                }
1798
28
            }
1799
213
        }
1800
1801
33.2k
        properties->set(IdentifiedObject::NAME_KEY, name);
1802
33.2k
    }
1803
1804
34.6k
    if (identifiers->empty() && !authNameFromAlias.empty()) {
1805
46
        identifiers->add(Identifier::create(
1806
46
            codeFromAlias,
1807
46
            PropertyMap()
1808
46
                .set(Identifier::CODESPACE_KEY, authNameFromAlias)
1809
46
                .set(Identifier::AUTHORITY_KEY, authNameFromAlias)));
1810
46
    }
1811
34.6k
    if (!identifiers->empty()) {
1812
2.42k
        properties->set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
1813
2.42k
    }
1814
1815
34.6k
    auto &remarkNode = nodeP->lookForChild(WKTConstants::REMARK);
1816
34.6k
    if (!isNull(remarkNode)) {
1817
381
        const auto &remarkChildren = remarkNode->GP()->children();
1818
381
        if (remarkChildren.size() == 1) {
1819
377
            properties->set(IdentifiedObject::REMARKS_KEY,
1820
377
                            stripQuotes(remarkChildren[0]));
1821
377
        } else {
1822
4
            ThrowNotRequiredNumberOfChildren(remarkNode->GP()->value());
1823
4
        }
1824
381
    }
1825
1826
34.6k
    ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create();
1827
173k
    for (const auto &subNode : nodeP->children()) {
1828
173k
        const auto &subNodeName(subNode->GP()->value());
1829
173k
        if (ci_equal(subNodeName, WKTConstants::USAGE)) {
1830
233
            auto objectDomain = buildObjectDomain(subNode);
1831
233
            if (!objectDomain) {
1832
6
                throw ParsingException(
1833
6
                    concat("missing children in ", subNodeName, " node"));
1834
6
            }
1835
227
            array->add(NN_NO_CHECK(objectDomain));
1836
227
        }
1837
173k
    }
1838
34.6k
    if (!array->empty()) {
1839
95
        properties->set(ObjectUsage::OBJECT_DOMAIN_KEY, array);
1840
34.5k
    } else {
1841
34.5k
        auto objectDomain = buildObjectDomain(node);
1842
34.5k
        if (objectDomain) {
1843
758
            properties->set(ObjectUsage::OBJECT_DOMAIN_KEY,
1844
758
                            NN_NO_CHECK(objectDomain));
1845
758
        }
1846
34.5k
    }
1847
1848
34.6k
    auto &versionNode = nodeP->lookForChild(WKTConstants::VERSION);
1849
34.6k
    if (!isNull(versionNode)) {
1850
212
        const auto &versionChildren = versionNode->GP()->children();
1851
212
        if (versionChildren.size() == 1) {
1852
207
            properties->set(CoordinateOperation::OPERATION_VERSION_KEY,
1853
207
                            stripQuotes(versionChildren[0]));
1854
207
        } else {
1855
5
            ThrowNotRequiredNumberOfChildren(versionNode->GP()->value());
1856
5
        }
1857
212
    }
1858
1859
34.6k
    return *properties;
1860
34.6k
}
1861
1862
// ---------------------------------------------------------------------------
1863
1864
ObjectDomainPtr
1865
34.7k
WKTParser::Private::buildObjectDomain(const WKTNodeNNPtr &node) {
1866
1867
34.7k
    const auto *nodeP = node->GP();
1868
34.7k
    auto &scopeNode = nodeP->lookForChild(WKTConstants::SCOPE);
1869
34.7k
    auto &areaNode = nodeP->lookForChild(WKTConstants::AREA);
1870
34.7k
    auto &bboxNode = nodeP->lookForChild(WKTConstants::BBOX);
1871
34.7k
    auto &verticalExtentNode =
1872
34.7k
        nodeP->lookForChild(WKTConstants::VERTICALEXTENT);
1873
34.7k
    auto &temporalExtentNode = nodeP->lookForChild(WKTConstants::TIMEEXTENT);
1874
34.7k
    if (!isNull(scopeNode) || !isNull(areaNode) || !isNull(bboxNode) ||
1875
34.4k
        !isNull(verticalExtentNode) || !isNull(temporalExtentNode)) {
1876
1.00k
        optional<std::string> scope;
1877
1.00k
        const auto *scopeNodeP = scopeNode->GP();
1878
1.00k
        const auto &scopeChildren = scopeNodeP->children();
1879
1.00k
        if (scopeChildren.size() == 1) {
1880
5
            scope = stripQuotes(scopeChildren[0]);
1881
5
        }
1882
1.00k
        ExtentPtr extent;
1883
1.00k
        if (!isNull(areaNode) || !isNull(bboxNode)) {
1884
94
            util::optional<std::string> description;
1885
94
            std::vector<GeographicExtentNNPtr> geogExtent;
1886
94
            std::vector<VerticalExtentNNPtr> verticalExtent;
1887
94
            std::vector<TemporalExtentNNPtr> temporalExtent;
1888
94
            if (!isNull(areaNode)) {
1889
79
                const auto &areaChildren = areaNode->GP()->children();
1890
79
                if (areaChildren.size() == 1) {
1891
71
                    description = stripQuotes(areaChildren[0]);
1892
71
                } else {
1893
8
                    ThrowNotRequiredNumberOfChildren(areaNode->GP()->value());
1894
8
                }
1895
79
            }
1896
86
            if (!isNull(bboxNode)) {
1897
15
                const auto &bboxChildren = bboxNode->GP()->children();
1898
15
                if (bboxChildren.size() == 4) {
1899
3
                    double south, west, north, east;
1900
3
                    try {
1901
3
                        south = asDouble(bboxChildren[0]);
1902
3
                        west = asDouble(bboxChildren[1]);
1903
3
                        north = asDouble(bboxChildren[2]);
1904
3
                        east = asDouble(bboxChildren[3]);
1905
3
                    } catch (const std::exception &) {
1906
3
                        throw ParsingException(concat("not 4 double values in ",
1907
3
                                                      bboxNode->GP()->value(),
1908
3
                                                      " node"));
1909
3
                    }
1910
0
                    try {
1911
0
                        auto bbox = GeographicBoundingBox::create(west, south,
1912
0
                                                                  east, north);
1913
0
                        geogExtent.emplace_back(bbox);
1914
0
                    } catch (const std::exception &e) {
1915
0
                        throw ParsingException(concat("Invalid ",
1916
0
                                                      bboxNode->GP()->value(),
1917
0
                                                      " node: ") +
1918
0
                                               e.what());
1919
0
                    }
1920
12
                } else {
1921
12
                    ThrowNotRequiredNumberOfChildren(bboxNode->GP()->value());
1922
12
                }
1923
15
            }
1924
1925
71
            if (!isNull(verticalExtentNode)) {
1926
0
                const auto &verticalExtentChildren =
1927
0
                    verticalExtentNode->GP()->children();
1928
0
                const auto verticalExtentChildrenSize =
1929
0
                    verticalExtentChildren.size();
1930
0
                if (verticalExtentChildrenSize == 2 ||
1931
0
                    verticalExtentChildrenSize == 3) {
1932
0
                    double min;
1933
0
                    double max;
1934
0
                    try {
1935
0
                        min = asDouble(verticalExtentChildren[0]);
1936
0
                        max = asDouble(verticalExtentChildren[1]);
1937
0
                    } catch (const std::exception &) {
1938
0
                        throw ParsingException(
1939
0
                            concat("not 2 double values in ",
1940
0
                                   verticalExtentNode->GP()->value(), " node"));
1941
0
                    }
1942
0
                    UnitOfMeasure unit = UnitOfMeasure::METRE;
1943
0
                    if (verticalExtentChildrenSize == 3) {
1944
0
                        unit = buildUnit(verticalExtentChildren[2],
1945
0
                                         UnitOfMeasure::Type::LINEAR);
1946
0
                    }
1947
0
                    verticalExtent.emplace_back(VerticalExtent::create(
1948
0
                        min, max, util::nn_make_shared<UnitOfMeasure>(unit)));
1949
0
                } else {
1950
0
                    ThrowNotRequiredNumberOfChildren(
1951
0
                        verticalExtentNode->GP()->value());
1952
0
                }
1953
0
            }
1954
1955
71
            if (!isNull(temporalExtentNode)) {
1956
47
                const auto &temporalExtentChildren =
1957
47
                    temporalExtentNode->GP()->children();
1958
47
                if (temporalExtentChildren.size() == 2) {
1959
46
                    temporalExtent.emplace_back(TemporalExtent::create(
1960
46
                        stripQuotes(temporalExtentChildren[0]),
1961
46
                        stripQuotes(temporalExtentChildren[1])));
1962
46
                } else {
1963
1
                    ThrowNotRequiredNumberOfChildren(
1964
1
                        temporalExtentNode->GP()->value());
1965
1
                }
1966
47
            }
1967
70
            extent = Extent::create(description, geogExtent, verticalExtent,
1968
70
                                    temporalExtent)
1969
70
                         .as_nullable();
1970
70
        }
1971
985
        return ObjectDomain::create(scope, extent).as_nullable();
1972
1.00k
    }
1973
1974
33.7k
    return nullptr;
1975
34.7k
}
1976
1977
// ---------------------------------------------------------------------------
1978
1979
UnitOfMeasure WKTParser::Private::buildUnit(const WKTNodeNNPtr &node,
1980
1.62k
                                            UnitOfMeasure::Type type) {
1981
1.62k
    const auto *nodeP = node->GP();
1982
1.62k
    const auto &children = nodeP->children();
1983
1.62k
    if ((type != UnitOfMeasure::Type::TIME && children.size() < 2) ||
1984
1.61k
        (type == UnitOfMeasure::Type::TIME && children.size() < 1)) {
1985
12
        ThrowNotEnoughChildren(nodeP->value());
1986
12
    }
1987
1.61k
    try {
1988
1.61k
        std::string unitName(stripQuotes(children[0]));
1989
1.61k
        PropertyMap properties(buildProperties(node));
1990
1.61k
        auto &idNode =
1991
1.61k
            nodeP->lookForChild(WKTConstants::ID, WKTConstants::AUTHORITY);
1992
1.61k
        if (!isNull(idNode) && idNode->GP()->childrenSize() < 2) {
1993
35
            emitRecoverableWarning("not enough children in " +
1994
35
                                   idNode->GP()->value() + " node");
1995
35
        }
1996
1.61k
        const bool hasValidIdNode =
1997
1.61k
            !isNull(idNode) && idNode->GP()->childrenSize() >= 2;
1998
1999
1.61k
        const auto &idNodeChildren(idNode->GP()->children());
2000
1.61k
        std::string codeSpace(hasValidIdNode ? stripQuotes(idNodeChildren[0])
2001
1.61k
                                             : std::string());
2002
1.61k
        std::string code(hasValidIdNode ? stripQuotes(idNodeChildren[1])
2003
1.61k
                                        : std::string());
2004
2005
1.61k
        bool queryDb = true;
2006
1.61k
        if (type == UnitOfMeasure::Type::UNKNOWN) {
2007
47
            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
47
            } else if (ci_equal(unitName, "DEGREE") ||
2016
47
                       ci_equal(unitName, "GRAD")) {
2017
0
                type = UnitOfMeasure::Type::ANGULAR;
2018
0
            }
2019
47
        }
2020
2021
1.61k
        if (esriStyle_ && dbContext_ && queryDb) {
2022
385
            std::string outTableName;
2023
385
            std::string authNameFromAlias;
2024
385
            std::string codeFromAlias;
2025
385
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2026
385
                                                        std::string());
2027
385
            auto officialName = authFactory->getOfficialNameFromAlias(
2028
385
                unitName, "unit_of_measure", "ESRI", false, outTableName,
2029
385
                authNameFromAlias, codeFromAlias);
2030
385
            if (!officialName.empty()) {
2031
121
                unitName = std::move(officialName);
2032
121
                codeSpace = std::move(authNameFromAlias);
2033
121
                code = std::move(codeFromAlias);
2034
121
            }
2035
385
        }
2036
2037
1.61k
        double convFactor = children.size() >= 2 ? asDouble(children[1]) : 0.0;
2038
1.61k
        constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37;
2039
1.61k
        constexpr double REL_ERROR = 1e-10;
2040
        // Fix common rounding errors
2041
1.61k
        if (std::fabs(convFactor - UnitOfMeasure::DEGREE.conversionToSI()) <
2042
1.61k
            REL_ERROR * convFactor) {
2043
136
            convFactor = UnitOfMeasure::DEGREE.conversionToSI();
2044
1.47k
        } else if (std::fabs(convFactor - US_FOOT_CONV_FACTOR) <
2045
1.47k
                   REL_ERROR * convFactor) {
2046
0
            convFactor = US_FOOT_CONV_FACTOR;
2047
0
        }
2048
2049
1.61k
        return UnitOfMeasure(unitName, convFactor, type, codeSpace, code);
2050
1.61k
    } catch (const std::exception &e) {
2051
20
        throw buildRethrow(__FUNCTION__, e);
2052
20
    }
2053
1.61k
}
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
23.1k
                                                     UnitOfMeasure::Type type) {
2060
23.1k
    const auto *nodeP = node->GP();
2061
23.1k
    {
2062
23.1k
        auto &unitNode = nodeP->lookForChild(WKTConstants::LENGTHUNIT);
2063
23.1k
        if (!isNull(unitNode)) {
2064
4
            return buildUnit(unitNode, UnitOfMeasure::Type::LINEAR);
2065
4
        }
2066
23.1k
    }
2067
2068
23.1k
    {
2069
23.1k
        auto &unitNode = nodeP->lookForChild(WKTConstants::ANGLEUNIT);
2070
23.1k
        if (!isNull(unitNode)) {
2071
5
            return buildUnit(unitNode, UnitOfMeasure::Type::ANGULAR);
2072
5
        }
2073
23.1k
    }
2074
2075
23.1k
    {
2076
23.1k
        auto &unitNode = nodeP->lookForChild(WKTConstants::SCALEUNIT);
2077
23.1k
        if (!isNull(unitNode)) {
2078
14
            return buildUnit(unitNode, UnitOfMeasure::Type::SCALE);
2079
14
        }
2080
23.1k
    }
2081
2082
23.1k
    {
2083
23.1k
        auto &unitNode = nodeP->lookForChild(WKTConstants::TIMEUNIT);
2084
23.1k
        if (!isNull(unitNode)) {
2085
2
            return buildUnit(unitNode, UnitOfMeasure::Type::TIME);
2086
2
        }
2087
23.1k
    }
2088
23.1k
    {
2089
23.1k
        auto &unitNode = nodeP->lookForChild(WKTConstants::TEMPORALQUANTITY);
2090
23.1k
        if (!isNull(unitNode)) {
2091
0
            return buildUnit(unitNode, UnitOfMeasure::Type::TIME);
2092
0
        }
2093
23.1k
    }
2094
2095
23.1k
    {
2096
23.1k
        auto &unitNode = nodeP->lookForChild(WKTConstants::PARAMETRICUNIT);
2097
23.1k
        if (!isNull(unitNode)) {
2098
2
            return buildUnit(unitNode, UnitOfMeasure::Type::PARAMETRIC);
2099
2
        }
2100
23.1k
    }
2101
2102
23.1k
    {
2103
23.1k
        auto &unitNode = nodeP->lookForChild(WKTConstants::UNIT);
2104
23.1k
        if (!isNull(unitNode)) {
2105
1.54k
            return buildUnit(unitNode, type);
2106
1.54k
        }
2107
23.1k
    }
2108
2109
21.6k
    return UnitOfMeasure::NONE;
2110
23.1k
}
2111
2112
// ---------------------------------------------------------------------------
2113
2114
5.13k
EllipsoidNNPtr WKTParser::Private::buildEllipsoid(const WKTNodeNNPtr &node) {
2115
5.13k
    const auto *nodeP = node->GP();
2116
5.13k
    const auto &children = nodeP->children();
2117
5.13k
    if (children.size() < 3) {
2118
13
        ThrowNotEnoughChildren(nodeP->value());
2119
13
    }
2120
5.12k
    try {
2121
5.12k
        UnitOfMeasure unit =
2122
5.12k
            buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR);
2123
5.12k
        if (unit == UnitOfMeasure::NONE) {
2124
4.74k
            unit = UnitOfMeasure::METRE;
2125
4.74k
        }
2126
5.12k
        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
5.12k
        const auto &invFlatteningChild = children[2];
2132
5.12k
        if (invFlatteningChild->GP()->value() == "\"inf\"") {
2133
0
            emitRecoverableWarning("Inverse flattening = \"inf\" is not "
2134
0
                                   "conformant, but understood");
2135
0
        }
2136
5.12k
        Scale invFlattening(invFlatteningChild->GP()->value() == "\"inf\""
2137
5.12k
                                ? 0
2138
5.12k
                                : asDouble(invFlatteningChild));
2139
5.12k
        const auto ellpsProperties = buildProperties(node);
2140
5.12k
        std::string ellpsName;
2141
5.12k
        ellpsProperties.getStringValue(IdentifiedObject::NAME_KEY, ellpsName);
2142
5.12k
        const auto celestialBody(Ellipsoid::guessBodyName(
2143
5.12k
            dbContext_, semiMajorAxis.getSIValue(), ellpsName));
2144
5.12k
        if (invFlattening.getSIValue() == 0) {
2145
2.88k
            return Ellipsoid::createSphere(ellpsProperties, semiMajorAxis,
2146
2.88k
                                           celestialBody);
2147
2.88k
        } else {
2148
2.24k
            return Ellipsoid::createFlattenedSphere(
2149
2.24k
                ellpsProperties, semiMajorAxis, invFlattening, celestialBody);
2150
2.24k
        }
2151
5.12k
    } catch (const std::exception &e) {
2152
40
        throw buildRethrow(__FUNCTION__, e);
2153
40
    }
2154
5.12k
}
2155
2156
// ---------------------------------------------------------------------------
2157
2158
PrimeMeridianNNPtr WKTParser::Private::buildPrimeMeridian(
2159
292
    const WKTNodeNNPtr &node, const UnitOfMeasure &defaultAngularUnit) {
2160
292
    const auto *nodeP = node->GP();
2161
292
    const auto &children = nodeP->children();
2162
292
    if (children.size() < 2) {
2163
3
        ThrowNotEnoughChildren(nodeP->value());
2164
3
    }
2165
289
    auto name = stripQuotes(children[0]);
2166
289
    UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR);
2167
289
    if (unit == UnitOfMeasure::NONE) {
2168
289
        unit = defaultAngularUnit;
2169
289
        if (unit == UnitOfMeasure::NONE) {
2170
220
            unit = UnitOfMeasure::DEGREE;
2171
220
        }
2172
289
    }
2173
289
    try {
2174
289
        double angleValue = asDouble(children[1]);
2175
2176
        // Correct for GDAL WKT1 and WKT1-ESRI departure
2177
289
        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
289
        } else {
2182
289
            static const struct {
2183
289
                const char *name;
2184
289
                int deg;
2185
289
                int min;
2186
289
                double sec;
2187
289
            } primeMeridiansDMS[] = {
2188
289
                {"Lisbon", -9, 7, 54.862},  {"Bogota", -74, 4, 51.3},
2189
289
                {"Madrid", -3, 41, 14.55},  {"Rome", 12, 27, 8.4},
2190
289
                {"Bern", 7, 26, 22.5},      {"Jakarta", 106, 48, 27.79},
2191
289
                {"Ferro", -17, 40, 0},      {"Brussels", 4, 22, 4.71},
2192
289
                {"Stockholm", 18, 3, 29.8}, {"Athens", 23, 42, 58.815},
2193
289
                {"Oslo", 10, 43, 22.5},     {"Paris RGS", 2, 20, 13.95},
2194
289
                {"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
3.46k
            for (const auto &pmDef : primeMeridiansDMS) {
2202
3.46k
                if (name == pmDef.name) {
2203
97
                    double dmsAsDecimalValue =
2204
97
                        (pmDef.deg >= 0 ? 1 : -1) *
2205
97
                        (std::abs(pmDef.deg) + pmDef.min / 100. +
2206
97
                         pmDef.sec / 10000.);
2207
97
                    double dmsAsDecimalDegreeValue =
2208
97
                        (pmDef.deg >= 0 ? 1 : -1) *
2209
97
                        (std::abs(pmDef.deg) + pmDef.min / 60. +
2210
97
                         pmDef.sec / 3600.);
2211
97
                    if (std::fabs(angleValue - dmsAsDecimalValue) < 1e-8 ||
2212
97
                        std::fabs(angleValue - dmsAsDecimalDegreeValue) <
2213
97
                            1e-8) {
2214
0
                        angleValue = dmsAsDecimalDegreeValue;
2215
0
                        unit = UnitOfMeasure::DEGREE;
2216
0
                    }
2217
97
                    break;
2218
97
                }
2219
3.46k
            }
2220
289
        }
2221
2222
289
        auto &properties = buildProperties(node);
2223
289
        if (dbContext_ && esriStyle_) {
2224
150
            std::string outTableName;
2225
150
            std::string codeFromAlias;
2226
150
            std::string authNameFromAlias;
2227
150
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2228
150
                                                        std::string());
2229
150
            auto officialName = authFactory->getOfficialNameFromAlias(
2230
150
                name, "prime_meridian", "ESRI", false, outTableName,
2231
150
                authNameFromAlias, codeFromAlias);
2232
150
            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
150
        }
2247
2248
289
        Angle angle(angleValue, unit);
2249
289
        return PrimeMeridian::create(properties, angle);
2250
289
    } catch (const std::exception &e) {
2251
3
        throw buildRethrow(__FUNCTION__, e);
2252
3
    }
2253
289
}
2254
2255
// ---------------------------------------------------------------------------
2256
2257
5.79k
optional<std::string> WKTParser::Private::getAnchor(const WKTNodeNNPtr &node) {
2258
2259
5.79k
    auto &anchorNode = node->GP()->lookForChild(WKTConstants::ANCHOR);
2260
5.79k
    if (anchorNode->GP()->childrenSize() == 1) {
2261
353
        return optional<std::string>(
2262
353
            stripQuotes(anchorNode->GP()->children()[0]));
2263
353
    }
2264
5.44k
    return optional<std::string>();
2265
5.79k
}
2266
2267
// ---------------------------------------------------------------------------
2268
2269
optional<common::Measure>
2270
5.42k
WKTParser::Private::getAnchorEpoch(const WKTNodeNNPtr &node) {
2271
2272
5.42k
    auto &anchorEpochNode = node->GP()->lookForChild(WKTConstants::ANCHOREPOCH);
2273
5.42k
    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
5.42k
    return optional<common::Measure>();
2283
5.42k
}
2284
// ---------------------------------------------------------------------------
2285
2286
static const PrimeMeridianNNPtr &
2287
fixupPrimeMeridan(const EllipsoidNNPtr &ellipsoid,
2288
9.57k
                  const PrimeMeridianNNPtr &pm) {
2289
9.57k
    return (ellipsoid->celestialBody() != Ellipsoid::EARTH &&
2290
6.74k
            pm.get() == PrimeMeridian::GREENWICH.get())
2291
9.57k
               ? PrimeMeridian::REFERENCE_MERIDIAN
2292
9.57k
               : pm;
2293
9.57k
}
2294
2295
// ---------------------------------------------------------------------------
2296
2297
GeodeticReferenceFrameNNPtr WKTParser::Private::buildGeodeticReferenceFrame(
2298
    const WKTNodeNNPtr &node, const PrimeMeridianNNPtr &primeMeridian,
2299
3.33k
    const WKTNodeNNPtr &dynamicNode) {
2300
3.33k
    const auto *nodeP = node->GP();
2301
3.33k
    auto &ellipsoidNode =
2302
3.33k
        nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID);
2303
3.33k
    if (isNull(ellipsoidNode)) {
2304
53
        ThrowMissing(WKTConstants::ELLIPSOID);
2305
53
    }
2306
3.27k
    auto &properties = buildProperties(node);
2307
2308
    // do that before buildEllipsoid() so that esriStyle_ can be set
2309
3.27k
    auto name = stripQuotes(nodeP->children()[0]);
2310
2311
3.27k
    const auto identifyFromName = [&](const std::string &l_name) {
2312
1.54k
        if (dbContext_) {
2313
1.49k
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2314
1.49k
                                                        std::string());
2315
1.49k
            auto res = authFactory->createObjectsFromName(
2316
1.49k
                l_name,
2317
1.49k
                {AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, true,
2318
1.49k
                1);
2319
1.49k
            if (!res.empty()) {
2320
597
                bool foundDatumName = false;
2321
597
                const auto &refDatum = res.front();
2322
597
                if (metadata::Identifier::isEquivalentName(
2323
597
                        l_name.c_str(), refDatum->nameStr().c_str())) {
2324
3
                    foundDatumName = true;
2325
594
                } else if (refDatum->identifiers().size() == 1) {
2326
594
                    const auto &id = refDatum->identifiers()[0];
2327
594
                    const auto aliases =
2328
594
                        authFactory->databaseContext()->getAliases(
2329
594
                            *id->codeSpace(), id->code(), refDatum->nameStr(),
2330
594
                            "geodetic_datum", "not EPSG_OLD");
2331
1.36k
                    for (const auto &alias : aliases) {
2332
1.36k
                        if (metadata::Identifier::isEquivalentName(
2333
1.36k
                                l_name.c_str(), alias.c_str())) {
2334
43
                            foundDatumName = true;
2335
43
                            break;
2336
43
                        }
2337
1.36k
                    }
2338
594
                }
2339
597
                if (foundDatumName) {
2340
46
                    properties.set(IdentifiedObject::NAME_KEY,
2341
46
                                   refDatum->nameStr());
2342
46
                    if (!properties.get(Identifier::CODESPACE_KEY) &&
2343
46
                        refDatum->identifiers().size() == 1) {
2344
46
                        const auto &id = refDatum->identifiers()[0];
2345
46
                        auto identifiers = ArrayOfBaseObject::create();
2346
46
                        identifiers->add(Identifier::create(
2347
46
                            id->code(), PropertyMap()
2348
46
                                            .set(Identifier::CODESPACE_KEY,
2349
46
                                                 *id->codeSpace())
2350
46
                                            .set(Identifier::AUTHORITY_KEY,
2351
46
                                                 *id->codeSpace())));
2352
46
                        properties.set(IdentifiedObject::IDENTIFIERS_KEY,
2353
46
                                       identifiers);
2354
46
                    }
2355
46
                    return true;
2356
46
                }
2357
899
            } else {
2358
                // Get official name from database if AUTHORITY is present
2359
899
                auto &idNode = nodeP->lookForChild(WKTConstants::AUTHORITY);
2360
899
                if (!isNull(idNode)) {
2361
0
                    try {
2362
0
                        auto id = buildId(node, idNode, false, false);
2363
0
                        auto authFactory2 = AuthorityFactory::create(
2364
0
                            NN_NO_CHECK(dbContext_), *id->codeSpace());
2365
0
                        auto dbDatum =
2366
0
                            authFactory2->createGeodeticDatum(id->code());
2367
0
                        properties.set(IdentifiedObject::NAME_KEY,
2368
0
                                       dbDatum->nameStr());
2369
0
                        return true;
2370
0
                    } catch (const std::exception &) {
2371
0
                    }
2372
0
                }
2373
899
            }
2374
1.49k
        }
2375
1.50k
        return false;
2376
1.54k
    };
2377
2378
    // Remap GDAL WGS_1984 to EPSG v9 "World Geodetic System 1984" official
2379
    // name.
2380
    // Also remap EPSG v10 datum ensemble names to non-ensemble EPSG v9
2381
3.27k
    bool nameSet = false;
2382
3.27k
    if (name == "WGS_1984" || name == "World Geodetic System 1984 ensemble") {
2383
46
        nameSet = true;
2384
46
        properties.set(IdentifiedObject::NAME_KEY,
2385
46
                       GeodeticReferenceFrame::EPSG_6326->nameStr());
2386
3.23k
    } else if (name == "European Terrestrial Reference System 1989 ensemble") {
2387
0
        nameSet = true;
2388
0
        properties.set(IdentifiedObject::NAME_KEY,
2389
0
                       "European Terrestrial Reference System 1989");
2390
0
    }
2391
2392
    // If we got hints this might be a ESRI WKT, then check in the DB to
2393
    // confirm
2394
3.27k
    std::string officialName;
2395
3.27k
    std::string authNameFromAlias;
2396
3.27k
    std::string codeFromAlias;
2397
3.27k
    if (!nameSet && maybeEsriStyle_ && dbContext_ &&
2398
914
        !(starts_with(name, "D_") || esriStyle_)) {
2399
125
        std::string outTableName;
2400
125
        auto authFactory =
2401
125
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
2402
125
        officialName = authFactory->getOfficialNameFromAlias(
2403
125
            name, "geodetic_datum", "ESRI", false, outTableName,
2404
125
            authNameFromAlias, codeFromAlias);
2405
125
        if (!officialName.empty()) {
2406
0
            maybeEsriStyle_ = false;
2407
0
            esriStyle_ = true;
2408
0
        }
2409
125
    }
2410
2411
3.27k
    if (!nameSet && (starts_with(name, "D_") || esriStyle_)) {
2412
2.17k
        esriStyle_ = true;
2413
2.17k
        const char *tableNameForAlias = nullptr;
2414
2.17k
        if (name == "D_WGS_1984") {
2415
96
            name = "World Geodetic System 1984";
2416
96
            authNameFromAlias = Identifier::EPSG;
2417
96
            codeFromAlias = "6326";
2418
2.08k
        } else if (name == "D_ETRS_1989") {
2419
0
            name = "European Terrestrial Reference System 1989";
2420
0
            authNameFromAlias = Identifier::EPSG;
2421
0
            codeFromAlias = "6258";
2422
2.08k
        } else if (name == "D_unknown") {
2423
85
            name = "unknown";
2424
1.99k
        } else if (name == "D_Unknown_based_on_WGS_84_ellipsoid") {
2425
0
            name = "Unknown based on WGS 84 ellipsoid";
2426
1.99k
        } else {
2427
1.99k
            tableNameForAlias = "geodetic_datum";
2428
1.99k
        }
2429
2430
2.17k
        bool setNameAndId = true;
2431
2.17k
        if (dbContext_ && tableNameForAlias) {
2432
1.51k
            if (officialName.empty()) {
2433
1.51k
                std::string outTableName;
2434
1.51k
                auto authFactory = AuthorityFactory::create(
2435
1.51k
                    NN_NO_CHECK(dbContext_), std::string());
2436
1.51k
                officialName = authFactory->getOfficialNameFromAlias(
2437
1.51k
                    name, tableNameForAlias, "ESRI", false, outTableName,
2438
1.51k
                    authNameFromAlias, codeFromAlias);
2439
1.51k
            }
2440
1.51k
            if (officialName.empty()) {
2441
1.49k
                if (starts_with(name, "D_")) {
2442
                    // For the case of "D_GDA2020" where there is no D_GDA2020
2443
                    // ESRI alias, so just try without the D_ prefix.
2444
1.21k
                    const auto nameWithoutDPrefix = name.substr(2);
2445
1.21k
                    if (identifyFromName(nameWithoutDPrefix)) {
2446
40
                        setNameAndId =
2447
40
                            false; // already done in identifyFromName()
2448
40
                    }
2449
1.21k
                }
2450
1.49k
            } else {
2451
17
                if (primeMeridian->nameStr() !=
2452
17
                    PrimeMeridian::GREENWICH->nameStr()) {
2453
0
                    auto nameWithPM =
2454
0
                        officialName + " (" + primeMeridian->nameStr() + ")";
2455
0
                    if (dbContext_->isKnownName(nameWithPM, "geodetic_datum")) {
2456
0
                        officialName = std::move(nameWithPM);
2457
0
                    }
2458
0
                }
2459
17
                name = std::move(officialName);
2460
17
            }
2461
1.51k
        }
2462
2463
2.17k
        if (setNameAndId) {
2464
2.13k
            properties.set(IdentifiedObject::NAME_KEY, name);
2465
2.13k
            if (!authNameFromAlias.empty()) {
2466
113
                auto identifiers = ArrayOfBaseObject::create();
2467
113
                identifiers->add(Identifier::create(
2468
113
                    codeFromAlias,
2469
113
                    PropertyMap()
2470
113
                        .set(Identifier::CODESPACE_KEY, authNameFromAlias)
2471
113
                        .set(Identifier::AUTHORITY_KEY, authNameFromAlias)));
2472
113
                properties.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
2473
113
            }
2474
2.13k
        }
2475
2.17k
    } else if (!nameSet && name.find('_') != std::string::npos) {
2476
        // Likely coming from WKT1
2477
332
        identifyFromName(name);
2478
332
    }
2479
2480
3.27k
    auto ellipsoid = buildEllipsoid(ellipsoidNode);
2481
3.27k
    const auto &primeMeridianModified =
2482
3.27k
        fixupPrimeMeridan(ellipsoid, primeMeridian);
2483
2484
3.27k
    auto &TOWGS84Node = nodeP->lookForChild(WKTConstants::TOWGS84);
2485
3.27k
    if (!isNull(TOWGS84Node)) {
2486
1
        const auto &TOWGS84Children = TOWGS84Node->GP()->children();
2487
1
        const size_t TOWGS84Size = TOWGS84Children.size();
2488
1
        if (TOWGS84Size == 3 || TOWGS84Size == 7) {
2489
0
            try {
2490
0
                for (const auto &child : TOWGS84Children) {
2491
0
                    toWGS84Parameters_.push_back(asDouble(child));
2492
0
                }
2493
2494
0
                if (TOWGS84Size == 7 && dbContext_) {
2495
0
                    dbContext_->toWGS84AutocorrectWrongValues(
2496
0
                        toWGS84Parameters_[0], toWGS84Parameters_[1],
2497
0
                        toWGS84Parameters_[2], toWGS84Parameters_[3],
2498
0
                        toWGS84Parameters_[4], toWGS84Parameters_[5],
2499
0
                        toWGS84Parameters_[6]);
2500
0
                }
2501
2502
0
                for (size_t i = TOWGS84Size; i < 7; ++i) {
2503
0
                    toWGS84Parameters_.push_back(0.0);
2504
0
                }
2505
0
            } catch (const std::exception &) {
2506
0
                throw ParsingException("Invalid TOWGS84 node");
2507
0
            }
2508
1
        } else {
2509
1
            throw ParsingException("Invalid TOWGS84 node");
2510
1
        }
2511
1
    }
2512
2513
3.27k
    auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
2514
3.27k
    const auto &extensionChildren = extensionNode->GP()->children();
2515
3.27k
    if (extensionChildren.size() == 2) {
2516
0
        if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) {
2517
0
            datumPROJ4Grids_ = stripQuotes(extensionChildren[1]);
2518
0
        }
2519
0
    }
2520
2521
3.27k
    if (!isNull(dynamicNode)) {
2522
18
        double frameReferenceEpoch = 0.0;
2523
18
        util::optional<std::string> modelName;
2524
18
        parseDynamic(dynamicNode, frameReferenceEpoch, modelName);
2525
18
        return DynamicGeodeticReferenceFrame::create(
2526
18
            properties, ellipsoid, getAnchor(node), primeMeridianModified,
2527
18
            common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR),
2528
18
            modelName);
2529
18
    }
2530
2531
3.25k
    return GeodeticReferenceFrame::create(properties, ellipsoid,
2532
3.25k
                                          getAnchor(node), getAnchorEpoch(node),
2533
3.25k
                                          primeMeridianModified);
2534
3.27k
}
2535
2536
// ---------------------------------------------------------------------------
2537
2538
DatumEnsembleNNPtr
2539
WKTParser::Private::buildDatumEnsemble(const WKTNodeNNPtr &node,
2540
                                       const PrimeMeridianPtr &primeMeridian,
2541
241
                                       bool expectEllipsoid) {
2542
241
    const auto *nodeP = node->GP();
2543
241
    auto &ellipsoidNode =
2544
241
        nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID);
2545
241
    if (expectEllipsoid && isNull(ellipsoidNode)) {
2546
13
        ThrowMissing(WKTConstants::ELLIPSOID);
2547
13
    }
2548
2549
228
    std::vector<DatumNNPtr> datums;
2550
6.73k
    for (const auto &subNode : nodeP->children()) {
2551
6.73k
        if (ci_equal(subNode->GP()->value(), WKTConstants::MEMBER)) {
2552
2.29k
            if (subNode->GP()->childrenSize() == 0) {
2553
7
                throw ParsingException("Invalid MEMBER node");
2554
7
            }
2555
2.28k
            if (expectEllipsoid) {
2556
1.85k
                datums.emplace_back(GeodeticReferenceFrame::create(
2557
1.85k
                    buildProperties(subNode), buildEllipsoid(ellipsoidNode),
2558
1.85k
                    optional<std::string>(),
2559
1.85k
                    primeMeridian ? NN_NO_CHECK(primeMeridian)
2560
1.85k
                                  : PrimeMeridian::GREENWICH));
2561
1.85k
            } else {
2562
432
                datums.emplace_back(
2563
432
                    VerticalReferenceFrame::create(buildProperties(subNode)));
2564
432
            }
2565
2.28k
        }
2566
6.73k
    }
2567
2568
221
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::ENSEMBLEACCURACY);
2569
221
    auto &accuracyNodeChildren = accuracyNode->GP()->children();
2570
221
    if (accuracyNodeChildren.empty()) {
2571
90
        ThrowMissing(WKTConstants::ENSEMBLEACCURACY);
2572
90
    }
2573
131
    auto accuracy =
2574
131
        PositionalAccuracy::create(accuracyNodeChildren[0]->GP()->value());
2575
2576
131
    try {
2577
131
        return DatumEnsemble::create(buildProperties(node), datums, accuracy);
2578
131
    } catch (const util::Exception &e) {
2579
8
        throw buildRethrow(__FUNCTION__, e);
2580
8
    }
2581
131
}
2582
2583
// ---------------------------------------------------------------------------
2584
2585
3
MeridianNNPtr WKTParser::Private::buildMeridian(const WKTNodeNNPtr &node) {
2586
3
    const auto *nodeP = node->GP();
2587
3
    const auto &children = nodeP->children();
2588
3
    if (children.size() < 2) {
2589
3
        ThrowNotEnoughChildren(nodeP->value());
2590
3
    }
2591
0
    UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR);
2592
0
    try {
2593
0
        double angleValue = asDouble(children[0]);
2594
0
        Angle angle(angleValue, unit);
2595
0
        return Meridian::create(angle);
2596
0
    } catch (const std::exception &e) {
2597
0
        throw buildRethrow(__FUNCTION__, e);
2598
0
    }
2599
0
}
2600
2601
// ---------------------------------------------------------------------------
2602
2603
5
PROJ_NO_RETURN static void ThrowParsingExceptionMissingUNIT() {
2604
5
    throw ParsingException("buildCS: missing UNIT");
2605
5
}
2606
2607
// ---------------------------------------------------------------------------
2608
2609
CoordinateSystemAxisNNPtr
2610
WKTParser::Private::buildAxis(const WKTNodeNNPtr &node,
2611
                              const UnitOfMeasure &unitIn,
2612
                              const UnitOfMeasure::Type &unitType,
2613
442
                              bool isGeocentric, int expectedOrderNum) {
2614
442
    const auto *nodeP = node->GP();
2615
442
    const auto &children = nodeP->children();
2616
442
    if (children.size() < 2) {
2617
3
        ThrowNotEnoughChildren(nodeP->value());
2618
3
    }
2619
2620
439
    auto &orderNode = nodeP->lookForChild(WKTConstants::ORDER);
2621
439
    if (!isNull(orderNode)) {
2622
0
        const auto &orderNodeChildren = orderNode->GP()->children();
2623
0
        if (orderNodeChildren.size() != 1) {
2624
0
            ThrowNotEnoughChildren(WKTConstants::ORDER);
2625
0
        }
2626
0
        const auto &order = orderNodeChildren[0]->GP()->value();
2627
0
        int orderNum;
2628
0
        try {
2629
0
            orderNum = std::stoi(order);
2630
0
        } catch (const std::exception &) {
2631
0
            throw ParsingException(
2632
0
                concat("buildAxis: invalid ORDER value: ", order));
2633
0
        }
2634
0
        if (orderNum != expectedOrderNum) {
2635
0
            throw ParsingException(
2636
0
                concat("buildAxis: did not get expected ORDER value: ", order));
2637
0
        }
2638
0
    }
2639
2640
    // The axis designation in WK2 can be: "name", "(abbrev)" or "name
2641
    // (abbrev)"
2642
439
    std::string axisDesignation(stripQuotes(children[0]));
2643
439
    size_t sepPos = axisDesignation.find(" (");
2644
439
    std::string axisName;
2645
439
    std::string abbreviation;
2646
439
    if (sepPos != std::string::npos && axisDesignation.back() == ')') {
2647
0
        axisName = CoordinateSystemAxis::normalizeAxisName(
2648
0
            axisDesignation.substr(0, sepPos));
2649
0
        abbreviation = axisDesignation.substr(sepPos + 2);
2650
0
        abbreviation.resize(abbreviation.size() - 1);
2651
439
    } else if (!axisDesignation.empty() && axisDesignation[0] == '(' &&
2652
0
               axisDesignation.back() == ')') {
2653
0
        abbreviation = axisDesignation.substr(1, axisDesignation.size() - 2);
2654
0
        if (abbreviation == AxisAbbreviation::E) {
2655
0
            axisName = AxisName::Easting;
2656
0
        } else if (abbreviation == AxisAbbreviation::N) {
2657
0
            axisName = AxisName::Northing;
2658
0
        } else if (abbreviation == AxisAbbreviation::lat) {
2659
0
            axisName = AxisName::Latitude;
2660
0
        } else if (abbreviation == AxisAbbreviation::lon) {
2661
0
            axisName = AxisName::Longitude;
2662
0
        }
2663
439
    } else {
2664
439
        axisName = CoordinateSystemAxis::normalizeAxisName(axisDesignation);
2665
439
        if (axisName == AxisName::Latitude) {
2666
0
            abbreviation = AxisAbbreviation::lat;
2667
439
        } else if (axisName == AxisName::Longitude) {
2668
0
            abbreviation = AxisAbbreviation::lon;
2669
439
        } else if (axisName == AxisName::Ellipsoidal_height) {
2670
0
            abbreviation = AxisAbbreviation::h;
2671
0
        }
2672
439
    }
2673
439
    const std::string &dirString = children[1]->GP()->value();
2674
439
    const AxisDirection *direction = AxisDirection::valueOf(dirString);
2675
2676
    // WKT2, geocentric CS: axis names are omitted
2677
439
    if (axisName.empty()) {
2678
11
        if (direction == &AxisDirection::GEOCENTRIC_X &&
2679
0
            abbreviation == AxisAbbreviation::X) {
2680
0
            axisName = AxisName::Geocentric_X;
2681
11
        } else if (direction == &AxisDirection::GEOCENTRIC_Y &&
2682
0
                   abbreviation == AxisAbbreviation::Y) {
2683
0
            axisName = AxisName::Geocentric_Y;
2684
11
        } else if (direction == &AxisDirection::GEOCENTRIC_Z &&
2685
0
                   abbreviation == AxisAbbreviation::Z) {
2686
0
            axisName = AxisName::Geocentric_Z;
2687
0
        }
2688
11
    }
2689
2690
    // WKT1
2691
439
    if (!direction && isGeocentric && axisName == AxisName::Geocentric_X) {
2692
0
        abbreviation = AxisAbbreviation::X;
2693
0
        direction = &AxisDirection::GEOCENTRIC_X;
2694
439
    } else if (!direction && isGeocentric &&
2695
5
               axisName == AxisName::Geocentric_Y) {
2696
0
        abbreviation = AxisAbbreviation::Y;
2697
0
        direction = &AxisDirection::GEOCENTRIC_Y;
2698
439
    } else if (isGeocentric && axisName == AxisName::Geocentric_Z &&
2699
0
               (dirString == AxisDirectionWKT1::NORTH.toString() ||
2700
0
                dirString == AxisDirectionWKT1::OTHER.toString())) {
2701
0
        abbreviation = AxisAbbreviation::Z;
2702
0
        direction = &AxisDirection::GEOCENTRIC_Z;
2703
439
    } else if (dirString == AxisDirectionWKT1::OTHER.toString()) {
2704
0
        direction = &AxisDirection::UNSPECIFIED;
2705
439
    } else if (dirString == "UNKNOWN") {
2706
        // Found in WKT1 of NSIDC's EASE-Grid Sea Ice Age datasets.
2707
        // Cf https://github.com/OSGeo/gdal/issues/7210
2708
0
        emitRecoverableWarning("UNKNOWN is not a valid direction name.");
2709
0
        direction = &AxisDirection::UNSPECIFIED;
2710
0
    }
2711
2712
439
    if (!direction) {
2713
54
        throw ParsingException(
2714
54
            concat("unhandled axis direction: ", children[1]->GP()->value()));
2715
54
    }
2716
385
    UnitOfMeasure unit(buildUnitInSubNode(node));
2717
385
    if (unit == UnitOfMeasure::NONE) {
2718
        // If no unit in the AXIS node, use the one potentially coming from
2719
        // the CS.
2720
363
        unit = unitIn;
2721
363
        if (unit == UnitOfMeasure::NONE &&
2722
5
            unitType != UnitOfMeasure::Type::NONE &&
2723
5
            unitType != UnitOfMeasure::Type::TIME) {
2724
5
            ThrowParsingExceptionMissingUNIT();
2725
5
        }
2726
363
    }
2727
2728
380
    auto &meridianNode = nodeP->lookForChild(WKTConstants::MERIDIAN);
2729
2730
380
    util::optional<double> minVal;
2731
380
    auto &axisMinValueNode = nodeP->lookForChild(WKTConstants::AXISMINVALUE);
2732
380
    if (!isNull(axisMinValueNode)) {
2733
0
        const auto &axisMinValueNodeChildren =
2734
0
            axisMinValueNode->GP()->children();
2735
0
        if (axisMinValueNodeChildren.size() != 1) {
2736
0
            ThrowNotEnoughChildren(WKTConstants::AXISMINVALUE);
2737
0
        }
2738
0
        const auto &val = axisMinValueNodeChildren[0];
2739
0
        try {
2740
0
            minVal = asDouble(val);
2741
0
        } catch (const std::exception &) {
2742
0
            throw ParsingException(concat(
2743
0
                "buildAxis: invalid AXISMINVALUE value: ", val->GP()->value()));
2744
0
        }
2745
0
    }
2746
2747
380
    util::optional<double> maxVal;
2748
380
    auto &axisMaxValueNode = nodeP->lookForChild(WKTConstants::AXISMAXVALUE);
2749
380
    if (!isNull(axisMaxValueNode)) {
2750
0
        const auto &axisMaxValueNodeChildren =
2751
0
            axisMaxValueNode->GP()->children();
2752
0
        if (axisMaxValueNodeChildren.size() != 1) {
2753
0
            ThrowNotEnoughChildren(WKTConstants::AXISMAXVALUE);
2754
0
        }
2755
0
        const auto &val = axisMaxValueNodeChildren[0];
2756
0
        try {
2757
0
            maxVal = asDouble(val);
2758
0
        } catch (const std::exception &) {
2759
0
            throw ParsingException(concat(
2760
0
                "buildAxis: invalid AXISMAXVALUE value: ", val->GP()->value()));
2761
0
        }
2762
0
    }
2763
2764
380
    util::optional<RangeMeaning> rangeMeaning;
2765
380
    auto &rangeMeaningNode = nodeP->lookForChild(WKTConstants::RANGEMEANING);
2766
380
    if (!isNull(rangeMeaningNode)) {
2767
24
        const auto &rangeMeaningNodeChildren =
2768
24
            rangeMeaningNode->GP()->children();
2769
24
        if (rangeMeaningNodeChildren.size() != 1) {
2770
2
            ThrowNotEnoughChildren(WKTConstants::RANGEMEANING);
2771
2
        }
2772
22
        const std::string &val = rangeMeaningNodeChildren[0]->GP()->value();
2773
22
        const RangeMeaning *meaning = RangeMeaning::valueOf(val);
2774
22
        if (meaning == nullptr) {
2775
7
            throw ParsingException(
2776
7
                concat("buildAxis: invalid RANGEMEANING value: ", val));
2777
7
        }
2778
15
        rangeMeaning = util::optional<RangeMeaning>(*meaning);
2779
15
    }
2780
2781
371
    return CoordinateSystemAxis::create(
2782
371
        buildProperties(node).set(IdentifiedObject::NAME_KEY, axisName),
2783
371
        abbreviation, *direction, unit, minVal, maxVal, rangeMeaning,
2784
371
        !isNull(meridianNode) ? buildMeridian(meridianNode).as_nullable()
2785
371
                              : nullptr);
2786
380
}
2787
2788
// ---------------------------------------------------------------------------
2789
2790
static const PropertyMap emptyPropertyMap{};
2791
2792
// ---------------------------------------------------------------------------
2793
2794
1
PROJ_NO_RETURN static void ThrowParsingException(const std::string &msg) {
2795
1
    throw ParsingException(msg);
2796
1
}
2797
2798
// ---------------------------------------------------------------------------
2799
2800
static ParsingException
2801
3
buildParsingExceptionInvalidAxisCount(const std::string &csType) {
2802
3
    return ParsingException(
2803
3
        concat("buildCS: invalid CS axis count for ", csType));
2804
3
}
2805
2806
// ---------------------------------------------------------------------------
2807
2808
void WKTParser::Private::emitRecoverableMissingUNIT(
2809
5.83k
    const std::string &parentNodeName, const UnitOfMeasure &fallbackUnit) {
2810
5.83k
    std::string msg("buildCS: missing UNIT in ");
2811
5.83k
    msg += parentNodeName;
2812
5.83k
    if (!strict_ && fallbackUnit == UnitOfMeasure::METRE) {
2813
2.98k
        msg += ". Assuming metre";
2814
2.98k
    } else if (!strict_ && fallbackUnit == UnitOfMeasure::DEGREE) {
2815
2.85k
        msg += ". Assuming degree";
2816
2.85k
    }
2817
5.83k
    emitRecoverableWarning(msg);
2818
5.83k
}
2819
2820
// ---------------------------------------------------------------------------
2821
2822
CoordinateSystemNNPtr
2823
WKTParser::Private::buildCS(const WKTNodeNNPtr &node, /* maybe null */
2824
                            const WKTNodeNNPtr &parentNode,
2825
10.1k
                            const UnitOfMeasure &defaultAngularUnit) {
2826
10.1k
    bool isGeocentric = false;
2827
10.1k
    std::string csType;
2828
10.1k
    const int numberOfAxis =
2829
10.1k
        parentNode->countChildrenOfName(WKTConstants::AXIS);
2830
10.1k
    int axisCount = numberOfAxis;
2831
10.1k
    const auto &parentNodeName = parentNode->GP()->value();
2832
10.1k
    if (!isNull(node)) {
2833
7
        const auto *nodeP = node->GP();
2834
7
        const auto &children = nodeP->children();
2835
7
        if (children.size() < 2) {
2836
3
            ThrowNotEnoughChildren(nodeP->value());
2837
3
        }
2838
4
        csType = children[0]->GP()->value();
2839
4
        try {
2840
4
            axisCount = std::stoi(children[1]->GP()->value());
2841
4
        } catch (const std::exception &) {
2842
1
            ThrowParsingException(concat("buildCS: invalid CS axis count: ",
2843
1
                                         children[1]->GP()->value()));
2844
1
        }
2845
10.0k
    } else {
2846
10.0k
        const char *csTypeCStr = CartesianCS::WKT2_TYPE;
2847
10.0k
        if (ci_equal(parentNodeName, WKTConstants::GEOCCS)) {
2848
            // csTypeCStr = CartesianCS::WKT2_TYPE;
2849
94
            isGeocentric = true;
2850
94
            if (axisCount == 0) {
2851
80
                auto unit =
2852
80
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2853
80
                if (unit == UnitOfMeasure::NONE) {
2854
71
                    unit = UnitOfMeasure::METRE;
2855
71
                    emitRecoverableMissingUNIT(parentNodeName, unit);
2856
71
                }
2857
80
                return CartesianCS::createGeocentric(unit);
2858
80
            }
2859
10.0k
        } else if (ci_equal(parentNodeName, WKTConstants::GEOGCS)) {
2860
2.93k
            csTypeCStr = EllipsoidalCS::WKT2_TYPE;
2861
2.93k
            if (axisCount == 0) {
2862
                // Missing axis with GEOGCS ? Presumably Long/Lat order
2863
                // implied
2864
2.92k
                auto unit = buildUnitInSubNode(parentNode,
2865
2.92k
                                               UnitOfMeasure::Type::ANGULAR);
2866
2.92k
                if (unit == UnitOfMeasure::NONE) {
2867
2.85k
                    unit = defaultAngularUnit;
2868
2.85k
                    emitRecoverableMissingUNIT(parentNodeName, unit);
2869
2.85k
                }
2870
2871
                // ESRI WKT for geographic 3D CRS
2872
2.92k
                auto &linUnitNode =
2873
2.92k
                    parentNode->GP()->lookForChild(WKTConstants::LINUNIT);
2874
2.92k
                if (!isNull(linUnitNode)) {
2875
57
                    return EllipsoidalCS::
2876
57
                        createLongitudeLatitudeEllipsoidalHeight(
2877
57
                            unit, buildUnit(linUnitNode,
2878
57
                                            UnitOfMeasure::Type::LINEAR));
2879
57
                }
2880
2881
                // WKT1 --> long/lat
2882
2.86k
                return EllipsoidalCS::createLongitudeLatitude(unit);
2883
2.92k
            }
2884
7.06k
        } else if (ci_equal(parentNodeName, WKTConstants::BASEGEODCRS) ||
2885
7.06k
                   ci_equal(parentNodeName, WKTConstants::BASEGEOGCRS)) {
2886
0
            csTypeCStr = EllipsoidalCS::WKT2_TYPE;
2887
0
            if (axisCount == 0) {
2888
0
                auto unit = buildUnitInSubNode(parentNode,
2889
0
                                               UnitOfMeasure::Type::ANGULAR);
2890
0
                if (unit == UnitOfMeasure::NONE) {
2891
0
                    unit = defaultAngularUnit;
2892
0
                }
2893
                // WKT2 --> presumably lat/long
2894
0
                return EllipsoidalCS::createLatitudeLongitude(unit);
2895
0
            }
2896
7.06k
        } else if (ci_equal(parentNodeName, WKTConstants::PROJCS) ||
2897
6.02k
                   ci_equal(parentNodeName, WKTConstants::BASEPROJCRS) ||
2898
6.02k
                   ci_equal(parentNodeName, WKTConstants::BASEENGCRS)) {
2899
1.03k
            csTypeCStr = CartesianCS::WKT2_TYPE;
2900
1.03k
            if (axisCount == 0) {
2901
1.03k
                auto unit =
2902
1.03k
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2903
1.03k
                if (unit == UnitOfMeasure::NONE) {
2904
957
                    unit = UnitOfMeasure::METRE;
2905
957
                    if (ci_equal(parentNodeName, WKTConstants::PROJCS)) {
2906
957
                        emitRecoverableMissingUNIT(parentNodeName, unit);
2907
957
                    }
2908
957
                }
2909
1.03k
                return CartesianCS::createEastingNorthing(unit);
2910
1.03k
            }
2911
6.02k
        } else if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
2912
5.29k
                   ci_equal(parentNodeName, WKTConstants::VERTCS) ||
2913
3.79k
                   ci_equal(parentNodeName, WKTConstants::BASEVERTCRS)) {
2914
2.23k
            csTypeCStr = VerticalCS::WKT2_TYPE;
2915
2916
2.23k
            bool downDirection = false;
2917
2.23k
            if (ci_equal(parentNodeName, WKTConstants::VERTCS)) // ESRI
2918
1.49k
            {
2919
9.64k
                for (const auto &childNode : parentNode->GP()->children()) {
2920
9.64k
                    const auto &childNodeChildren = childNode->GP()->children();
2921
9.64k
                    if (childNodeChildren.size() == 2 &&
2922
1.51k
                        ci_equal(childNode->GP()->value(),
2923
1.51k
                                 WKTConstants::PARAMETER) &&
2924
21
                        childNodeChildren[0]->GP()->value() ==
2925
21
                            "\"Direction\"") {
2926
0
                        const auto &paramValue =
2927
0
                            childNodeChildren[1]->GP()->value();
2928
0
                        try {
2929
0
                            double val = asDouble(childNodeChildren[1]);
2930
0
                            if (val == 1.0) {
2931
                                // ok
2932
0
                            } else if (val == -1.0) {
2933
0
                                downDirection = true;
2934
0
                            }
2935
0
                        } catch (const std::exception &) {
2936
0
                            throw ParsingException(
2937
0
                                concat("unhandled parameter value type : ",
2938
0
                                       paramValue));
2939
0
                        }
2940
0
                    }
2941
9.64k
                }
2942
1.49k
            }
2943
2944
2.23k
            if (axisCount == 0) {
2945
2.21k
                auto unit =
2946
2.21k
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2947
2.21k
                if (unit == UnitOfMeasure::NONE) {
2948
1.93k
                    unit = UnitOfMeasure::METRE;
2949
1.93k
                    if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
2950
1.93k
                        ci_equal(parentNodeName, WKTConstants::VERTCS)) {
2951
1.93k
                        emitRecoverableMissingUNIT(parentNodeName, unit);
2952
1.93k
                    }
2953
1.93k
                }
2954
2.21k
                if (downDirection) {
2955
0
                    return VerticalCS::create(
2956
0
                        util::PropertyMap(),
2957
0
                        CoordinateSystemAxis::create(
2958
0
                            util::PropertyMap().set(IdentifiedObject::NAME_KEY,
2959
0
                                                    "depth"),
2960
0
                            "D", AxisDirection::DOWN, unit));
2961
0
                }
2962
2.21k
                return VerticalCS::createGravityRelatedHeight(unit);
2963
2.21k
            }
2964
3.79k
        } else if (ci_equal(parentNodeName, WKTConstants::LOCAL_CS)) {
2965
3.79k
            if (axisCount == 0) {
2966
3.58k
                auto unit =
2967
3.58k
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2968
3.58k
                if (unit == UnitOfMeasure::NONE) {
2969
3.32k
                    unit = UnitOfMeasure::METRE;
2970
3.32k
                }
2971
3.58k
                return CartesianCS::createEastingNorthing(unit);
2972
3.58k
            } else if (axisCount == 1) {
2973
87
                csTypeCStr = VerticalCS::WKT2_TYPE;
2974
123
            } else if (axisCount == 2 || axisCount == 3) {
2975
120
                csTypeCStr = CartesianCS::WKT2_TYPE;
2976
120
            } else {
2977
3
                throw ParsingException(
2978
3
                    "buildCS: unexpected AXIS count for LOCAL_CS");
2979
3
            }
2980
3.79k
        } else if (ci_equal(parentNodeName, WKTConstants::BASEPARAMCRS)) {
2981
0
            csTypeCStr = ParametricCS::WKT2_TYPE;
2982
0
            if (axisCount == 0) {
2983
0
                auto unit =
2984
0
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2985
0
                if (unit == UnitOfMeasure::NONE) {
2986
0
                    unit = UnitOfMeasure("unknown", 1,
2987
0
                                         UnitOfMeasure::Type::PARAMETRIC);
2988
0
                }
2989
0
                return ParametricCS::create(
2990
0
                    emptyPropertyMap,
2991
0
                    CoordinateSystemAxis::create(
2992
0
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
2993
0
                                          "unknown parametric"),
2994
0
                        std::string(), AxisDirection::UNSPECIFIED, unit));
2995
0
            }
2996
0
        } else if (ci_equal(parentNodeName, WKTConstants::BASETIMECRS)) {
2997
0
            csTypeCStr = TemporalCS::WKT2_2015_TYPE;
2998
0
            if (axisCount == 0) {
2999
0
                auto unit =
3000
0
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::TIME);
3001
0
                if (unit == UnitOfMeasure::NONE) {
3002
0
                    unit =
3003
0
                        UnitOfMeasure("unknown", 1, UnitOfMeasure::Type::TIME);
3004
0
                }
3005
0
                return DateTimeTemporalCS::create(
3006
0
                    emptyPropertyMap,
3007
0
                    CoordinateSystemAxis::create(
3008
0
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
3009
0
                                          "unknown temporal"),
3010
0
                        std::string(), AxisDirection::FUTURE, unit));
3011
0
            }
3012
0
        } else {
3013
            // Shouldn't happen normally
3014
0
            throw ParsingException(
3015
0
                concat("buildCS: unexpected parent node: ", parentNodeName));
3016
0
        }
3017
254
        csType = csTypeCStr;
3018
254
    }
3019
3020
257
    if (axisCount != 1 && axisCount != 2 && axisCount != 3) {
3021
0
        throw buildParsingExceptionInvalidAxisCount(csType);
3022
0
    }
3023
257
    if (numberOfAxis != axisCount) {
3024
3
        throw ParsingException("buildCS: declared number of axis by CS node "
3025
3
                               "and number of AXIS are inconsistent");
3026
3
    }
3027
3028
254
    const auto unitType =
3029
254
        ci_equal(csType, EllipsoidalCS::WKT2_TYPE)
3030
254
            ? UnitOfMeasure::Type::ANGULAR
3031
254
        : ci_equal(csType, OrdinalCS::WKT2_TYPE) ? UnitOfMeasure::Type::NONE
3032
242
        : ci_equal(csType, ParametricCS::WKT2_TYPE)
3033
242
            ? UnitOfMeasure::Type::PARAMETRIC
3034
242
        : ci_equal(csType, CartesianCS::WKT2_TYPE) ||
3035
105
                ci_equal(csType, VerticalCS::WKT2_TYPE) ||
3036
0
                ci_equal(csType, AffineCS::WKT2_TYPE)
3037
242
            ? UnitOfMeasure::Type::LINEAR
3038
242
        : (ci_equal(csType, TemporalCS::WKT2_2015_TYPE) ||
3039
0
           ci_equal(csType, DateTimeTemporalCS::WKT2_2019_TYPE) ||
3040
0
           ci_equal(csType, TemporalCountCS::WKT2_2019_TYPE) ||
3041
0
           ci_equal(csType, TemporalMeasureCS::WKT2_2019_TYPE))
3042
0
            ? UnitOfMeasure::Type::TIME
3043
0
            : UnitOfMeasure::Type::UNKNOWN;
3044
254
    UnitOfMeasure unit = buildUnitInSubNode(parentNode, unitType);
3045
3046
254
    if (unit == UnitOfMeasure::NONE) {
3047
25
        if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
3048
18
            ci_equal(parentNodeName, WKTConstants::VERTCS)) {
3049
18
            unit = UnitOfMeasure::METRE;
3050
18
            emitRecoverableMissingUNIT(parentNodeName, unit);
3051
18
        }
3052
25
    }
3053
3054
254
    std::vector<CoordinateSystemAxisNNPtr> axisList;
3055
696
    for (int i = 0; i < axisCount; i++) {
3056
442
        axisList.emplace_back(
3057
442
            buildAxis(parentNode->GP()->lookForChild(WKTConstants::AXIS, i),
3058
442
                      unit, unitType, isGeocentric, i + 1));
3059
442
    }
3060
3061
254
    const PropertyMap &csMap = emptyPropertyMap;
3062
254
    if (ci_equal(csType, EllipsoidalCS::WKT2_TYPE)) {
3063
7
        if (axisCount == 2) {
3064
4
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1]);
3065
4
        } else if (axisCount == 3) {
3066
2
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1],
3067
2
                                         axisList[2]);
3068
2
        }
3069
247
    } else if (ci_equal(csType, CartesianCS::WKT2_TYPE)) {
3070
109
        if (axisCount == 2) {
3071
56
            return CartesianCS::create(csMap, axisList[0], axisList[1]);
3072
56
        } else if (axisCount == 3) {
3073
51
            return CartesianCS::create(csMap, axisList[0], axisList[1],
3074
51
                                       axisList[2]);
3075
51
        }
3076
138
    } else if (ci_equal(csType, AffineCS::WKT2_TYPE)) {
3077
0
        if (axisCount == 2) {
3078
0
            return AffineCS::create(csMap, axisList[0], axisList[1]);
3079
0
        } else if (axisCount == 3) {
3080
0
            return AffineCS::create(csMap, axisList[0], axisList[1],
3081
0
                                    axisList[2]);
3082
0
        }
3083
138
    } else if (ci_equal(csType, VerticalCS::WKT2_TYPE)) {
3084
56
        if (axisCount == 1) {
3085
56
            return VerticalCS::create(csMap, axisList[0]);
3086
56
        }
3087
82
    } else if (ci_equal(csType, SphericalCS::WKT2_TYPE)) {
3088
0
        if (axisCount == 2) {
3089
            // Extension to ISO19111 to support (planet)-ocentric CS with
3090
            // geocentric latitude
3091
0
            return SphericalCS::create(csMap, axisList[0], axisList[1]);
3092
0
        } else if (axisCount == 3) {
3093
0
            return SphericalCS::create(csMap, axisList[0], axisList[1],
3094
0
                                       axisList[2]);
3095
0
        }
3096
82
    } else if (ci_equal(csType, OrdinalCS::WKT2_TYPE)) { // WKT2-2019
3097
0
        return OrdinalCS::create(csMap, axisList);
3098
82
    } else if (ci_equal(csType, ParametricCS::WKT2_TYPE)) {
3099
0
        if (axisCount == 1) {
3100
0
            return ParametricCS::create(csMap, axisList[0]);
3101
0
        }
3102
82
    } else if (ci_equal(csType, TemporalCS::WKT2_2015_TYPE)) {
3103
0
        if (axisCount == 1) {
3104
0
            if (isNull(
3105
0
                    parentNode->GP()->lookForChild(WKTConstants::TIMEUNIT)) &&
3106
0
                isNull(parentNode->GP()->lookForChild(WKTConstants::UNIT))) {
3107
0
                return DateTimeTemporalCS::create(csMap, axisList[0]);
3108
0
            } else {
3109
                // Default to TemporalMeasureCS
3110
                // TemporalCount could also be possible
3111
0
                return TemporalMeasureCS::create(csMap, axisList[0]);
3112
0
            }
3113
0
        }
3114
82
    } else if (ci_equal(csType, DateTimeTemporalCS::WKT2_2019_TYPE)) {
3115
0
        if (axisCount == 1) {
3116
0
            return DateTimeTemporalCS::create(csMap, axisList[0]);
3117
0
        }
3118
82
    } else if (ci_equal(csType, TemporalCountCS::WKT2_2019_TYPE)) {
3119
0
        if (axisCount == 1) {
3120
0
            return TemporalCountCS::create(csMap, axisList[0]);
3121
0
        }
3122
82
    } else if (ci_equal(csType, TemporalMeasureCS::WKT2_2019_TYPE)) {
3123
0
        if (axisCount == 1) {
3124
0
            return TemporalMeasureCS::create(csMap, axisList[0]);
3125
0
        }
3126
82
    } else {
3127
82
        throw ParsingException(concat("unhandled CS type: ", csType));
3128
82
    }
3129
3
    throw buildParsingExceptionInvalidAxisCount(csType);
3130
254
}
3131
3132
// ---------------------------------------------------------------------------
3133
3134
std::string
3135
5.14k
WKTParser::Private::getExtensionProj4(const WKTNode::Private *nodeP) {
3136
5.14k
    auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
3137
5.14k
    const auto &extensionChildren = extensionNode->GP()->children();
3138
5.14k
    if (extensionChildren.size() == 2) {
3139
81
        if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
3140
0
            return stripQuotes(extensionChildren[1]);
3141
0
        }
3142
81
    }
3143
5.14k
    return std::string();
3144
5.14k
}
3145
3146
// ---------------------------------------------------------------------------
3147
3148
void WKTParser::Private::addExtensionProj4ToProp(const WKTNode::Private *nodeP,
3149
4.04k
                                                 PropertyMap &props) {
3150
4.04k
    const auto extensionProj4(getExtensionProj4(nodeP));
3151
4.04k
    if (!extensionProj4.empty()) {
3152
0
        props.set("EXTENSION_PROJ4", extensionProj4);
3153
0
    }
3154
4.04k
}
3155
3156
// ---------------------------------------------------------------------------
3157
3158
GeodeticCRSNNPtr
3159
WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node,
3160
3.21k
                                     bool forceGeocentricIfNoCs) {
3161
3.21k
    const auto *nodeP = node->GP();
3162
3.21k
    auto &datumNode = nodeP->lookForChild(
3163
3.21k
        WKTConstants::DATUM, WKTConstants::GEODETICDATUM, WKTConstants::TRF);
3164
3.21k
    auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE);
3165
3.21k
    if (isNull(datumNode) && isNull(ensembleNode)) {
3166
45
        throw ParsingException("Missing DATUM or ENSEMBLE node");
3167
45
    }
3168
3169
    // Do that now so that esriStyle_ can be set before buildPrimeMeridian()
3170
3.16k
    auto props = buildProperties(node);
3171
3172
3.16k
    auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC);
3173
3174
3.16k
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
3175
3.16k
    const auto &nodeName = nodeP->value();
3176
3.16k
    if (isNull(csNode) && !ci_equal(nodeName, WKTConstants::GEOGCS) &&
3177
105
        !ci_equal(nodeName, WKTConstants::GEOCCS) &&
3178
0
        !ci_equal(nodeName, WKTConstants::BASEGEODCRS) &&
3179
0
        !ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) {
3180
0
        ThrowMissing(WKTConstants::CS_);
3181
0
    }
3182
3183
3.16k
    auto &primeMeridianNode =
3184
3.16k
        nodeP->lookForChild(WKTConstants::PRIMEM, WKTConstants::PRIMEMERIDIAN);
3185
3.16k
    if (isNull(primeMeridianNode)) {
3186
        // PRIMEM is required in WKT1
3187
2.87k
        if (ci_equal(nodeName, WKTConstants::GEOGCS) ||
3188
2.87k
            ci_equal(nodeName, WKTConstants::GEOCCS)) {
3189
2.87k
            emitRecoverableWarning(nodeName + " should have a PRIMEM node");
3190
2.87k
        }
3191
2.87k
    }
3192
3193
3.16k
    auto angularUnit =
3194
3.16k
        buildUnitInSubNode(node, ci_equal(nodeName, WKTConstants::GEOGCS)
3195
3.16k
                                     ? UnitOfMeasure::Type::ANGULAR
3196
3.16k
                                     : UnitOfMeasure::Type::UNKNOWN);
3197
3.16k
    if (angularUnit.type() != UnitOfMeasure::Type::ANGULAR) {
3198
3.02k
        angularUnit = UnitOfMeasure::NONE;
3199
3.02k
    }
3200
3201
3.16k
    auto primeMeridian =
3202
3.16k
        !isNull(primeMeridianNode)
3203
3.16k
            ? buildPrimeMeridian(primeMeridianNode, angularUnit)
3204
3.16k
            : PrimeMeridian::GREENWICH;
3205
3.16k
    if (angularUnit == UnitOfMeasure::NONE) {
3206
3.02k
        angularUnit = primeMeridian->longitude().unit();
3207
3.02k
    }
3208
3209
3.16k
    addExtensionProj4ToProp(nodeP, props);
3210
3211
    // No explicit AXIS node ? (WKT1)
3212
3.16k
    if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) {
3213
3.12k
        props.set("IMPLICIT_CS", true);
3214
3.12k
    }
3215
3216
3.16k
    const std::string crsName = stripQuotes(nodeP->children()[0]);
3217
3.16k
    if (esriStyle_ && dbContext_) {
3218
615
        std::string outTableName;
3219
615
        std::string authNameFromAlias;
3220
615
        std::string codeFromAlias;
3221
615
        auto authFactory =
3222
615
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
3223
615
        auto officialName = authFactory->getOfficialNameFromAlias(
3224
615
            crsName, "geodetic_crs", "ESRI", false, outTableName,
3225
615
            authNameFromAlias, codeFromAlias);
3226
615
        if (!officialName.empty()) {
3227
52
            props.set(IdentifiedObject::NAME_KEY, officialName);
3228
52
        }
3229
615
    }
3230
3231
3.16k
    auto datum =
3232
3.16k
        !isNull(datumNode)
3233
3.16k
            ? buildGeodeticReferenceFrame(datumNode, primeMeridian, dynamicNode)
3234
3.02k
                  .as_nullable()
3235
3.16k
            : nullptr;
3236
3.16k
    auto datumEnsemble =
3237
3.16k
        !isNull(ensembleNode)
3238
3.16k
            ? buildDatumEnsemble(ensembleNode, primeMeridian, true)
3239
127
                  .as_nullable()
3240
3.16k
            : nullptr;
3241
3.16k
    auto cs = buildCS(csNode, node, angularUnit);
3242
3243
    // If there's no CS[] node, typically for a BASEGEODCRS of a projected CRS,
3244
    // in a few rare cases, this might be a Geocentric CRS, and thus a
3245
    // Cartesian CS, and not the ellipsoidalCS we assumed above. The only way
3246
    // to figure that is to resolve the CRS from its code...
3247
3.16k
    if (isNull(csNode) && dbContext_ &&
3248
2.25k
        ci_equal(nodeName, WKTConstants::BASEGEODCRS)) {
3249
0
        const auto &nodeChildren = nodeP->children();
3250
0
        for (const auto &subNode : nodeChildren) {
3251
0
            const auto &subNodeName(subNode->GP()->value());
3252
0
            if (ci_equal(subNodeName, WKTConstants::ID) ||
3253
0
                ci_equal(subNodeName, WKTConstants::AUTHORITY)) {
3254
0
                auto id = buildId(node, subNode, true, false);
3255
0
                if (id) {
3256
0
                    try {
3257
0
                        auto authFactory = AuthorityFactory::create(
3258
0
                            NN_NO_CHECK(dbContext_), *id->codeSpace());
3259
0
                        auto dbCRS = authFactory->createGeodeticCRS(id->code());
3260
0
                        cs = dbCRS->coordinateSystem();
3261
0
                    } catch (const util::Exception &) {
3262
0
                    }
3263
0
                }
3264
0
            }
3265
0
        }
3266
0
    }
3267
3.16k
    if (forceGeocentricIfNoCs && isNull(csNode) &&
3268
0
        ci_equal(nodeName, WKTConstants::BASEGEODCRS)) {
3269
0
        cs = cs::CartesianCS::createGeocentric(UnitOfMeasure::METRE);
3270
0
    }
3271
3272
3.16k
    auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs);
3273
3.16k
    if (ellipsoidalCS) {
3274
2.92k
        if (ci_equal(nodeName, WKTConstants::GEOCCS)) {
3275
0
            throw ParsingException("ellipsoidal CS not expected in GEOCCS");
3276
0
        }
3277
2.92k
        try {
3278
2.92k
            auto crs = GeographicCRS::create(props, datum, datumEnsemble,
3279
2.92k
                                             NN_NO_CHECK(ellipsoidalCS));
3280
            // In case of missing CS node, or to check it, query the coordinate
3281
            // system from the DB if possible (typically for the baseCRS of a
3282
            // ProjectedCRS)
3283
2.92k
            if (!crs->identifiers().empty() && dbContext_) {
3284
127
                GeographicCRSPtr dbCRS;
3285
127
                try {
3286
127
                    const auto &id = crs->identifiers()[0];
3287
127
                    auto authFactory = AuthorityFactory::create(
3288
127
                        NN_NO_CHECK(dbContext_), *id->codeSpace());
3289
127
                    dbCRS = authFactory->createGeographicCRS(id->code())
3290
127
                                .as_nullable();
3291
127
                } catch (const util::Exception &) {
3292
127
                }
3293
127
                if (dbCRS &&
3294
0
                    (!isNull(csNode) ||
3295
0
                     node->countChildrenOfName(WKTConstants::AXIS) != 0) &&
3296
0
                    !ellipsoidalCS->_isEquivalentTo(
3297
0
                        dbCRS->coordinateSystem().get(),
3298
0
                        util::IComparable::Criterion::EQUIVALENT)) {
3299
0
                    if (unsetIdentifiersIfIncompatibleDef_) {
3300
0
                        emitRecoverableWarning(
3301
0
                            "Coordinate system of GeographicCRS in the WKT "
3302
0
                            "definition is different from the one of the "
3303
0
                            "authority. Unsetting the identifier to avoid "
3304
0
                            "confusion");
3305
0
                        props.unset(Identifier::CODESPACE_KEY);
3306
0
                        props.unset(Identifier::AUTHORITY_KEY);
3307
0
                        props.unset(IdentifiedObject::IDENTIFIERS_KEY);
3308
0
                    }
3309
0
                    crs = GeographicCRS::create(props, datum, datumEnsemble,
3310
0
                                                NN_NO_CHECK(ellipsoidalCS));
3311
127
                } else if (dbCRS) {
3312
0
                    auto csFromDB = dbCRS->coordinateSystem();
3313
0
                    auto csFromDBAltered = csFromDB;
3314
0
                    if (!isNull(nodeP->lookForChild(WKTConstants::UNIT))) {
3315
0
                        csFromDBAltered =
3316
0
                            csFromDB->alterAngularUnit(angularUnit);
3317
0
                        if (unsetIdentifiersIfIncompatibleDef_ &&
3318
0
                            !csFromDBAltered->_isEquivalentTo(
3319
0
                                csFromDB.get(),
3320
0
                                util::IComparable::Criterion::EQUIVALENT)) {
3321
0
                            emitRecoverableWarning(
3322
0
                                "Coordinate system of GeographicCRS in the WKT "
3323
0
                                "definition is different from the one of the "
3324
0
                                "authority. Unsetting the identifier to avoid "
3325
0
                                "confusion");
3326
0
                            props.unset(Identifier::CODESPACE_KEY);
3327
0
                            props.unset(Identifier::AUTHORITY_KEY);
3328
0
                            props.unset(IdentifiedObject::IDENTIFIERS_KEY);
3329
0
                        }
3330
0
                    }
3331
0
                    crs = GeographicCRS::create(props, datum, datumEnsemble,
3332
0
                                                csFromDBAltered);
3333
0
                }
3334
127
            }
3335
2.92k
            return crs;
3336
2.92k
        } catch (const util::Exception &e) {
3337
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
3338
0
                                   e.what());
3339
0
        }
3340
2.92k
    } else if (ci_equal(nodeName, WKTConstants::GEOGCRS) ||
3341
89
               ci_equal(nodeName, WKTConstants::GEOGRAPHICCRS) ||
3342
89
               ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) {
3343
        // This is a WKT2-2019 GeographicCRS. An ellipsoidal CS is expected
3344
0
        throw ParsingException(concat("ellipsoidal CS expected, but found ",
3345
0
                                      cs->getWKT2Type(true)));
3346
0
    }
3347
3348
237
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
3349
237
    if (cartesianCS) {
3350
89
        if (cartesianCS->axisList().size() != 3) {
3351
0
            throw ParsingException(
3352
0
                "Cartesian CS for a GeodeticCRS should have 3 axis");
3353
0
        }
3354
89
        try {
3355
89
            return GeodeticCRS::create(props, datum, datumEnsemble,
3356
89
                                       NN_NO_CHECK(cartesianCS));
3357
89
        } catch (const util::Exception &e) {
3358
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
3359
0
                                   e.what());
3360
0
        }
3361
89
    }
3362
3363
148
    auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
3364
148
    if (sphericalCS) {
3365
0
        try {
3366
0
            return GeodeticCRS::create(props, datum, datumEnsemble,
3367
0
                                       NN_NO_CHECK(sphericalCS));
3368
0
        } catch (const util::Exception &e) {
3369
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
3370
0
                                   e.what());
3371
0
        }
3372
0
    }
3373
3374
148
    throw ParsingException(
3375
148
        concat("unhandled CS type: ", cs->getWKT2Type(true)));
3376
148
}
3377
3378
// ---------------------------------------------------------------------------
3379
3380
1
CRSNNPtr WKTParser::Private::buildDerivedGeodeticCRS(const WKTNodeNNPtr &node) {
3381
1
    const auto *nodeP = node->GP();
3382
1
    auto &baseGeodCRSNode = nodeP->lookForChild(WKTConstants::BASEGEODCRS,
3383
1
                                                WKTConstants::BASEGEOGCRS);
3384
    // given the constraints enforced on calling code path
3385
1
    assert(!isNull(baseGeodCRSNode));
3386
3387
1
    auto &derivingConversionNode =
3388
1
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
3389
1
    if (isNull(derivingConversionNode)) {
3390
1
        ThrowMissing(WKTConstants::DERIVINGCONVERSION);
3391
1
    }
3392
0
    auto derivingConversion = buildConversion(
3393
0
        derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
3394
3395
0
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
3396
0
    if (isNull(csNode)) {
3397
0
        ThrowMissing(WKTConstants::CS_);
3398
0
    }
3399
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
3400
3401
0
    bool forceGeocentricIfNoCs = false;
3402
0
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
3403
0
    if (cartesianCS) {
3404
0
        if (cartesianCS->axisList().size() != 3) {
3405
0
            throw ParsingException(
3406
0
                "Cartesian CS for a GeodeticCRS should have 3 axis");
3407
0
        }
3408
0
        const int methodCode = derivingConversion->method()->getEPSGCode();
3409
0
        if ((methodCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
3410
0
             methodCode ==
3411
0
                 EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC ||
3412
0
             methodCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
3413
0
             methodCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
3414
0
             methodCode ==
3415
0
                 EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
3416
0
             methodCode ==
3417
0
                 EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC) &&
3418
0
            nodeP->lookForChild(WKTConstants::BASEGEODCRS) != nullptr) {
3419
0
            forceGeocentricIfNoCs = true;
3420
0
        }
3421
0
    }
3422
0
    auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode, forceGeocentricIfNoCs);
3423
3424
0
    auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs);
3425
0
    if (ellipsoidalCS) {
3426
3427
0
        if (ellipsoidalCS->axisList().size() == 3 &&
3428
0
            baseGeodCRS->coordinateSystem()->axisList().size() == 2) {
3429
0
            baseGeodCRS =
3430
0
                NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
3431
0
                    baseGeodCRS->promoteTo3D(std::string(), dbContext_)));
3432
0
        }
3433
3434
0
        return DerivedGeographicCRS::create(buildProperties(node), baseGeodCRS,
3435
0
                                            derivingConversion,
3436
0
                                            NN_NO_CHECK(ellipsoidalCS));
3437
0
    } else if (ci_equal(nodeP->value(), WKTConstants::GEOGCRS)) {
3438
        // This is a WKT2-2019 GeographicCRS. An ellipsoidal CS is expected
3439
0
        throw ParsingException(concat("ellipsoidal CS expected, but found ",
3440
0
                                      cs->getWKT2Type(true)));
3441
0
    }
3442
3443
0
    if (cartesianCS) {
3444
0
        return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS,
3445
0
                                          derivingConversion,
3446
0
                                          NN_NO_CHECK(cartesianCS));
3447
0
    }
3448
3449
0
    auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
3450
0
    if (sphericalCS) {
3451
0
        return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS,
3452
0
                                          derivingConversion,
3453
0
                                          NN_NO_CHECK(sphericalCS));
3454
0
    }
3455
3456
0
    throw ParsingException(
3457
0
        concat("unhandled CS type: ", cs->getWKT2Type(true)));
3458
0
}
3459
3460
// ---------------------------------------------------------------------------
3461
3462
UnitOfMeasure WKTParser::Private::guessUnitForParameter(
3463
    const std::string &paramName, const UnitOfMeasure &defaultLinearUnit,
3464
13.6k
    const UnitOfMeasure &defaultAngularUnit) {
3465
13.6k
    UnitOfMeasure unit;
3466
    // scale must be first because of 'Scale factor on pseudo standard parallel'
3467
13.6k
    if (ci_find(paramName, "scale") != std::string::npos ||
3468
13.1k
        ci_find(paramName, "scaling factor") != std::string::npos) {
3469
516
        unit = UnitOfMeasure::SCALE_UNITY;
3470
13.1k
    } else if (ci_find(paramName, "latitude") != std::string::npos ||
3471
12.9k
               ci_find(paramName, "longitude") != std::string::npos ||
3472
12.7k
               ci_find(paramName, "meridian") != std::string::npos ||
3473
12.6k
               ci_find(paramName, "parallel") != std::string::npos ||
3474
11.6k
               ci_find(paramName, "azimuth") != std::string::npos ||
3475
11.5k
               ci_find(paramName, "angle") != std::string::npos ||
3476
11.2k
               ci_find(paramName, "heading") != std::string::npos ||
3477
10.9k
               ci_find(paramName, "rotation") != std::string::npos) {
3478
2.19k
        unit = defaultAngularUnit;
3479
10.9k
    } else if (ci_find(paramName, "easting") != std::string::npos ||
3480
10.8k
               ci_find(paramName, "northing") != std::string::npos ||
3481
10.7k
               ci_find(paramName, "height") != std::string::npos) {
3482
207
        unit = defaultLinearUnit;
3483
207
    }
3484
13.6k
    return unit;
3485
13.6k
}
3486
3487
// ---------------------------------------------------------------------------
3488
3489
static bool
3490
4.47k
isEPSGCodeForInterpolationParameter(const OperationParameterNNPtr &parameter) {
3491
4.47k
    const auto &name = parameter->nameStr();
3492
4.47k
    const auto epsgCode = parameter->getEPSGCode();
3493
4.47k
    return name == EPSG_NAME_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
3494
4.45k
           epsgCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
3495
4.14k
           name == EPSG_NAME_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS ||
3496
4.14k
           epsgCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS;
3497
4.47k
}
3498
3499
// ---------------------------------------------------------------------------
3500
3501
3.11k
static bool isIntegerParameter(const OperationParameterNNPtr &parameter) {
3502
3.11k
    return isEPSGCodeForInterpolationParameter(parameter);
3503
3.11k
}
3504
3505
// ---------------------------------------------------------------------------
3506
3507
void WKTParser::Private::consumeParameters(
3508
    const WKTNodeNNPtr &node, bool isAbridged,
3509
    std::vector<OperationParameterNNPtr> &parameters,
3510
    std::vector<ParameterValueNNPtr> &values,
3511
    const UnitOfMeasure &defaultLinearUnit,
3512
1.86k
    const UnitOfMeasure &defaultAngularUnit) {
3513
12.9k
    for (const auto &childNode : node->GP()->children()) {
3514
12.9k
        const auto &childNodeChildren = childNode->GP()->children();
3515
12.9k
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
3516
3.29k
            if (childNodeChildren.size() < 2) {
3517
30
                ThrowNotEnoughChildren(childNode->GP()->value());
3518
30
            }
3519
3.26k
            parameters.push_back(
3520
3.26k
                OperationParameter::create(buildProperties(childNode)));
3521
3.26k
            const auto &paramValue = childNodeChildren[1]->GP()->value();
3522
3.26k
            if (!paramValue.empty() && paramValue[0] == '"') {
3523
99
                values.push_back(
3524
99
                    ParameterValue::create(stripQuotes(childNodeChildren[1])));
3525
3.16k
            } else {
3526
3.16k
                try {
3527
3.16k
                    double val = asDouble(childNodeChildren[1]);
3528
3.16k
                    auto unit = buildUnitInSubNode(childNode);
3529
3.16k
                    if (unit == UnitOfMeasure::NONE) {
3530
3.11k
                        const auto &paramName =
3531
3.11k
                            childNodeChildren[0]->GP()->value();
3532
3.11k
                        unit = guessUnitForParameter(
3533
3.11k
                            paramName, defaultLinearUnit, defaultAngularUnit);
3534
3.11k
                    }
3535
3536
3.16k
                    if (isAbridged) {
3537
0
                        const auto &paramName = parameters.back()->nameStr();
3538
0
                        int paramEPSGCode = 0;
3539
0
                        const auto &paramIds = parameters.back()->identifiers();
3540
0
                        if (paramIds.size() == 1 &&
3541
0
                            ci_equal(*(paramIds[0]->codeSpace()),
3542
0
                                     Identifier::EPSG)) {
3543
0
                            paramEPSGCode = ::atoi(paramIds[0]->code().c_str());
3544
0
                        }
3545
0
                        const common::UnitOfMeasure *pUnit = nullptr;
3546
0
                        if (OperationParameterValue::convertFromAbridged(
3547
0
                                paramName, val, pUnit, paramEPSGCode)) {
3548
0
                            unit = *pUnit;
3549
0
                            parameters.back() = OperationParameter::create(
3550
0
                                buildProperties(childNode)
3551
0
                                    .set(Identifier::CODESPACE_KEY,
3552
0
                                         Identifier::EPSG)
3553
0
                                    .set(Identifier::CODE_KEY, paramEPSGCode));
3554
0
                        }
3555
0
                    }
3556
3557
3.16k
                    if (isIntegerParameter(parameters.back())) {
3558
252
                        values.push_back(ParameterValue::create(
3559
252
                            std::stoi(childNodeChildren[1]->GP()->value())));
3560
2.91k
                    } else {
3561
2.91k
                        values.push_back(
3562
2.91k
                            ParameterValue::create(Measure(val, unit)));
3563
2.91k
                    }
3564
3.16k
                } catch (const std::exception &) {
3565
58
                    throw ParsingException(concat(
3566
58
                        "unhandled parameter value type : ", paramValue));
3567
58
                }
3568
3.16k
            }
3569
9.69k
        } else if (ci_equal(childNode->GP()->value(),
3570
9.69k
                            WKTConstants::PARAMETERFILE)) {
3571
179
            if (childNodeChildren.size() < 2) {
3572
0
                ThrowNotEnoughChildren(childNode->GP()->value());
3573
0
            }
3574
179
            parameters.push_back(
3575
179
                OperationParameter::create(buildProperties(childNode)));
3576
179
            values.push_back(ParameterValue::createFilename(
3577
179
                stripQuotes(childNodeChildren[1])));
3578
179
        }
3579
12.9k
    }
3580
1.86k
}
3581
3582
// ---------------------------------------------------------------------------
3583
3584
static CRSPtr dealWithEPSGCodeForInterpolationCRSParameter(
3585
    DatabaseContextPtr &dbContext,
3586
    std::vector<OperationParameterNNPtr> &parameters,
3587
    std::vector<ParameterValueNNPtr> &values);
3588
3589
ConversionNNPtr
3590
WKTParser::Private::buildConversion(const WKTNodeNNPtr &node,
3591
                                    const UnitOfMeasure &defaultLinearUnit,
3592
1.88k
                                    const UnitOfMeasure &defaultAngularUnit) {
3593
1.88k
    auto &methodNode = node->GP()->lookForChild(WKTConstants::METHOD,
3594
1.88k
                                                WKTConstants::PROJECTION);
3595
1.88k
    if (isNull(methodNode)) {
3596
21
        ThrowMissing(WKTConstants::METHOD);
3597
21
    }
3598
1.86k
    if (methodNode->GP()->childrenSize() == 0) {
3599
2
        ThrowNotEnoughChildren(WKTConstants::METHOD);
3600
2
    }
3601
3602
1.86k
    std::vector<OperationParameterNNPtr> parameters;
3603
1.86k
    std::vector<ParameterValueNNPtr> values;
3604
1.86k
    consumeParameters(node, false, parameters, values, defaultLinearUnit,
3605
1.86k
                      defaultAngularUnit);
3606
3607
1.86k
    auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
3608
1.86k
        dbContext_, parameters, values);
3609
3610
1.86k
    auto &convProps = buildProperties(node);
3611
1.86k
    auto &methodProps = buildProperties(methodNode);
3612
1.86k
    std::string convName;
3613
1.86k
    std::string methodName;
3614
1.86k
    if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) &&
3615
1.76k
        methodProps.getStringValue(IdentifiedObject::NAME_KEY, methodName) &&
3616
1.76k
        starts_with(convName, "Inverse of ") &&
3617
0
        starts_with(methodName, "Inverse of ")) {
3618
3619
0
        auto &invConvProps = buildProperties(node, true);
3620
0
        auto &invMethodProps = buildProperties(methodNode, true);
3621
0
        auto conv = NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(
3622
0
            Conversion::create(invConvProps, invMethodProps, parameters, values)
3623
0
                ->inverse()));
3624
0
        if (interpolationCRS)
3625
0
            conv->setInterpolationCRS(interpolationCRS);
3626
0
        return conv;
3627
0
    }
3628
1.86k
    auto conv = Conversion::create(convProps, methodProps, parameters, values);
3629
1.86k
    if (interpolationCRS)
3630
0
        conv->setInterpolationCRS(interpolationCRS);
3631
1.86k
    return conv;
3632
1.86k
}
3633
3634
// ---------------------------------------------------------------------------
3635
3636
static CRSPtr dealWithEPSGCodeForInterpolationCRSParameter(
3637
    DatabaseContextPtr &dbContext,
3638
    std::vector<OperationParameterNNPtr> &parameters,
3639
1.77k
    std::vector<ParameterValueNNPtr> &values) {
3640
    // Transform EPSG hacky PARAMETER["EPSG code for Interpolation CRS",
3641
    // crs_epsg_code] into proper interpolation CRS
3642
1.77k
    if (dbContext != nullptr) {
3643
2.87k
        for (size_t i = 0; i < parameters.size(); ++i) {
3644
1.36k
            if (isEPSGCodeForInterpolationParameter(parameters[i])) {
3645
80
                const int code = values[i]->integerValue();
3646
80
                try {
3647
80
                    auto authFactory = AuthorityFactory::create(
3648
80
                        NN_NO_CHECK(dbContext), Identifier::EPSG);
3649
80
                    auto interpolationCRS =
3650
80
                        authFactory
3651
80
                            ->createGeographicCRS(internal::toString(code))
3652
80
                            .as_nullable();
3653
80
                    parameters.erase(parameters.begin() + i);
3654
80
                    values.erase(values.begin() + i);
3655
80
                    return interpolationCRS;
3656
80
                } catch (const util::Exception &) {
3657
80
                }
3658
80
            }
3659
1.36k
        }
3660
1.50k
    }
3661
1.77k
    return nullptr;
3662
1.77k
}
3663
3664
// ---------------------------------------------------------------------------
3665
3666
TransformationNNPtr
3667
0
WKTParser::Private::buildCoordinateOperation(const WKTNodeNNPtr &node) {
3668
0
    const auto *nodeP = node->GP();
3669
0
    auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD);
3670
0
    if (isNull(methodNode)) {
3671
0
        ThrowMissing(WKTConstants::METHOD);
3672
0
    }
3673
0
    if (methodNode->GP()->childrenSize() == 0) {
3674
0
        ThrowNotEnoughChildren(WKTConstants::METHOD);
3675
0
    }
3676
3677
0
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
3678
0
    if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) {
3679
0
        ThrowMissing(WKTConstants::SOURCECRS);
3680
0
    }
3681
0
    auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
3682
0
    if (!sourceCRS) {
3683
0
        throw ParsingException("Invalid content in SOURCECRS node");
3684
0
    }
3685
3686
0
    auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
3687
0
    if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) {
3688
0
        ThrowMissing(WKTConstants::TARGETCRS);
3689
0
    }
3690
0
    auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]);
3691
0
    if (!targetCRS) {
3692
0
        throw ParsingException("Invalid content in TARGETCRS node");
3693
0
    }
3694
3695
0
    auto &interpolationCRSNode =
3696
0
        nodeP->lookForChild(WKTConstants::INTERPOLATIONCRS);
3697
0
    CRSPtr interpolationCRS;
3698
0
    if (/*!isNull(interpolationCRSNode) && */ interpolationCRSNode->GP()
3699
0
            ->childrenSize() == 1) {
3700
0
        interpolationCRS = buildCRS(interpolationCRSNode->GP()->children()[0]);
3701
0
    }
3702
3703
0
    std::vector<OperationParameterNNPtr> parameters;
3704
0
    std::vector<ParameterValueNNPtr> values;
3705
0
    const auto &defaultLinearUnit = UnitOfMeasure::NONE;
3706
0
    const auto &defaultAngularUnit = UnitOfMeasure::NONE;
3707
0
    consumeParameters(node, false, parameters, values, defaultLinearUnit,
3708
0
                      defaultAngularUnit);
3709
3710
0
    if (interpolationCRS == nullptr)
3711
0
        interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
3712
0
            dbContext_, parameters, values);
3713
3714
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
3715
0
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY);
3716
0
    if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) {
3717
0
        accuracies.push_back(PositionalAccuracy::create(
3718
0
            stripQuotes(accuracyNode->GP()->children()[0])));
3719
0
    }
3720
3721
0
    return Transformation::create(buildProperties(node), NN_NO_CHECK(sourceCRS),
3722
0
                                  NN_NO_CHECK(targetCRS), interpolationCRS,
3723
0
                                  buildProperties(methodNode), parameters,
3724
0
                                  values, accuracies);
3725
0
}
3726
3727
// ---------------------------------------------------------------------------
3728
3729
PointMotionOperationNNPtr
3730
0
WKTParser::Private::buildPointMotionOperation(const WKTNodeNNPtr &node) {
3731
0
    const auto *nodeP = node->GP();
3732
0
    auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD);
3733
0
    if (isNull(methodNode)) {
3734
0
        ThrowMissing(WKTConstants::METHOD);
3735
0
    }
3736
0
    if (methodNode->GP()->childrenSize() == 0) {
3737
0
        ThrowNotEnoughChildren(WKTConstants::METHOD);
3738
0
    }
3739
3740
0
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
3741
0
    if (sourceCRSNode->GP()->childrenSize() != 1) {
3742
0
        ThrowMissing(WKTConstants::SOURCECRS);
3743
0
    }
3744
0
    auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
3745
0
    if (!sourceCRS) {
3746
0
        throw ParsingException("Invalid content in SOURCECRS node");
3747
0
    }
3748
3749
0
    std::vector<OperationParameterNNPtr> parameters;
3750
0
    std::vector<ParameterValueNNPtr> values;
3751
0
    const auto &defaultLinearUnit = UnitOfMeasure::NONE;
3752
0
    const auto &defaultAngularUnit = UnitOfMeasure::NONE;
3753
0
    consumeParameters(node, false, parameters, values, defaultLinearUnit,
3754
0
                      defaultAngularUnit);
3755
3756
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
3757
0
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY);
3758
0
    if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) {
3759
0
        accuracies.push_back(PositionalAccuracy::create(
3760
0
            stripQuotes(accuracyNode->GP()->children()[0])));
3761
0
    }
3762
3763
0
    return PointMotionOperation::create(
3764
0
        buildProperties(node), NN_NO_CHECK(sourceCRS),
3765
0
        buildProperties(methodNode), parameters, values, accuracies);
3766
0
}
3767
3768
// ---------------------------------------------------------------------------
3769
3770
ConcatenatedOperationNNPtr
3771
0
WKTParser::Private::buildConcatenatedOperation(const WKTNodeNNPtr &node) {
3772
3773
0
    const auto *nodeP = node->GP();
3774
0
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
3775
0
    if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) {
3776
0
        ThrowMissing(WKTConstants::SOURCECRS);
3777
0
    }
3778
0
    auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
3779
0
    if (!sourceCRS) {
3780
0
        throw ParsingException("Invalid content in SOURCECRS node");
3781
0
    }
3782
3783
0
    auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
3784
0
    if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) {
3785
0
        ThrowMissing(WKTConstants::TARGETCRS);
3786
0
    }
3787
0
    auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]);
3788
0
    if (!targetCRS) {
3789
0
        throw ParsingException("Invalid content in TARGETCRS node");
3790
0
    }
3791
3792
0
    std::vector<CoordinateOperationNNPtr> operations;
3793
0
    for (const auto &childNode : nodeP->children()) {
3794
0
        if (ci_equal(childNode->GP()->value(), WKTConstants::STEP)) {
3795
0
            if (childNode->GP()->childrenSize() != 1) {
3796
0
                throw ParsingException("Invalid content in STEP node");
3797
0
            }
3798
0
            auto op = nn_dynamic_pointer_cast<CoordinateOperation>(
3799
0
                build(childNode->GP()->children()[0]));
3800
0
            if (!op) {
3801
0
                throw ParsingException("Invalid content in STEP node");
3802
0
            }
3803
0
            operations.emplace_back(NN_NO_CHECK(op));
3804
0
        }
3805
0
    }
3806
3807
0
    ConcatenatedOperation::fixSteps(
3808
0
        NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), operations, dbContext_,
3809
0
        /* fixDirectionAllowed = */ true);
3810
3811
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
3812
0
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY);
3813
0
    if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) {
3814
0
        accuracies.push_back(PositionalAccuracy::create(
3815
0
            stripQuotes(accuracyNode->GP()->children()[0])));
3816
0
    }
3817
3818
0
    try {
3819
0
        return ConcatenatedOperation::create(buildProperties(node), operations,
3820
0
                                             accuracies);
3821
0
    } catch (const InvalidOperation &e) {
3822
0
        throw ParsingException(
3823
0
            std::string("Cannot build concatenated operation: ") + e.what());
3824
0
    }
3825
0
}
3826
3827
// ---------------------------------------------------------------------------
3828
3829
bool WKTParser::Private::hasWebMercPROJ4String(
3830
1.02k
    const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode) {
3831
1.02k
    if (projectionNode->GP()->childrenSize() == 0) {
3832
0
        ThrowNotEnoughChildren(WKTConstants::PROJECTION);
3833
0
    }
3834
1.02k
    const std::string wkt1ProjectionName =
3835
1.02k
        stripQuotes(projectionNode->GP()->children()[0]);
3836
3837
1.02k
    auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION);
3838
3839
1.02k
    if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
3840
1.02k
                                               "Mercator_1SP") &&
3841
0
        projCRSNode->countChildrenOfName("center_latitude") == 0) {
3842
3843
        // Hack to detect the hacky way of encodign webmerc in GDAL WKT1
3844
        // with a EXTENSION["PROJ4", "+proj=merc +a=6378137 +b=6378137
3845
        // +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m
3846
        // +nadgrids=@null +wktext +no_defs"] node
3847
0
        if (extensionNode && extensionNode->GP()->childrenSize() == 2 &&
3848
0
            ci_equal(stripQuotes(extensionNode->GP()->children()[0]),
3849
0
                     "PROJ4")) {
3850
0
            std::string projString =
3851
0
                stripQuotes(extensionNode->GP()->children()[1]);
3852
0
            if (projString.find("+proj=merc") != std::string::npos &&
3853
0
                projString.find("+a=6378137") != std::string::npos &&
3854
0
                projString.find("+b=6378137") != std::string::npos &&
3855
0
                projString.find("+lon_0=0") != std::string::npos &&
3856
0
                projString.find("+x_0=0") != std::string::npos &&
3857
0
                projString.find("+y_0=0") != std::string::npos &&
3858
0
                projString.find("+nadgrids=@null") != std::string::npos &&
3859
0
                (projString.find("+lat_ts=") == std::string::npos ||
3860
0
                 projString.find("+lat_ts=0") != std::string::npos) &&
3861
0
                (projString.find("+k=") == std::string::npos ||
3862
0
                 projString.find("+k=1") != std::string::npos) &&
3863
0
                (projString.find("+units=") == std::string::npos ||
3864
0
                 projString.find("+units=m") != std::string::npos)) {
3865
0
                return true;
3866
0
            }
3867
0
        }
3868
0
    }
3869
1.02k
    return false;
3870
1.02k
}
3871
3872
// ---------------------------------------------------------------------------
3873
3874
static const MethodMapping *
3875
selectSphericalOrEllipsoidal(const MethodMapping *mapping,
3876
20.5k
                             const GeodeticCRSNNPtr &baseGeodCRS) {
3877
20.5k
    if (mapping->epsg_code ==
3878
20.5k
            EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL ||
3879
20.5k
        mapping->epsg_code == EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA) {
3880
416
        mapping = getMapping(
3881
416
            baseGeodCRS->ellipsoid()->isSphere()
3882
416
                ? EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL
3883
416
                : EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA);
3884
20.1k
    } else if (mapping->epsg_code ==
3885
20.1k
                   EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL ||
3886
20.1k
               mapping->epsg_code ==
3887
20.1k
                   EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) {
3888
375
        mapping = getMapping(
3889
375
            baseGeodCRS->ellipsoid()->isSphere()
3890
375
                ? EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL
3891
375
                : EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA);
3892
19.7k
    } else if (mapping->epsg_code ==
3893
19.7k
                   EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL ||
3894
19.7k
               mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL) {
3895
8
        mapping =
3896
8
            getMapping(baseGeodCRS->ellipsoid()->isSphere()
3897
8
                           ? EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL
3898
8
                           : EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL);
3899
8
    }
3900
20.5k
    return mapping;
3901
20.5k
}
3902
3903
// ---------------------------------------------------------------------------
3904
3905
const ESRIMethodMapping *WKTParser::Private::getESRIMapping(
3906
    const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode,
3907
1.06k
    std::map<std::string, std::string, ci_less_struct> &mapParamNameToValue) {
3908
1.06k
    const std::string esriProjectionName =
3909
1.06k
        stripQuotes(projectionNode->GP()->children()[0]);
3910
3911
    // Lambert_Conformal_Conic or Krovak may map to different WKT2 methods
3912
    // depending
3913
    // on the parameters / their values
3914
1.06k
    const auto esriMappings = getMappingsFromESRI(esriProjectionName);
3915
1.06k
    if (esriMappings.empty()) {
3916
629
        return nullptr;
3917
629
    }
3918
3919
    // Build a map of present parameters
3920
6.86k
    for (const auto &childNode : projCRSNode->GP()->children()) {
3921
6.86k
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
3922
2.58k
            const auto &childNodeChildren = childNode->GP()->children();
3923
2.58k
            if (childNodeChildren.size() < 2) {
3924
11
                ThrowNotEnoughChildren(WKTConstants::PARAMETER);
3925
11
            }
3926
2.57k
            const std::string parameterName(stripQuotes(childNodeChildren[0]));
3927
2.57k
            const auto &paramValue = childNodeChildren[1]->GP()->value();
3928
2.57k
            mapParamNameToValue[parameterName] = paramValue;
3929
2.57k
        }
3930
6.86k
    }
3931
3932
    // Compare parameters present with the ones expected in the mapping
3933
428
    const ESRIMethodMapping *esriMapping = nullptr;
3934
428
    int bestMatchCount = -1;
3935
717
    for (const auto &mapping : esriMappings) {
3936
717
        int matchCount = 0;
3937
717
        int unmatchCount = 0;
3938
7.03k
        for (const auto *param = mapping->params; param->esri_name; ++param) {
3939
6.31k
            auto iter = mapParamNameToValue.find(param->esri_name);
3940
6.31k
            if (iter != mapParamNameToValue.end()) {
3941
266
                if (param->wkt2_name == nullptr) {
3942
5
                    bool ok = true;
3943
5
                    try {
3944
5
                        if (io::asDouble(param->fixed_value) ==
3945
5
                            io::asDouble(iter->second)) {
3946
3
                            matchCount++;
3947
3
                        } else {
3948
2
                            ok = false;
3949
2
                        }
3950
5
                    } catch (const std::exception &) {
3951
0
                        ok = false;
3952
0
                    }
3953
5
                    if (!ok) {
3954
2
                        matchCount = -1;
3955
2
                        break;
3956
2
                    }
3957
261
                } else {
3958
261
                    matchCount++;
3959
261
                }
3960
6.05k
            } else if (param->is_fixed_value) {
3961
3
                mapParamNameToValue[param->esri_name] = param->fixed_value;
3962
6.04k
            } else {
3963
6.04k
                unmatchCount++;
3964
6.04k
            }
3965
6.31k
        }
3966
717
        if (matchCount > bestMatchCount &&
3967
428
            !(maybeEsriStyle_ && unmatchCount >= matchCount)) {
3968
428
            esriMapping = mapping;
3969
428
            bestMatchCount = matchCount;
3970
428
        }
3971
717
    }
3972
3973
428
    return esriMapping;
3974
428
}
3975
3976
// ---------------------------------------------------------------------------
3977
3978
ConversionNNPtr WKTParser::Private::buildProjectionFromESRI(
3979
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
3980
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
3981
    const UnitOfMeasure &defaultAngularUnit,
3982
    const ESRIMethodMapping *esriMapping,
3983
428
    std::map<std::string, std::string, ci_less_struct> &mapParamNameToValue) {
3984
428
    std::map<std::string, const char *> mapWKT2NameToESRIName;
3985
3.86k
    for (const auto *param = esriMapping->params; param->esri_name; ++param) {
3986
3.43k
        if (param->wkt2_name) {
3987
2.55k
            mapWKT2NameToESRIName[param->wkt2_name] = param->esri_name;
3988
2.55k
        }
3989
3.43k
    }
3990
3991
428
    const std::string esriProjectionName =
3992
428
        stripQuotes(projectionNode->GP()->children()[0]);
3993
428
    const char *projectionMethodWkt2Name = esriMapping->wkt2_name;
3994
428
    if (ci_equal(esriProjectionName, "Krovak")) {
3995
287
        const std::string projCRSName =
3996
287
            stripQuotes(projCRSNode->GP()->children()[0]);
3997
287
        if (projCRSName.find("_East_North") != std::string::npos) {
3998
0
            projectionMethodWkt2Name = EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED;
3999
0
        }
4000
287
    }
4001
4002
428
    const auto *wkt2_mapping = getMapping(projectionMethodWkt2Name);
4003
428
    if (ci_equal(esriProjectionName, "Stereographic")) {
4004
8
        try {
4005
8
            const auto iterLatitudeOfOrigin =
4006
8
                mapParamNameToValue.find("Latitude_Of_Origin");
4007
8
            if (iterLatitudeOfOrigin != mapParamNameToValue.end() &&
4008
8
                std::fabs(io::asDouble(iterLatitudeOfOrigin->second)) == 90.0) {
4009
6
                wkt2_mapping =
4010
6
                    getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A);
4011
6
            }
4012
8
        } catch (const std::exception &) {
4013
0
        }
4014
8
    }
4015
428
    assert(wkt2_mapping);
4016
4017
428
    wkt2_mapping = selectSphericalOrEllipsoidal(wkt2_mapping, baseGeodCRS);
4018
4019
428
    PropertyMap propertiesMethod;
4020
428
    propertiesMethod.set(IdentifiedObject::NAME_KEY, wkt2_mapping->wkt2_name);
4021
428
    if (wkt2_mapping->epsg_code != 0) {
4022
366
        propertiesMethod.set(Identifier::CODE_KEY, wkt2_mapping->epsg_code);
4023
366
        propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
4024
366
    }
4025
4026
428
    std::vector<OperationParameterNNPtr> parameters;
4027
428
    std::vector<ParameterValueNNPtr> values;
4028
4029
428
    if (wkt2_mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL &&
4030
0
        ci_equal(esriProjectionName, "Plate_Carree")) {
4031
        // Add a fixed  Latitude of 1st parallel = 0 so as to have all
4032
        // parameters expected by Equidistant Cylindrical.
4033
0
        mapWKT2NameToESRIName[EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL] =
4034
0
            "Standard_Parallel_1";
4035
0
        mapParamNameToValue["Standard_Parallel_1"] = "0";
4036
428
    } else if ((wkt2_mapping->epsg_code ==
4037
428
                    EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A ||
4038
427
                wkt2_mapping->epsg_code ==
4039
427
                    EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) &&
4040
1
               !ci_equal(esriProjectionName,
4041
1
                         "Rectified_Skew_Orthomorphic_Natural_Origin") &&
4042
0
               !ci_equal(esriProjectionName,
4043
0
                         "Rectified_Skew_Orthomorphic_Center")) {
4044
        // ESRI WKT lacks the angle to skew grid
4045
        // Take it from the azimuth value
4046
0
        mapWKT2NameToESRIName
4047
0
            [EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID] = "Azimuth";
4048
0
    }
4049
4050
2.98k
    for (int i = 0; wkt2_mapping->params[i] != nullptr; i++) {
4051
2.55k
        const auto *paramMapping = wkt2_mapping->params[i];
4052
4053
2.55k
        auto iter = mapWKT2NameToESRIName.find(paramMapping->wkt2_name);
4054
2.55k
        if (iter == mapWKT2NameToESRIName.end()) {
4055
2
            continue;
4056
2
        }
4057
2.55k
        const auto &esriParamName = iter->second;
4058
2.55k
        auto iter2 = mapParamNameToValue.find(esriParamName);
4059
2.55k
        auto mapParamNameToValueEnd = mapParamNameToValue.end();
4060
2.55k
        if (iter2 == mapParamNameToValueEnd) {
4061
            // In case we don't find a direct match, try the aliases
4062
2.29k
            for (iter2 = mapParamNameToValue.begin();
4063
11.8k
                 iter2 != mapParamNameToValueEnd; ++iter2) {
4064
9.54k
                if (areEquivalentParameters(iter2->first, esriParamName)) {
4065
19
                    break;
4066
19
                }
4067
9.54k
            }
4068
2.29k
            if (iter2 == mapParamNameToValueEnd) {
4069
2.27k
                continue;
4070
2.27k
            }
4071
2.29k
        }
4072
4073
273
        PropertyMap propertiesParameter;
4074
273
        propertiesParameter.set(IdentifiedObject::NAME_KEY,
4075
273
                                paramMapping->wkt2_name);
4076
273
        if (paramMapping->epsg_code != 0) {
4077
269
            propertiesParameter.set(Identifier::CODE_KEY,
4078
269
                                    paramMapping->epsg_code);
4079
269
            propertiesParameter.set(Identifier::CODESPACE_KEY,
4080
269
                                    Identifier::EPSG);
4081
269
        }
4082
273
        parameters.push_back(OperationParameter::create(propertiesParameter));
4083
4084
273
        try {
4085
273
            double val = io::asDouble(iter2->second);
4086
273
            auto unit = guessUnitForParameter(
4087
273
                paramMapping->wkt2_name, defaultLinearUnit, defaultAngularUnit);
4088
273
            values.push_back(ParameterValue::create(Measure(val, unit)));
4089
273
        } catch (const std::exception &) {
4090
0
            throw ParsingException(
4091
0
                concat("unhandled parameter value type : ", iter2->second));
4092
0
        }
4093
273
    }
4094
4095
428
    return Conversion::create(
4096
428
               PropertyMap().set(IdentifiedObject::NAME_KEY,
4097
428
                                 esriProjectionName == "Gauss_Kruger"
4098
428
                                     ? "unnnamed (Gauss Kruger)"
4099
428
                                     : "unnamed"),
4100
428
               propertiesMethod, parameters, values)
4101
428
        ->identify();
4102
428
}
4103
4104
// ---------------------------------------------------------------------------
4105
4106
ConversionNNPtr WKTParser::Private::buildProjectionFromESRI(
4107
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
4108
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
4109
704
    const UnitOfMeasure &defaultAngularUnit) {
4110
4111
704
    std::map<std::string, std::string, ci_less_struct> mapParamNameToValue;
4112
704
    const auto esriMapping =
4113
704
        getESRIMapping(projCRSNode, projectionNode, mapParamNameToValue);
4114
704
    if (esriMapping == nullptr) {
4115
303
        return buildProjectionStandard(baseGeodCRS, projCRSNode, projectionNode,
4116
303
                                       defaultLinearUnit, defaultAngularUnit);
4117
303
    }
4118
4119
401
    return buildProjectionFromESRI(baseGeodCRS, projCRSNode, projectionNode,
4120
401
                                   defaultLinearUnit, defaultAngularUnit,
4121
401
                                   esriMapping, mapParamNameToValue);
4122
704
}
4123
4124
// ---------------------------------------------------------------------------
4125
4126
ConversionNNPtr WKTParser::Private::buildProjection(
4127
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
4128
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
4129
1.02k
    const UnitOfMeasure &defaultAngularUnit) {
4130
1.02k
    if (projectionNode->GP()->childrenSize() == 0) {
4131
0
        ThrowNotEnoughChildren(WKTConstants::PROJECTION);
4132
0
    }
4133
1.02k
    if (esriStyle_ || maybeEsriStyle_) {
4134
704
        return buildProjectionFromESRI(baseGeodCRS, projCRSNode, projectionNode,
4135
704
                                       defaultLinearUnit, defaultAngularUnit);
4136
704
    }
4137
322
    return buildProjectionStandard(baseGeodCRS, projCRSNode, projectionNode,
4138
322
                                   defaultLinearUnit, defaultAngularUnit);
4139
1.02k
}
4140
4141
// ---------------------------------------------------------------------------
4142
4143
std::string
4144
WKTParser::Private::projectionGetParameter(const WKTNodeNNPtr &projCRSNode,
4145
0
                                           const char *paramName) {
4146
0
    for (const auto &childNode : projCRSNode->GP()->children()) {
4147
0
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
4148
0
            const auto &childNodeChildren = childNode->GP()->children();
4149
0
            if (childNodeChildren.size() == 2 &&
4150
0
                metadata::Identifier::isEquivalentName(
4151
0
                    stripQuotes(childNodeChildren[0]).c_str(), paramName)) {
4152
0
                return childNodeChildren[1]->GP()->value();
4153
0
            }
4154
0
        }
4155
0
    }
4156
0
    return std::string();
4157
0
}
4158
4159
// ---------------------------------------------------------------------------
4160
4161
ConversionNNPtr WKTParser::Private::buildProjectionStandard(
4162
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
4163
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
4164
625
    const UnitOfMeasure &defaultAngularUnit) {
4165
625
    std::string wkt1ProjectionName =
4166
625
        stripQuotes(projectionNode->GP()->children()[0]);
4167
4168
625
    std::vector<OperationParameterNNPtr> parameters;
4169
625
    std::vector<ParameterValueNNPtr> values;
4170
625
    bool tryToIdentifyWKT1Method = true;
4171
4172
625
    auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION);
4173
625
    const auto &extensionChildren = extensionNode->GP()->children();
4174
4175
625
    bool gdal_3026_hack = false;
4176
625
    if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
4177
625
                                               "Mercator_1SP") &&
4178
0
        projectionGetParameter(projCRSNode, "center_latitude").empty()) {
4179
4180
        // Hack for https://trac.osgeo.org/gdal/ticket/3026
4181
0
        std::string lat0(
4182
0
            projectionGetParameter(projCRSNode, "latitude_of_origin"));
4183
0
        if (!lat0.empty() && lat0 != "0" && lat0 != "0.0") {
4184
0
            wkt1ProjectionName = "Mercator_2SP";
4185
0
            gdal_3026_hack = true;
4186
0
        } else {
4187
            // The latitude of origin, which should always be zero, is
4188
            // missing
4189
            // in GDAL WKT1, but provisionned in the EPSG Mercator_1SP
4190
            // definition,
4191
            // so add it manually.
4192
0
            PropertyMap propertiesParameter;
4193
0
            propertiesParameter.set(IdentifiedObject::NAME_KEY,
4194
0
                                    "Latitude of natural origin");
4195
0
            propertiesParameter.set(Identifier::CODE_KEY, 8801);
4196
0
            propertiesParameter.set(Identifier::CODESPACE_KEY,
4197
0
                                    Identifier::EPSG);
4198
0
            parameters.push_back(
4199
0
                OperationParameter::create(propertiesParameter));
4200
0
            values.push_back(
4201
0
                ParameterValue::create(Measure(0, UnitOfMeasure::DEGREE)));
4202
0
        }
4203
4204
625
    } else if (metadata::Identifier::isEquivalentName(
4205
625
                   wkt1ProjectionName.c_str(), "Polar_Stereographic")) {
4206
125
        std::map<std::string, Measure> mapParameters;
4207
6.65k
        for (const auto &childNode : projCRSNode->GP()->children()) {
4208
6.65k
            const auto &childNodeChildren = childNode->GP()->children();
4209
6.65k
            if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) &&
4210
4.74k
                childNodeChildren.size() == 2) {
4211
4.54k
                const std::string wkt1ParameterName(
4212
4.54k
                    stripQuotes(childNodeChildren[0]));
4213
4.54k
                try {
4214
4.54k
                    double val = asDouble(childNodeChildren[1]);
4215
4.54k
                    auto unit = guessUnitForParameter(wkt1ParameterName,
4216
4.54k
                                                      defaultLinearUnit,
4217
4.54k
                                                      defaultAngularUnit);
4218
4.54k
                    mapParameters.insert(std::pair<std::string, Measure>(
4219
4.54k
                        tolower(wkt1ParameterName), Measure(val, unit)));
4220
4.54k
                } catch (const std::exception &) {
4221
41
                }
4222
4.54k
            }
4223
6.65k
        }
4224
4225
125
        Measure latitudeOfOrigin = mapParameters["latitude_of_origin"];
4226
125
        Measure centralMeridian = mapParameters["central_meridian"];
4227
125
        Measure scaleFactorFromMap = mapParameters["scale_factor"];
4228
125
        Measure scaleFactor((scaleFactorFromMap.unit() == UnitOfMeasure::NONE)
4229
125
                                ? Measure(1.0, UnitOfMeasure::SCALE_UNITY)
4230
125
                                : scaleFactorFromMap);
4231
125
        Measure falseEasting = mapParameters["false_easting"];
4232
125
        Measure falseNorthing = mapParameters["false_northing"];
4233
125
        if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
4234
0
            scaleFactor.getSIValue() == 1.0) {
4235
0
            return Conversion::createPolarStereographicVariantB(
4236
0
                PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
4237
0
                Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()),
4238
0
                Angle(centralMeridian.value(), centralMeridian.unit()),
4239
0
                Length(falseEasting.value(), falseEasting.unit()),
4240
0
                Length(falseNorthing.value(), falseNorthing.unit()));
4241
0
        }
4242
4243
125
        if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
4244
0
            std::fabs(std::fabs(latitudeOfOrigin.convertToUnit(
4245
0
                          UnitOfMeasure::DEGREE)) -
4246
0
                      90.0) < 1e-10) {
4247
0
            return Conversion::createPolarStereographicVariantA(
4248
0
                PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
4249
0
                Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()),
4250
0
                Angle(centralMeridian.value(), centralMeridian.unit()),
4251
0
                Scale(scaleFactor.value(), scaleFactor.unit()),
4252
0
                Length(falseEasting.value(), falseEasting.unit()),
4253
0
                Length(falseNorthing.value(), falseNorthing.unit()));
4254
0
        }
4255
4256
125
        tryToIdentifyWKT1Method = false;
4257
        // Import GDAL PROJ4 extension nodes
4258
500
    } else if (extensionChildren.size() == 2 &&
4259
35
               ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
4260
0
        std::string projString = stripQuotes(extensionChildren[1]);
4261
0
        if (starts_with(projString, "+proj=")) {
4262
0
            if (projString.find(" +type=crs") == std::string::npos) {
4263
0
                projString += " +type=crs";
4264
0
            }
4265
0
            try {
4266
0
                auto projObj =
4267
0
                    PROJStringParser().createFromPROJString(projString);
4268
0
                auto projObjCrs =
4269
0
                    nn_dynamic_pointer_cast<ProjectedCRS>(projObj);
4270
0
                if (projObjCrs) {
4271
0
                    return projObjCrs->derivingConversion();
4272
0
                }
4273
0
            } catch (const io::ParsingException &) {
4274
0
            }
4275
0
        }
4276
0
    }
4277
4278
625
    std::string projectionName(std::move(wkt1ProjectionName));
4279
625
    const MethodMapping *mapping =
4280
625
        tryToIdentifyWKT1Method ? getMappingFromWKT1(projectionName) : nullptr;
4281
4282
625
    if (!mapping) {
4283
        // Sometimes non-WKT1:ESRI looking WKT can actually use WKT1:ESRI
4284
        // projection definitions
4285
364
        std::map<std::string, std::string, ci_less_struct> mapParamNameToValue;
4286
364
        const auto esriMapping =
4287
364
            getESRIMapping(projCRSNode, projectionNode, mapParamNameToValue);
4288
364
        if (esriMapping != nullptr) {
4289
30
            return buildProjectionFromESRI(
4290
30
                baseGeodCRS, projCRSNode, projectionNode, defaultLinearUnit,
4291
30
                defaultAngularUnit, esriMapping, mapParamNameToValue);
4292
30
        }
4293
364
    }
4294
4295
595
    if (mapping) {
4296
261
        mapping = selectSphericalOrEllipsoidal(mapping, baseGeodCRS);
4297
334
    } else if (metadata::Identifier::isEquivalentName(
4298
334
                   projectionName.c_str(), "Lambert Conformal Conic")) {
4299
        // Lambert Conformal Conic or Lambert_Conformal_Conic are respectively
4300
        // used by Oracle WKT and Trimble for either LCC 1SP or 2SP, so we
4301
        // have to look at parameters to figure out the variant.
4302
16
        bool found2ndStdParallel = false;
4303
16
        bool foundScaleFactor = false;
4304
612
        for (const auto &childNode : projCRSNode->GP()->children()) {
4305
612
            if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
4306
288
                const auto &childNodeChildren = childNode->GP()->children();
4307
288
                if (childNodeChildren.size() < 2) {
4308
5
                    ThrowNotEnoughChildren(WKTConstants::PARAMETER);
4309
5
                }
4310
283
                const std::string wkt1ParameterName(
4311
283
                    stripQuotes(childNodeChildren[0]));
4312
283
                if (metadata::Identifier::isEquivalentName(
4313
283
                        wkt1ParameterName.c_str(), WKT1_STANDARD_PARALLEL_2)) {
4314
0
                    found2ndStdParallel = true;
4315
283
                } else if (metadata::Identifier::isEquivalentName(
4316
283
                               wkt1ParameterName.c_str(), WKT1_SCALE_FACTOR)) {
4317
0
                    foundScaleFactor = true;
4318
0
                }
4319
283
            }
4320
612
        }
4321
11
        if (found2ndStdParallel && !foundScaleFactor) {
4322
0
            mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
4323
11
        } else if (!found2ndStdParallel && foundScaleFactor) {
4324
0
            mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
4325
11
        } else if (found2ndStdParallel && foundScaleFactor) {
4326
            // Not sure if that happens
4327
0
            mapping = getMapping(
4328
0
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN);
4329
0
        }
4330
11
    }
4331
4332
    // For Krovak, we need to look at axis to decide between the Krovak and
4333
    // Krovak East-North Oriented methods
4334
590
    if (ci_equal(projectionName, "Krovak") &&
4335
60
        projCRSNode->countChildrenOfName(WKTConstants::AXIS) == 2 &&
4336
0
        &buildAxis(projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 0),
4337
0
                   defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false, 1)
4338
0
                ->direction() == &AxisDirection::SOUTH &&
4339
0
        &buildAxis(projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 1),
4340
0
                   defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false, 2)
4341
0
                ->direction() == &AxisDirection::WEST) {
4342
0
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
4343
0
    }
4344
4345
590
    PropertyMap propertiesMethod;
4346
590
    if (mapping) {
4347
261
        projectionName = mapping->wkt2_name;
4348
261
        if (mapping->epsg_code != 0) {
4349
258
            propertiesMethod.set(Identifier::CODE_KEY, mapping->epsg_code);
4350
258
            propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
4351
258
        }
4352
261
    }
4353
590
    propertiesMethod.set(IdentifiedObject::NAME_KEY, projectionName);
4354
4355
590
    std::vector<bool> foundParameters;
4356
590
    if (mapping) {
4357
261
        size_t countParams = 0;
4358
2.07k
        while (mapping->params[countParams] != nullptr) {
4359
1.81k
            ++countParams;
4360
1.81k
        }
4361
261
        foundParameters.resize(countParams);
4362
261
    }
4363
4364
13.3k
    for (const auto &childNode : projCRSNode->GP()->children()) {
4365
13.3k
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
4366
5.88k
            const auto &childNodeChildren = childNode->GP()->children();
4367
5.88k
            if (childNodeChildren.size() < 2) {
4368
55
                ThrowNotEnoughChildren(WKTConstants::PARAMETER);
4369
55
            }
4370
5.82k
            const auto &paramValue = childNodeChildren[1]->GP()->value();
4371
4372
5.82k
            PropertyMap propertiesParameter;
4373
5.82k
            const std::string wkt1ParameterName(
4374
5.82k
                stripQuotes(childNodeChildren[0]));
4375
5.82k
            std::string parameterName(wkt1ParameterName);
4376
5.82k
            if (gdal_3026_hack) {
4377
0
                if (ci_equal(parameterName, "latitude_of_origin")) {
4378
0
                    parameterName = "standard_parallel_1";
4379
0
                } else if (ci_equal(parameterName, "scale_factor") &&
4380
0
                           paramValue == "1") {
4381
0
                    continue;
4382
0
                }
4383
0
            }
4384
5.82k
            auto *paramMapping =
4385
5.82k
                mapping ? getMappingFromWKT1(mapping, parameterName) : nullptr;
4386
5.82k
            if (mapping &&
4387
2.39k
                mapping->epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
4388
0
                ci_equal(parameterName, "latitude_of_origin")) {
4389
                // Some illegal formulations of Mercator_2SP have a unexpected
4390
                // latitude_of_origin parameter. We accept it on import, but
4391
                // do not accept it when exporting to PROJ string, unless it is
4392
                // zero.
4393
                // No need to try to update foundParameters[] as this is a
4394
                // unexpected one.
4395
0
                parameterName = EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN;
4396
0
                propertiesParameter.set(
4397
0
                    Identifier::CODE_KEY,
4398
0
                    EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
4399
0
                propertiesParameter.set(Identifier::CODESPACE_KEY,
4400
0
                                        Identifier::EPSG);
4401
5.82k
            } else if (mapping && paramMapping) {
4402
8
                for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
4403
8
                    if (mapping->params[idx] == paramMapping) {
4404
4
                        foundParameters[idx] = true;
4405
4
                        break;
4406
4
                    }
4407
8
                }
4408
4
                parameterName = paramMapping->wkt2_name;
4409
4
                if (paramMapping->epsg_code != 0) {
4410
4
                    propertiesParameter.set(Identifier::CODE_KEY,
4411
4
                                            paramMapping->epsg_code);
4412
4
                    propertiesParameter.set(Identifier::CODESPACE_KEY,
4413
4
                                            Identifier::EPSG);
4414
4
                }
4415
4
            }
4416
5.82k
            propertiesParameter.set(IdentifiedObject::NAME_KEY, parameterName);
4417
5.82k
            parameters.push_back(
4418
5.82k
                OperationParameter::create(propertiesParameter));
4419
5.82k
            try {
4420
5.82k
                double val = io::asDouble(paramValue);
4421
5.82k
                auto unit = guessUnitForParameter(
4422
5.82k
                    wkt1ParameterName, defaultLinearUnit, defaultAngularUnit);
4423
5.82k
                values.push_back(ParameterValue::create(Measure(val, unit)));
4424
5.82k
            } catch (const std::exception &) {
4425
67
                throw ParsingException(
4426
67
                    concat("unhandled parameter value type : ", paramValue));
4427
67
            }
4428
5.82k
        }
4429
13.3k
    }
4430
4431
    // Add back important parameters that should normally be present, but
4432
    // are sometimes missing. Currently we only deal with Scale factor at
4433
    // natural origin. This is to avoid a default value of 0 to slip in later.
4434
    // But such WKT should be considered invalid.
4435
468
    if (mapping) {
4436
1.92k
        for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
4437
1.68k
            if (!foundParameters[idx] &&
4438
1.67k
                mapping->params[idx]->epsg_code ==
4439
1.67k
                    EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) {
4440
4441
2
                emitRecoverableWarning(
4442
2
                    "The WKT string lacks a value "
4443
2
                    "for " EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN
4444
2
                    ". Default it to 1.");
4445
4446
2
                PropertyMap propertiesParameter;
4447
2
                propertiesParameter.set(
4448
2
                    Identifier::CODE_KEY,
4449
2
                    EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
4450
2
                propertiesParameter.set(Identifier::CODESPACE_KEY,
4451
2
                                        Identifier::EPSG);
4452
2
                propertiesParameter.set(
4453
2
                    IdentifiedObject::NAME_KEY,
4454
2
                    EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
4455
2
                parameters.push_back(
4456
2
                    OperationParameter::create(propertiesParameter));
4457
2
                values.push_back(ParameterValue::create(
4458
2
                    Measure(1.0, UnitOfMeasure::SCALE_UNITY)));
4459
2
            }
4460
1.68k
        }
4461
242
    }
4462
4463
468
    if (mapping && (mapping->epsg_code ==
4464
242
                        EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A ||
4465
242
                    mapping->epsg_code ==
4466
242
                        EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B)) {
4467
        // Special case when importing some GDAL WKT of Hotine Oblique Mercator
4468
        // that have a Azimuth parameter but lacks the Rectified Grid Angle.
4469
        // We have code in the exportToPROJString() to deal with that situation,
4470
        // but also adds the rectified grid angle from the azimuth on import.
4471
0
        bool foundAngleRecifiedToSkewGrid = false;
4472
0
        bool foundAzimuth = false;
4473
0
        for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
4474
0
            if (foundParameters[idx] &&
4475
0
                mapping->params[idx]->epsg_code ==
4476
0
                    EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) {
4477
0
                foundAngleRecifiedToSkewGrid = true;
4478
0
            } else if (foundParameters[idx] &&
4479
0
                       mapping->params[idx]->epsg_code ==
4480
0
                           EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE) {
4481
0
                foundAzimuth = true;
4482
0
            }
4483
0
        }
4484
0
        if (!foundAngleRecifiedToSkewGrid && foundAzimuth) {
4485
0
            for (size_t idx = 0; idx < parameters.size(); ++idx) {
4486
0
                if (parameters[idx]->getEPSGCode() ==
4487
0
                    EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE) {
4488
0
                    PropertyMap propertiesParameter;
4489
0
                    propertiesParameter.set(
4490
0
                        Identifier::CODE_KEY,
4491
0
                        EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID);
4492
0
                    propertiesParameter.set(Identifier::CODESPACE_KEY,
4493
0
                                            Identifier::EPSG);
4494
0
                    propertiesParameter.set(
4495
0
                        IdentifiedObject::NAME_KEY,
4496
0
                        EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID);
4497
0
                    parameters.push_back(
4498
0
                        OperationParameter::create(propertiesParameter));
4499
0
                    values.push_back(values[idx]);
4500
0
                }
4501
0
            }
4502
0
        }
4503
0
    }
4504
4505
468
    return Conversion::create(
4506
468
               PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
4507
468
               propertiesMethod, parameters, values)
4508
468
        ->identify();
4509
590
}
4510
4511
// ---------------------------------------------------------------------------
4512
4513
static ProjectedCRSNNPtr createPseudoMercator(const PropertyMap &props,
4514
401
                                              const cs::CartesianCSNNPtr &cs) {
4515
401
    auto conversion = Conversion::createPopularVisualisationPseudoMercator(
4516
401
        PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(0),
4517
401
        Angle(0), Length(0), Length(0));
4518
401
    return ProjectedCRS::create(props, GeographicCRS::EPSG_4326, conversion,
4519
401
                                cs);
4520
401
}
4521
4522
// ---------------------------------------------------------------------------
4523
4524
ProjectedCRSNNPtr
4525
1.09k
WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) {
4526
4527
1.09k
    const auto *nodeP = node->GP();
4528
1.09k
    auto &conversionNode = nodeP->lookForChild(WKTConstants::CONVERSION);
4529
1.09k
    auto &projectionNode = nodeP->lookForChild(WKTConstants::PROJECTION);
4530
1.09k
    if (isNull(conversionNode) && isNull(projectionNode)) {
4531
35
        ThrowMissing(WKTConstants::CONVERSION);
4532
35
    }
4533
4534
1.06k
    auto &baseGeodCRSNode =
4535
1.06k
        nodeP->lookForChild(WKTConstants::BASEGEODCRS,
4536
1.06k
                            WKTConstants::BASEGEOGCRS, WKTConstants::GEOGCS);
4537
1.06k
    if (isNull(baseGeodCRSNode)) {
4538
8
        throw ParsingException(
4539
8
            "Missing BASEGEODCRS / BASEGEOGCRS / GEOGCS node");
4540
8
    }
4541
1.05k
    auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode);
4542
4543
1.05k
    auto props = buildProperties(node);
4544
4545
1.05k
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
4546
1.05k
    const auto &nodeValue = nodeP->value();
4547
1.05k
    if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::PROJCS) &&
4548
0
        !ci_equal(nodeValue, WKTConstants::BASEPROJCRS)) {
4549
0
        ThrowMissing(WKTConstants::CS_);
4550
0
    }
4551
4552
1.05k
    std::string projCRSName = stripQuotes(nodeP->children()[0]);
4553
4554
1.05k
    auto cs = [this, &projCRSName, &nodeP, &csNode, &node, &nodeValue,
4555
1.05k
               &conversionNode]() -> CoordinateSystemNNPtr {
4556
1.03k
        if (isNull(csNode) && ci_equal(nodeValue, WKTConstants::BASEPROJCRS) &&
4557
0
            !isNull(conversionNode)) {
4558
            // A BASEPROJCRS (as of WKT2 18-010r11) normally lacks an explicit
4559
            // CS[] which cause issues to properly instantiate it. So we first
4560
            // start by trying to identify the BASEPROJCRS by its id or name.
4561
            // And fallback to exploring the conversion parameters to infer the
4562
            // CS AXIS unit from the linear parameter unit... Not fully bullet
4563
            // proof.
4564
0
            if (dbContext_) {
4565
                // Get official name from database if ID is present
4566
0
                auto &idNode = nodeP->lookForChild(WKTConstants::ID);
4567
0
                if (!isNull(idNode)) {
4568
0
                    try {
4569
0
                        auto id = buildId(node, idNode, false, false);
4570
0
                        auto authFactory = AuthorityFactory::create(
4571
0
                            NN_NO_CHECK(dbContext_), *id->codeSpace());
4572
0
                        auto projCRS =
4573
0
                            authFactory->createProjectedCRS(id->code());
4574
0
                        return projCRS->coordinateSystem();
4575
0
                    } catch (const std::exception &) {
4576
0
                    }
4577
0
                }
4578
4579
0
                auto authFactory = AuthorityFactory::create(
4580
0
                    NN_NO_CHECK(dbContext_), std::string());
4581
0
                auto res = authFactory->createObjectsFromName(
4582
0
                    projCRSName, {AuthorityFactory::ObjectType::PROJECTED_CRS},
4583
0
                    false, 2);
4584
0
                if (res.size() == 1) {
4585
0
                    auto projCRS =
4586
0
                        dynamic_cast<const ProjectedCRS *>(res.front().get());
4587
0
                    if (projCRS) {
4588
0
                        return projCRS->coordinateSystem();
4589
0
                    }
4590
0
                }
4591
0
            }
4592
4593
0
            auto conv = buildConversion(conversionNode, UnitOfMeasure::METRE,
4594
0
                                        UnitOfMeasure::DEGREE);
4595
0
            UnitOfMeasure linearUOM = UnitOfMeasure::NONE;
4596
0
            for (const auto &genOpParamvalue : conv->parameterValues()) {
4597
0
                auto opParamvalue =
4598
0
                    dynamic_cast<const operation::OperationParameterValue *>(
4599
0
                        genOpParamvalue.get());
4600
0
                if (opParamvalue) {
4601
0
                    const auto &parameterValue = opParamvalue->parameterValue();
4602
0
                    if (parameterValue->type() ==
4603
0
                        operation::ParameterValue::Type::MEASURE) {
4604
0
                        const auto &measure = parameterValue->value();
4605
0
                        const auto &unit = measure.unit();
4606
0
                        if (unit.type() == UnitOfMeasure::Type::LINEAR) {
4607
0
                            if (linearUOM == UnitOfMeasure::NONE) {
4608
0
                                linearUOM = unit;
4609
0
                            } else if (linearUOM != unit) {
4610
0
                                linearUOM = UnitOfMeasure::NONE;
4611
0
                                break;
4612
0
                            }
4613
0
                        }
4614
0
                    }
4615
0
                }
4616
0
            }
4617
0
            if (linearUOM != UnitOfMeasure::NONE) {
4618
0
                return CartesianCS::createEastingNorthing(linearUOM);
4619
0
            }
4620
0
        }
4621
1.03k
        return buildCS(csNode, node, UnitOfMeasure::NONE);
4622
1.03k
    }();
4623
1.05k
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
4624
4625
1.05k
    if (esriStyle_ && dbContext_) {
4626
486
        if (cartesianCS) {
4627
486
            std::string outTableName;
4628
486
            std::string authNameFromAlias;
4629
486
            std::string codeFromAlias;
4630
486
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
4631
486
                                                        std::string());
4632
486
            auto officialName = authFactory->getOfficialNameFromAlias(
4633
486
                projCRSName, "projected_crs", "ESRI", false, outTableName,
4634
486
                authNameFromAlias, codeFromAlias);
4635
486
            if (!officialName.empty()) {
4636
                // Special case for https://github.com/OSGeo/PROJ/issues/2086
4637
                // The name of the CRS to identify is
4638
                // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501
4639
                // whereas it should be
4640
                // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501_Feet
4641
3
                constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37;
4642
3
                if (projCRSName.find("_FIPS_") != std::string::npos &&
4643
0
                    projCRSName.find("_Feet") == std::string::npos &&
4644
0
                    std::fabs(
4645
0
                        cartesianCS->axisList()[0]->unit().conversionToSI() -
4646
0
                        US_FOOT_CONV_FACTOR) < 1e-10 * US_FOOT_CONV_FACTOR) {
4647
0
                    auto officialNameFromFeet =
4648
0
                        authFactory->getOfficialNameFromAlias(
4649
0
                            projCRSName + "_Feet", "projected_crs", "ESRI",
4650
0
                            false, outTableName, authNameFromAlias,
4651
0
                            codeFromAlias);
4652
0
                    if (!officialNameFromFeet.empty()) {
4653
0
                        officialName = std::move(officialNameFromFeet);
4654
0
                    }
4655
0
                }
4656
4657
3
                projCRSName = officialName;
4658
3
                props.set(IdentifiedObject::NAME_KEY, officialName);
4659
3
            }
4660
486
        }
4661
486
    }
4662
4663
1.05k
    if (isNull(conversionNode) && hasWebMercPROJ4String(node, projectionNode) &&
4664
0
        cartesianCS) {
4665
0
        toWGS84Parameters_.clear();
4666
0
        return createPseudoMercator(props, NN_NO_CHECK(cartesianCS));
4667
0
    }
4668
4669
    // WGS_84_Pseudo_Mercator: Particular case for corrupted ESRI WKT generated
4670
    // by older GDAL versions
4671
    // https://trac.osgeo.org/gdal/changeset/30732
4672
    // WGS_1984_Web_Mercator: deprecated ESRI:102113
4673
1.05k
    if (cartesianCS && (metadata::Identifier::isEquivalentName(
4674
1.03k
                            projCRSName.c_str(), "WGS_84_Pseudo_Mercator") ||
4675
1.03k
                        metadata::Identifier::isEquivalentName(
4676
1.03k
                            projCRSName.c_str(), "WGS_1984_Web_Mercator"))) {
4677
3
        toWGS84Parameters_.clear();
4678
3
        return createPseudoMercator(props, NN_NO_CHECK(cartesianCS));
4679
3
    }
4680
4681
    // For WKT2, if there is no explicit parameter unit, use metre for linear
4682
    // units and degree for angular units
4683
1.05k
    const UnitOfMeasure linearUnit(
4684
1.05k
        !isNull(conversionNode)
4685
1.05k
            ? UnitOfMeasure::METRE
4686
1.05k
            : buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR));
4687
1.05k
    const auto &angularUnit =
4688
1.05k
        !isNull(conversionNode)
4689
1.05k
            ? UnitOfMeasure::DEGREE
4690
1.05k
            : baseGeodCRS->coordinateSystem()->axisList()[0]->unit();
4691
4692
1.05k
    auto conversion =
4693
1.05k
        !isNull(conversionNode)
4694
1.05k
            ? buildConversion(conversionNode, linearUnit, angularUnit)
4695
1.05k
            : buildProjection(baseGeodCRS, node, projectionNode, linearUnit,
4696
1.04k
                              angularUnit);
4697
4698
    // No explicit AXIS node ? (WKT1)
4699
1.05k
    if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) {
4700
890
        props.set("IMPLICIT_CS", true);
4701
890
    }
4702
4703
1.05k
    if (isNull(csNode) && node->countChildrenOfName(WKTConstants::AXIS) == 0) {
4704
4705
890
        const auto methodCode = conversion->method()->getEPSGCode();
4706
        // Krovak south oriented ?
4707
890
        if (methodCode == EPSG_CODE_METHOD_KROVAK) {
4708
287
            cartesianCS =
4709
287
                CartesianCS::create(
4710
287
                    PropertyMap(),
4711
287
                    CoordinateSystemAxis::create(
4712
287
                        util::PropertyMap().set(IdentifiedObject::NAME_KEY,
4713
287
                                                AxisName::Southing),
4714
287
                        emptyString, AxisDirection::SOUTH, linearUnit),
4715
287
                    CoordinateSystemAxis::create(
4716
287
                        util::PropertyMap().set(IdentifiedObject::NAME_KEY,
4717
287
                                                AxisName::Westing),
4718
287
                        emptyString, AxisDirection::WEST, linearUnit))
4719
287
                    .as_nullable();
4720
603
        } else if (methodCode ==
4721
603
                       EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A ||
4722
597
                   methodCode ==
4723
597
                       EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) {
4724
            // It is likely that the ESRI definition of EPSG:32661 (UPS North) &
4725
            // EPSG:32761 (UPS South) uses the easting-northing order, instead
4726
            // of the EPSG northing-easting order.
4727
            // Same for WKT1_GDAL
4728
6
            const double lat0 = conversion->parameterValueNumeric(
4729
6
                EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
4730
6
                common::UnitOfMeasure::DEGREE);
4731
6
            if (std::fabs(lat0 - 90) < 1e-10) {
4732
5
                cartesianCS =
4733
5
                    CartesianCS::createNorthPoleEastingSouthNorthingSouth(
4734
5
                        linearUnit)
4735
5
                        .as_nullable();
4736
5
            } else if (std::fabs(lat0 - -90) < 1e-10) {
4737
1
                cartesianCS =
4738
1
                    CartesianCS::createSouthPoleEastingNorthNorthingNorth(
4739
1
                        linearUnit)
4740
1
                        .as_nullable();
4741
1
            }
4742
597
        } else if (methodCode ==
4743
597
                   EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
4744
33
            const double lat_ts = conversion->parameterValueNumeric(
4745
33
                EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL,
4746
33
                common::UnitOfMeasure::DEGREE);
4747
33
            if (lat_ts > 0) {
4748
3
                cartesianCS =
4749
3
                    CartesianCS::createNorthPoleEastingSouthNorthingSouth(
4750
3
                        linearUnit)
4751
3
                        .as_nullable();
4752
30
            } else if (lat_ts < 0) {
4753
0
                cartesianCS =
4754
0
                    CartesianCS::createSouthPoleEastingNorthNorthingNorth(
4755
0
                        linearUnit)
4756
0
                        .as_nullable();
4757
0
            }
4758
564
        } else if (methodCode ==
4759
564
                   EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
4760
0
            cartesianCS =
4761
0
                CartesianCS::createWestingSouthing(linearUnit).as_nullable();
4762
0
        }
4763
890
    }
4764
1.05k
    if (!cartesianCS) {
4765
0
        ThrowNotExpectedCSType(CartesianCS::WKT2_TYPE);
4766
0
    }
4767
4768
    // In EPSG v12.025, Norway projected systems based on ETRS89 (EPSG:4258)
4769
    // have switched to use ETRS89-NOR [EUREF89] (EPSG:10875). There's no way
4770
    // from the current content of the database to infer both CRS are equivalent
4771
1.05k
    if (starts_with(projCRSName, "ETRS89 / NTM zone")) {
4772
0
        projCRSName = "ETRS89-NOR [EUREF89] / NTM zone" +
4773
0
                      projCRSName.substr(strlen("ETRS89 / NTM zone"));
4774
0
        props.set(IdentifiedObject::NAME_KEY, projCRSName);
4775
0
    }
4776
1.05k
    if (dbContext_ &&
4777
644
        starts_with(projCRSName, "ETRS89-NOR [EUREF89] / NTM zone") &&
4778
0
        baseGeodCRS->nameStr() == "ETRS89" &&
4779
0
        util::isOfExactType<GeographicCRS>(*(baseGeodCRS.get())) &&
4780
0
        baseGeodCRS->coordinateSystem()->axisList().size() == 2) {
4781
0
        auto factoryCRS_EPSG =
4782
0
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), Identifier::EPSG);
4783
0
        try {
4784
0
            baseGeodCRS = factoryCRS_EPSG->createGeodeticCRS("10875");
4785
0
        } catch (const std::exception &) {
4786
0
        }
4787
0
    }
4788
4789
1.05k
    if (cartesianCS->axisList().size() == 3 &&
4790
1
        baseGeodCRS->coordinateSystem()->axisList().size() == 2) {
4791
1
        baseGeodCRS = NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
4792
1
            baseGeodCRS->promoteTo3D(std::string(), dbContext_)));
4793
1
    }
4794
4795
1.05k
    addExtensionProj4ToProp(nodeP, props);
4796
4797
1.05k
    return ProjectedCRS::create(props, baseGeodCRS, conversion,
4798
1.05k
                                NN_NO_CHECK(cartesianCS));
4799
1.05k
}
4800
4801
// ---------------------------------------------------------------------------
4802
4803
void WKTParser::Private::parseDynamic(const WKTNodeNNPtr &dynamicNode,
4804
                                      double &frameReferenceEpoch,
4805
41
                                      util::optional<std::string> &modelName) {
4806
41
    auto &frameEpochNode = dynamicNode->lookForChild(WKTConstants::FRAMEEPOCH);
4807
41
    const auto &frameEpochChildren = frameEpochNode->GP()->children();
4808
41
    if (frameEpochChildren.empty()) {
4809
7
        ThrowMissing(WKTConstants::FRAMEEPOCH);
4810
7
    }
4811
34
    try {
4812
34
        frameReferenceEpoch = asDouble(frameEpochChildren[0]);
4813
34
    } catch (const std::exception &) {
4814
0
        throw ParsingException("Invalid FRAMEEPOCH node");
4815
0
    }
4816
34
    auto &modelNode = dynamicNode->GP()->lookForChild(
4817
34
        WKTConstants::MODEL, WKTConstants::VELOCITYGRID);
4818
34
    const auto &modelChildren = modelNode->GP()->children();
4819
34
    if (modelChildren.size() == 1) {
4820
16
        modelName = stripQuotes(modelChildren[0]);
4821
16
    }
4822
34
}
4823
4824
// ---------------------------------------------------------------------------
4825
4826
VerticalReferenceFrameNNPtr WKTParser::Private::buildVerticalReferenceFrame(
4827
2.23k
    const WKTNodeNNPtr &node, const WKTNodeNNPtr &dynamicNode) {
4828
4829
2.23k
    if (!isNull(dynamicNode)) {
4830
23
        double frameReferenceEpoch = 0.0;
4831
23
        util::optional<std::string> modelName;
4832
23
        parseDynamic(dynamicNode, frameReferenceEpoch, modelName);
4833
23
        return DynamicVerticalReferenceFrame::create(
4834
23
            buildProperties(node), getAnchor(node),
4835
23
            optional<RealizationMethod>(),
4836
23
            common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR),
4837
23
            modelName);
4838
23
    }
4839
4840
    // WKT1 VERT_DATUM has a datum type after the datum name
4841
2.20k
    const auto *nodeP = node->GP();
4842
2.20k
    const std::string &name(nodeP->value());
4843
2.20k
    auto &props = buildProperties(node);
4844
2.20k
    const auto &children = nodeP->children();
4845
4846
2.20k
    if (esriStyle_ && dbContext_ && !children.empty()) {
4847
175
        std::string outTableName;
4848
175
        std::string authNameFromAlias;
4849
175
        std::string codeFromAlias;
4850
175
        auto authFactory =
4851
175
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
4852
175
        const std::string datumName = stripQuotes(children[0]);
4853
175
        auto officialName = authFactory->getOfficialNameFromAlias(
4854
175
            datumName, "vertical_datum", "ESRI", false, outTableName,
4855
175
            authNameFromAlias, codeFromAlias);
4856
175
        if (!officialName.empty()) {
4857
0
            props.set(IdentifiedObject::NAME_KEY, officialName);
4858
0
        }
4859
175
    }
4860
4861
2.20k
    if (ci_equal(name, WKTConstants::VERT_DATUM)) {
4862
673
        if (children.size() >= 2) {
4863
634
            props.set("VERT_DATUM_TYPE", children[1]->GP()->value());
4864
634
        }
4865
673
    }
4866
4867
2.20k
    return VerticalReferenceFrame::create(props, getAnchor(node),
4868
2.20k
                                          getAnchorEpoch(node));
4869
2.23k
}
4870
4871
// ---------------------------------------------------------------------------
4872
4873
TemporalDatumNNPtr
4874
115
WKTParser::Private::buildTemporalDatum(const WKTNodeNNPtr &node) {
4875
115
    const auto *nodeP = node->GP();
4876
115
    auto &calendarNode = nodeP->lookForChild(WKTConstants::CALENDAR);
4877
115
    std::string calendar = TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN;
4878
115
    const auto &calendarChildren = calendarNode->GP()->children();
4879
115
    if (calendarChildren.size() == 1) {
4880
34
        calendar = stripQuotes(calendarChildren[0]);
4881
34
    }
4882
4883
115
    auto &timeOriginNode = nodeP->lookForChild(WKTConstants::TIMEORIGIN);
4884
115
    std::string originStr;
4885
115
    const auto &timeOriginNodeChildren = timeOriginNode->GP()->children();
4886
115
    if (timeOriginNodeChildren.size() == 1) {
4887
0
        originStr = stripQuotes(timeOriginNodeChildren[0]);
4888
0
    }
4889
115
    auto origin = DateTime::create(originStr);
4890
115
    return TemporalDatum::create(buildProperties(node), origin, calendar);
4891
115
}
4892
4893
// ---------------------------------------------------------------------------
4894
4895
EngineeringDatumNNPtr
4896
97
WKTParser::Private::buildEngineeringDatum(const WKTNodeNNPtr &node) {
4897
97
    return EngineeringDatum::create(buildProperties(node), getAnchor(node));
4898
97
}
4899
4900
// ---------------------------------------------------------------------------
4901
4902
ParametricDatumNNPtr
4903
243
WKTParser::Private::buildParametricDatum(const WKTNodeNNPtr &node) {
4904
243
    return ParametricDatum::create(buildProperties(node), getAnchor(node));
4905
243
}
4906
4907
// ---------------------------------------------------------------------------
4908
4909
static CRSNNPtr
4910
createBoundCRSSourceTransformationCRS(const crs::CRSPtr &sourceCRS,
4911
0
                                      const crs::CRSPtr &targetCRS) {
4912
0
    CRSPtr sourceTransformationCRS;
4913
0
    if (dynamic_cast<GeographicCRS *>(targetCRS.get())) {
4914
0
        GeographicCRSPtr sourceGeographicCRS =
4915
0
            sourceCRS->extractGeographicCRS();
4916
0
        sourceTransformationCRS = sourceGeographicCRS;
4917
0
        if (sourceGeographicCRS) {
4918
0
            const auto &sourceDatum = sourceGeographicCRS->datum();
4919
0
            if (sourceDatum != nullptr && sourceGeographicCRS->primeMeridian()
4920
0
                                                  ->longitude()
4921
0
                                                  .getSIValue() != 0.0) {
4922
0
                sourceTransformationCRS =
4923
0
                    GeographicCRS::create(
4924
0
                        util::PropertyMap().set(
4925
0
                            common::IdentifiedObject::NAME_KEY,
4926
0
                            sourceGeographicCRS->nameStr() +
4927
0
                                " (with Greenwich prime meridian)"),
4928
0
                        datum::GeodeticReferenceFrame::create(
4929
0
                            util::PropertyMap().set(
4930
0
                                common::IdentifiedObject::NAME_KEY,
4931
0
                                sourceDatum->nameStr() +
4932
0
                                    " (with Greenwich prime meridian)"),
4933
0
                            sourceDatum->ellipsoid(),
4934
0
                            util::optional<std::string>(),
4935
0
                            datum::PrimeMeridian::GREENWICH),
4936
0
                        sourceGeographicCRS->coordinateSystem())
4937
0
                        .as_nullable();
4938
0
            }
4939
0
        } else {
4940
0
            auto vertSourceCRS =
4941
0
                std::dynamic_pointer_cast<VerticalCRS>(sourceCRS);
4942
0
            if (!vertSourceCRS) {
4943
0
                throw ParsingException(
4944
0
                    "Cannot find GeographicCRS or VerticalCRS in sourceCRS");
4945
0
            }
4946
0
            const auto &axis = vertSourceCRS->coordinateSystem()->axisList()[0];
4947
0
            if (axis->unit() == common::UnitOfMeasure::METRE &&
4948
0
                &(axis->direction()) == &AxisDirection::UP) {
4949
0
                sourceTransformationCRS = sourceCRS;
4950
0
            } else {
4951
0
                std::string sourceTransformationCRSName(
4952
0
                    vertSourceCRS->nameStr());
4953
0
                if (ends_with(sourceTransformationCRSName, " (ftUS)")) {
4954
0
                    sourceTransformationCRSName.resize(
4955
0
                        sourceTransformationCRSName.size() - strlen(" (ftUS)"));
4956
0
                }
4957
0
                if (ends_with(sourceTransformationCRSName, " depth")) {
4958
0
                    sourceTransformationCRSName.resize(
4959
0
                        sourceTransformationCRSName.size() - strlen(" depth"));
4960
0
                }
4961
0
                if (!ends_with(sourceTransformationCRSName, " height")) {
4962
0
                    sourceTransformationCRSName += " height";
4963
0
                }
4964
0
                sourceTransformationCRS =
4965
0
                    VerticalCRS::create(
4966
0
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
4967
0
                                          sourceTransformationCRSName),
4968
0
                        vertSourceCRS->datum(), vertSourceCRS->datumEnsemble(),
4969
0
                        VerticalCS::createGravityRelatedHeight(
4970
0
                            common::UnitOfMeasure::METRE))
4971
0
                        .as_nullable();
4972
0
            }
4973
0
        }
4974
0
    } else {
4975
0
        sourceTransformationCRS = sourceCRS;
4976
0
    }
4977
0
    return NN_NO_CHECK(sourceTransformationCRS);
4978
0
}
4979
4980
// ---------------------------------------------------------------------------
4981
4982
2.30k
CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) {
4983
2.30k
    const auto *nodeP = node->GP();
4984
2.30k
    const auto &nodeValue = nodeP->value();
4985
2.30k
    auto &vdatumNode =
4986
2.30k
        nodeP->lookForChild(WKTConstants::VDATUM, WKTConstants::VERT_DATUM,
4987
2.30k
                            WKTConstants::VERTICALDATUM, WKTConstants::VRF);
4988
2.30k
    auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE);
4989
    // like in ESRI  VERTCS["WGS_1984",DATUM["D_WGS_1984",
4990
    //               SPHEROID["WGS_1984",6378137.0,298.257223563]],
4991
    //               PARAMETER["Vertical_Shift",0.0],
4992
    //               PARAMETER["Direction",1.0],UNIT["Meter",1.0]
4993
2.30k
    auto &geogDatumNode = ci_equal(nodeValue, WKTConstants::VERTCS)
4994
2.30k
                              ? nodeP->lookForChild(WKTConstants::DATUM)
4995
2.30k
                              : null_node;
4996
2.30k
    if (isNull(vdatumNode) && isNull(geogDatumNode) && isNull(ensembleNode)) {
4997
43
        throw ParsingException("Missing VDATUM or ENSEMBLE node");
4998
43
    }
4999
5000
11.2k
    for (const auto &childNode : nodeP->children()) {
5001
11.2k
        const auto &childNodeChildren = childNode->GP()->children();
5002
11.2k
        if (childNodeChildren.size() == 2 &&
5003
1.85k
            ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) &&
5004
31
            childNodeChildren[0]->GP()->value() == "\"Vertical_Shift\"") {
5005
0
            esriStyle_ = true;
5006
0
            break;
5007
0
        }
5008
11.2k
    }
5009
5010
2.26k
    auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC);
5011
2.26k
    auto vdatum =
5012
2.26k
        !isNull(geogDatumNode)
5013
2.26k
            ? VerticalReferenceFrame::create(
5014
287
                  PropertyMap()
5015
287
                      .set(IdentifiedObject::NAME_KEY,
5016
287
                           buildGeodeticReferenceFrame(geogDatumNode,
5017
287
                                                       PrimeMeridian::GREENWICH,
5018
287
                                                       null_node)
5019
287
                               ->nameStr())
5020
287
                      .set("VERT_DATUM_TYPE", "2002"))
5021
287
                  .as_nullable()
5022
2.26k
        : !isNull(vdatumNode)
5023
1.97k
            ? buildVerticalReferenceFrame(vdatumNode, dynamicNode).as_nullable()
5024
1.97k
            : nullptr;
5025
2.26k
    auto datumEnsemble =
5026
2.26k
        !isNull(ensembleNode)
5027
2.26k
            ? buildDatumEnsemble(ensembleNode, nullptr, false).as_nullable()
5028
2.26k
            : nullptr;
5029
5030
2.26k
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5031
2.26k
    if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::VERT_CS) &&
5032
1.49k
        !ci_equal(nodeValue, WKTConstants::VERTCS) &&
5033
0
        !ci_equal(nodeValue, WKTConstants::BASEVERTCRS)) {
5034
0
        ThrowMissing(WKTConstants::CS_);
5035
0
    }
5036
2.26k
    auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(
5037
2.26k
        buildCS(csNode, node, UnitOfMeasure::NONE));
5038
2.26k
    if (!verticalCS) {
5039
0
        ThrowNotExpectedCSType(VerticalCS::WKT2_TYPE);
5040
0
    }
5041
5042
2.26k
    if (vdatum && vdatum->getWKT1DatumType() == "2002" &&
5043
304
        &(verticalCS->axisList()[0]->direction()) == &(AxisDirection::UP)) {
5044
304
        verticalCS =
5045
304
            VerticalCS::create(
5046
304
                util::PropertyMap(),
5047
304
                CoordinateSystemAxis::create(
5048
304
                    util::PropertyMap().set(IdentifiedObject::NAME_KEY,
5049
304
                                            "ellipsoidal height"),
5050
304
                    "h", AxisDirection::UP, verticalCS->axisList()[0]->unit()))
5051
304
                .as_nullable();
5052
304
    }
5053
5054
2.26k
    auto &props = buildProperties(node);
5055
5056
2.26k
    if (esriStyle_ && dbContext_) {
5057
425
        std::string outTableName;
5058
425
        std::string authNameFromAlias;
5059
425
        std::string codeFromAlias;
5060
425
        auto authFactory =
5061
425
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
5062
425
        const std::string vertCRSName = stripQuotes(nodeP->children()[0]);
5063
425
        auto officialName = authFactory->getOfficialNameFromAlias(
5064
425
            vertCRSName, "vertical_crs", "ESRI", false, outTableName,
5065
425
            authNameFromAlias, codeFromAlias);
5066
425
        if (!officialName.empty()) {
5067
0
            props.set(IdentifiedObject::NAME_KEY, officialName);
5068
0
        }
5069
425
    }
5070
5071
    // Deal with Lidar WKT1 VertCRS that embeds geoid model in CRS name,
5072
    // following conventions from
5073
    // https://pubs.usgs.gov/tm/11b4/pdf/tm11-B4.pdf
5074
    // page 9
5075
2.26k
    if (ci_equal(nodeValue, WKTConstants::VERT_CS) ||
5076
2.20k
        ci_equal(nodeValue, WKTConstants::VERTCS)) {
5077
2.20k
        std::string name;
5078
2.20k
        if (props.getStringValue(IdentifiedObject::NAME_KEY, name)) {
5079
2.20k
            std::string geoidName;
5080
2.20k
            for (const char *prefix :
5081
2.20k
                 {"NAVD88 - ", "NAVD88 via ", "NAVD88 height - ",
5082
8.81k
                  "NAVD88 height (ftUS) - "}) {
5083
8.81k
                if (starts_with(name, prefix)) {
5084
14
                    geoidName = name.substr(strlen(prefix));
5085
14
                    auto pos = geoidName.find_first_of(" (");
5086
14
                    if (pos != std::string::npos) {
5087
14
                        geoidName.resize(pos);
5088
14
                    }
5089
14
                    break;
5090
14
                }
5091
8.81k
            }
5092
2.20k
            if (!geoidName.empty()) {
5093
14
                const auto &axis = verticalCS->axisList()[0];
5094
14
                const auto &dir = axis->direction();
5095
14
                if (dir == cs::AxisDirection::UP) {
5096
14
                    if (axis->unit() == common::UnitOfMeasure::METRE) {
5097
14
                        props.set(IdentifiedObject::NAME_KEY, "NAVD88 height");
5098
14
                        props.set(Identifier::CODE_KEY, 5703);
5099
14
                        props.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
5100
14
                    } else if (axis->unit().name() == "US survey foot") {
5101
0
                        props.set(IdentifiedObject::NAME_KEY,
5102
0
                                  "NAVD88 height (ftUS)");
5103
0
                        props.set(Identifier::CODE_KEY, 6360);
5104
0
                        props.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
5105
0
                    }
5106
14
                }
5107
14
                PropertyMap propsModel;
5108
14
                propsModel.set(IdentifiedObject::NAME_KEY, toupper(geoidName));
5109
14
                PropertyMap propsDatum;
5110
14
                propsDatum.set(IdentifiedObject::NAME_KEY,
5111
14
                               "North American Vertical Datum 1988");
5112
14
                propsDatum.set(Identifier::CODE_KEY, 5103);
5113
14
                propsDatum.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
5114
14
                vdatum =
5115
14
                    VerticalReferenceFrame::create(propsDatum).as_nullable();
5116
14
                const auto dummyCRS =
5117
14
                    VerticalCRS::create(PropertyMap(), vdatum, datumEnsemble,
5118
14
                                        NN_NO_CHECK(verticalCS));
5119
14
                const auto model(Transformation::create(
5120
14
                    propsModel, dummyCRS, dummyCRS, nullptr,
5121
14
                    OperationMethod::create(
5122
14
                        PropertyMap(), std::vector<OperationParameterNNPtr>()),
5123
14
                    {}, {}));
5124
14
                props.set("GEOID_MODEL", model);
5125
14
            }
5126
2.20k
        }
5127
2.20k
    }
5128
5129
2.26k
    auto &geoidModelNode = nodeP->lookForChild(WKTConstants::GEOIDMODEL);
5130
2.26k
    if (!isNull(geoidModelNode)) {
5131
85
        ArrayOfBaseObjectNNPtr arrayModels = ArrayOfBaseObject::create();
5132
2.91k
        for (const auto &childNode : nodeP->children()) {
5133
2.91k
            const auto &childNodeChildren = childNode->GP()->children();
5134
2.91k
            if (childNodeChildren.size() >= 1 &&
5135
1.83k
                ci_equal(childNode->GP()->value(), WKTConstants::GEOIDMODEL)) {
5136
406
                auto &propsModel = buildProperties(childNode);
5137
406
                const auto dummyCRS =
5138
406
                    VerticalCRS::create(PropertyMap(), vdatum, datumEnsemble,
5139
406
                                        NN_NO_CHECK(verticalCS));
5140
406
                const auto model(Transformation::create(
5141
406
                    propsModel, dummyCRS, dummyCRS, nullptr,
5142
406
                    OperationMethod::create(
5143
406
                        PropertyMap(), std::vector<OperationParameterNNPtr>()),
5144
406
                    {}, {}));
5145
406
                arrayModels->add(model);
5146
406
            }
5147
2.91k
        }
5148
85
        props.set("GEOID_MODEL", arrayModels);
5149
85
    }
5150
5151
2.26k
    auto crs = nn_static_pointer_cast<CRS>(VerticalCRS::create(
5152
2.26k
        props, vdatum, datumEnsemble, NN_NO_CHECK(verticalCS)));
5153
5154
2.26k
    if (!isNull(vdatumNode)) {
5155
2.02k
        auto &extensionNode = vdatumNode->lookForChild(WKTConstants::EXTENSION);
5156
2.02k
        const auto &extensionChildren = extensionNode->GP()->children();
5157
2.02k
        if (extensionChildren.size() == 2) {
5158
92
            if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) {
5159
0
                const auto gridName(stripQuotes(extensionChildren[1]));
5160
                // This is the expansion of EPSG:5703 by old GDAL versions.
5161
                // See
5162
                // 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
5163
                // It is unlikely that the user really explicitly wants this.
5164
0
                if (gridName != "g2003conus.gtx,g2003alaska.gtx,"
5165
0
                                "g2003h01.gtx,g2003p01.gtx" &&
5166
0
                    gridName != "g2012a_conus.gtx,g2012a_alaska.gtx,"
5167
0
                                "g2012a_guam.gtx,g2012a_hawaii.gtx,"
5168
0
                                "g2012a_puertorico.gtx,g2012a_samoa.gtx") {
5169
0
                    auto geogCRS =
5170
0
                        geogCRSOfCompoundCRS_ &&
5171
0
                                geogCRSOfCompoundCRS_->primeMeridian()
5172
0
                                        ->longitude()
5173
0
                                        .getSIValue() == 0 &&
5174
0
                                geogCRSOfCompoundCRS_->coordinateSystem()
5175
0
                                        ->axisList()[0]
5176
0
                                        ->unit() == UnitOfMeasure::DEGREE
5177
0
                            ? geogCRSOfCompoundCRS_->promoteTo3D(std::string(),
5178
0
                                                                 dbContext_)
5179
0
                            : GeographicCRS::EPSG_4979;
5180
5181
0
                    auto sourceTransformationCRS =
5182
0
                        createBoundCRSSourceTransformationCRS(
5183
0
                            crs.as_nullable(), geogCRS.as_nullable());
5184
0
                    auto transformation = Transformation::
5185
0
                        createGravityRelatedHeightToGeographic3D(
5186
0
                            PropertyMap().set(
5187
0
                                IdentifiedObject::NAME_KEY,
5188
0
                                sourceTransformationCRS->nameStr() + " to " +
5189
0
                                    geogCRS->nameStr() + " ellipsoidal height"),
5190
0
                            sourceTransformationCRS, geogCRS, nullptr, gridName,
5191
0
                            std::vector<PositionalAccuracyNNPtr>());
5192
0
                    return nn_static_pointer_cast<CRS>(
5193
0
                        BoundCRS::create(crs, geogCRS, transformation));
5194
0
                }
5195
0
            }
5196
92
        }
5197
2.02k
    }
5198
5199
2.26k
    return crs;
5200
2.26k
}
5201
5202
// ---------------------------------------------------------------------------
5203
5204
DerivedVerticalCRSNNPtr
5205
0
WKTParser::Private::buildDerivedVerticalCRS(const WKTNodeNNPtr &node) {
5206
0
    const auto *nodeP = node->GP();
5207
0
    auto &baseVertCRSNode = nodeP->lookForChild(WKTConstants::BASEVERTCRS);
5208
    // given the constraints enforced on calling code path
5209
0
    assert(!isNull(baseVertCRSNode));
5210
5211
0
    auto baseVertCRS_tmp = buildVerticalCRS(baseVertCRSNode);
5212
0
    auto baseVertCRS = NN_NO_CHECK(baseVertCRS_tmp->extractVerticalCRS());
5213
5214
0
    auto &derivingConversionNode =
5215
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5216
0
    if (isNull(derivingConversionNode)) {
5217
0
        ThrowMissing(WKTConstants::DERIVINGCONVERSION);
5218
0
    }
5219
0
    auto derivingConversion = buildConversion(
5220
0
        derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
5221
5222
0
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5223
0
    if (isNull(csNode)) {
5224
0
        ThrowMissing(WKTConstants::CS_);
5225
0
    }
5226
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5227
5228
0
    auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(cs);
5229
0
    if (!verticalCS) {
5230
0
        throw ParsingException(
5231
0
            concat("vertical CS expected, but found ", cs->getWKT2Type(true)));
5232
0
    }
5233
5234
0
    return DerivedVerticalCRS::create(buildProperties(node), baseVertCRS,
5235
0
                                      derivingConversion,
5236
0
                                      NN_NO_CHECK(verticalCS));
5237
0
}
5238
5239
// ---------------------------------------------------------------------------
5240
5241
2.05k
CRSNNPtr WKTParser::Private::buildCompoundCRS(const WKTNodeNNPtr &node) {
5242
2.05k
    std::vector<CRSNNPtr> components;
5243
2.05k
    bool bFirstNode = true;
5244
17.3k
    for (const auto &child : node->GP()->children()) {
5245
17.3k
        auto crs = buildCRS(child);
5246
17.3k
        if (crs) {
5247
6.25k
            if (bFirstNode) {
5248
1.60k
                geogCRSOfCompoundCRS_ = crs->extractGeographicCRS();
5249
1.60k
                bFirstNode = false;
5250
1.60k
            }
5251
6.25k
            components.push_back(NN_NO_CHECK(crs));
5252
6.25k
        }
5253
17.3k
    }
5254
5255
2.05k
    if (ci_equal(node->GP()->value(), WKTConstants::COMPD_CS)) {
5256
787
        return CompoundCRS::createLax(buildProperties(node), components,
5257
787
                                      dbContext_);
5258
1.26k
    } else {
5259
1.26k
        return CompoundCRS::create(buildProperties(node), components);
5260
1.26k
    }
5261
2.05k
}
5262
5263
// ---------------------------------------------------------------------------
5264
5265
static TransformationNNPtr buildTransformationForBoundCRS(
5266
    DatabaseContextPtr &dbContext,
5267
    const util::PropertyMap &abridgedNodeProperties,
5268
    const util::PropertyMap &methodNodeProperties, const CRSNNPtr &sourceCRS,
5269
    const CRSNNPtr &targetCRS, std::vector<OperationParameterNNPtr> &parameters,
5270
0
    std::vector<ParameterValueNNPtr> &values) {
5271
5272
0
    auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
5273
0
        dbContext, parameters, values);
5274
5275
0
    const auto sourceTransformationCRS(
5276
0
        createBoundCRSSourceTransformationCRS(sourceCRS, targetCRS));
5277
0
    auto transformation = Transformation::create(
5278
0
        abridgedNodeProperties, sourceTransformationCRS, targetCRS,
5279
0
        interpolationCRS, methodNodeProperties, parameters, values,
5280
0
        std::vector<PositionalAccuracyNNPtr>());
5281
5282
    // If the transformation is a "Geographic3D to GravityRelatedHeight" one,
5283
    // then the sourceCRS is expected to be a GeographicCRS and the target a
5284
    // VerticalCRS. Due to how things work in a BoundCRS, we have the opposite,
5285
    // so use our "GravityRelatedHeight to Geographic3D" method instead.
5286
0
    if (Transformation::isGeographic3DToGravityRelatedHeight(
5287
0
            transformation->method(), true) &&
5288
0
        dynamic_cast<VerticalCRS *>(sourceTransformationCRS.get()) &&
5289
0
        dynamic_cast<GeographicCRS *>(targetCRS.get())) {
5290
0
        auto fileParameter = transformation->parameterValue(
5291
0
            EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
5292
0
            EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
5293
0
        if (fileParameter &&
5294
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
5295
0
            const auto &filename = fileParameter->valueFile();
5296
5297
0
            transformation =
5298
0
                Transformation::createGravityRelatedHeightToGeographic3D(
5299
0
                    abridgedNodeProperties, sourceTransformationCRS, targetCRS,
5300
0
                    interpolationCRS, filename,
5301
0
                    std::vector<PositionalAccuracyNNPtr>());
5302
0
        }
5303
0
    }
5304
0
    return transformation;
5305
0
}
5306
5307
// ---------------------------------------------------------------------------
5308
5309
3
BoundCRSNNPtr WKTParser::Private::buildBoundCRS(const WKTNodeNNPtr &node) {
5310
3
    const auto *nodeP = node->GP();
5311
3
    auto &abridgedNode =
5312
3
        nodeP->lookForChild(WKTConstants::ABRIDGEDTRANSFORMATION);
5313
3
    if (isNull(abridgedNode)) {
5314
3
        ThrowNotEnoughChildren(WKTConstants::ABRIDGEDTRANSFORMATION);
5315
3
    }
5316
5317
0
    auto &methodNode = abridgedNode->GP()->lookForChild(WKTConstants::METHOD);
5318
0
    if (isNull(methodNode)) {
5319
0
        ThrowMissing(WKTConstants::METHOD);
5320
0
    }
5321
0
    if (methodNode->GP()->childrenSize() == 0) {
5322
0
        ThrowNotEnoughChildren(WKTConstants::METHOD);
5323
0
    }
5324
5325
0
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
5326
0
    const auto &sourceCRSNodeChildren = sourceCRSNode->GP()->children();
5327
0
    if (sourceCRSNodeChildren.size() != 1) {
5328
0
        ThrowNotEnoughChildren(WKTConstants::SOURCECRS);
5329
0
    }
5330
0
    auto sourceCRS = buildCRS(sourceCRSNodeChildren[0]);
5331
0
    if (!sourceCRS) {
5332
0
        throw ParsingException("Invalid content in SOURCECRS node");
5333
0
    }
5334
5335
0
    auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
5336
0
    const auto &targetCRSNodeChildren = targetCRSNode->GP()->children();
5337
0
    if (targetCRSNodeChildren.size() != 1) {
5338
0
        ThrowNotEnoughChildren(WKTConstants::TARGETCRS);
5339
0
    }
5340
0
    auto targetCRS = buildCRS(targetCRSNodeChildren[0]);
5341
0
    if (!targetCRS) {
5342
0
        throw ParsingException("Invalid content in TARGETCRS node");
5343
0
    }
5344
5345
0
    std::vector<OperationParameterNNPtr> parameters;
5346
0
    std::vector<ParameterValueNNPtr> values;
5347
0
    const auto &defaultLinearUnit = UnitOfMeasure::NONE;
5348
0
    const auto &defaultAngularUnit = UnitOfMeasure::NONE;
5349
0
    consumeParameters(abridgedNode, true, parameters, values, defaultLinearUnit,
5350
0
                      defaultAngularUnit);
5351
5352
0
    const auto nnSourceCRS = NN_NO_CHECK(sourceCRS);
5353
0
    const auto nnTargetCRS = NN_NO_CHECK(targetCRS);
5354
0
    const auto transformation = buildTransformationForBoundCRS(
5355
0
        dbContext_, buildProperties(abridgedNode), buildProperties(methodNode),
5356
0
        nnSourceCRS, nnTargetCRS, parameters, values);
5357
5358
0
    return BoundCRS::create(buildProperties(node, false, false),
5359
0
                            NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS),
5360
0
                            transformation);
5361
0
}
5362
5363
// ---------------------------------------------------------------------------
5364
5365
TemporalCSNNPtr
5366
0
WKTParser::Private::buildTemporalCS(const WKTNodeNNPtr &parentNode) {
5367
5368
0
    auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS_);
5369
0
    if (isNull(csNode) &&
5370
0
        !ci_equal(parentNode->GP()->value(), WKTConstants::BASETIMECRS)) {
5371
0
        ThrowMissing(WKTConstants::CS_);
5372
0
    }
5373
0
    auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE);
5374
0
    auto temporalCS = nn_dynamic_pointer_cast<TemporalCS>(cs);
5375
0
    if (!temporalCS) {
5376
0
        ThrowNotExpectedCSType(TemporalCS::WKT2_2015_TYPE);
5377
0
    }
5378
0
    return NN_NO_CHECK(temporalCS);
5379
0
}
5380
5381
// ---------------------------------------------------------------------------
5382
5383
TemporalCRSNNPtr
5384
4
WKTParser::Private::buildTemporalCRS(const WKTNodeNNPtr &node) {
5385
4
    auto &datumNode =
5386
4
        node->GP()->lookForChild(WKTConstants::TDATUM, WKTConstants::TIMEDATUM);
5387
4
    if (isNull(datumNode)) {
5388
4
        throw ParsingException("Missing TDATUM / TIMEDATUM node");
5389
4
    }
5390
5391
0
    return TemporalCRS::create(buildProperties(node),
5392
0
                               buildTemporalDatum(datumNode),
5393
0
                               buildTemporalCS(node));
5394
4
}
5395
5396
// ---------------------------------------------------------------------------
5397
5398
DerivedTemporalCRSNNPtr
5399
0
WKTParser::Private::buildDerivedTemporalCRS(const WKTNodeNNPtr &node) {
5400
0
    const auto *nodeP = node->GP();
5401
0
    auto &baseCRSNode = nodeP->lookForChild(WKTConstants::BASETIMECRS);
5402
    // given the constraints enforced on calling code path
5403
0
    assert(!isNull(baseCRSNode));
5404
5405
0
    auto &derivingConversionNode =
5406
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5407
0
    if (isNull(derivingConversionNode)) {
5408
0
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5409
0
    }
5410
5411
0
    return DerivedTemporalCRS::create(
5412
0
        buildProperties(node), buildTemporalCRS(baseCRSNode),
5413
0
        buildConversion(derivingConversionNode, UnitOfMeasure::NONE,
5414
0
                        UnitOfMeasure::NONE),
5415
0
        buildTemporalCS(node));
5416
0
}
5417
5418
// ---------------------------------------------------------------------------
5419
5420
EngineeringCRSNNPtr
5421
12
WKTParser::Private::buildEngineeringCRS(const WKTNodeNNPtr &node) {
5422
12
    const auto *nodeP = node->GP();
5423
12
    auto &datumNode = nodeP->lookForChild(WKTConstants::EDATUM,
5424
12
                                          WKTConstants::ENGINEERINGDATUM);
5425
12
    if (isNull(datumNode)) {
5426
4
        throw ParsingException("Missing EDATUM / ENGINEERINGDATUM node");
5427
4
    }
5428
5429
8
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5430
8
    if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::BASEENGCRS)) {
5431
8
        ThrowMissing(WKTConstants::CS_);
5432
8
    }
5433
5434
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5435
0
    return EngineeringCRS::create(buildProperties(node),
5436
0
                                  buildEngineeringDatum(datumNode), cs);
5437
8
}
5438
5439
// ---------------------------------------------------------------------------
5440
5441
EngineeringCRSNNPtr
5442
3.79k
WKTParser::Private::buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node) {
5443
3.79k
    auto &datumNode = node->GP()->lookForChild(WKTConstants::LOCAL_DATUM);
5444
3.79k
    auto cs = buildCS(null_node, node, UnitOfMeasure::NONE);
5445
3.79k
    auto datum = EngineeringDatum::create(
5446
3.79k
        !isNull(datumNode)
5447
3.79k
            ? buildProperties(datumNode)
5448
3.79k
            :
5449
            // In theory OGC 01-009 mandates LOCAL_DATUM, but GDAL
5450
            // has a tradition of emitting just LOCAL_CS["foo"]
5451
3.79k
            []() {
5452
3.70k
                PropertyMap map;
5453
3.70k
                map.set(IdentifiedObject::NAME_KEY, UNKNOWN_ENGINEERING_DATUM);
5454
3.70k
                return map;
5455
3.70k
            }());
5456
3.79k
    return EngineeringCRS::create(buildProperties(node), datum, cs);
5457
3.79k
}
5458
5459
// ---------------------------------------------------------------------------
5460
5461
DerivedEngineeringCRSNNPtr
5462
0
WKTParser::Private::buildDerivedEngineeringCRS(const WKTNodeNNPtr &node) {
5463
0
    const auto *nodeP = node->GP();
5464
0
    auto &baseEngCRSNode = nodeP->lookForChild(WKTConstants::BASEENGCRS);
5465
    // given the constraints enforced on calling code path
5466
0
    assert(!isNull(baseEngCRSNode));
5467
5468
0
    auto baseEngCRS = buildEngineeringCRS(baseEngCRSNode);
5469
5470
0
    auto &derivingConversionNode =
5471
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5472
0
    if (isNull(derivingConversionNode)) {
5473
0
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5474
0
    }
5475
0
    auto derivingConversion = buildConversion(
5476
0
        derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
5477
5478
0
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5479
0
    if (isNull(csNode)) {
5480
0
        ThrowMissing(WKTConstants::CS_);
5481
0
    }
5482
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5483
5484
0
    return DerivedEngineeringCRS::create(buildProperties(node), baseEngCRS,
5485
0
                                         derivingConversion, cs);
5486
0
}
5487
5488
// ---------------------------------------------------------------------------
5489
5490
ParametricCSNNPtr
5491
0
WKTParser::Private::buildParametricCS(const WKTNodeNNPtr &parentNode) {
5492
5493
0
    auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS_);
5494
0
    if (isNull(csNode) &&
5495
0
        !ci_equal(parentNode->GP()->value(), WKTConstants::BASEPARAMCRS)) {
5496
0
        ThrowMissing(WKTConstants::CS_);
5497
0
    }
5498
0
    auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE);
5499
0
    auto parametricCS = nn_dynamic_pointer_cast<ParametricCS>(cs);
5500
0
    if (!parametricCS) {
5501
0
        ThrowNotExpectedCSType(ParametricCS::WKT2_TYPE);
5502
0
    }
5503
0
    return NN_NO_CHECK(parametricCS);
5504
0
}
5505
5506
// ---------------------------------------------------------------------------
5507
5508
ParametricCRSNNPtr
5509
1
WKTParser::Private::buildParametricCRS(const WKTNodeNNPtr &node) {
5510
1
    auto &datumNode = node->GP()->lookForChild(WKTConstants::PDATUM,
5511
1
                                               WKTConstants::PARAMETRICDATUM);
5512
1
    if (isNull(datumNode)) {
5513
1
        throw ParsingException("Missing PDATUM / PARAMETRICDATUM node");
5514
1
    }
5515
5516
0
    return ParametricCRS::create(buildProperties(node),
5517
0
                                 buildParametricDatum(datumNode),
5518
0
                                 buildParametricCS(node));
5519
1
}
5520
5521
// ---------------------------------------------------------------------------
5522
5523
DerivedParametricCRSNNPtr
5524
0
WKTParser::Private::buildDerivedParametricCRS(const WKTNodeNNPtr &node) {
5525
0
    const auto *nodeP = node->GP();
5526
0
    auto &baseParamCRSNode = nodeP->lookForChild(WKTConstants::BASEPARAMCRS);
5527
    // given the constraints enforced on calling code path
5528
0
    assert(!isNull(baseParamCRSNode));
5529
5530
0
    auto &derivingConversionNode =
5531
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5532
0
    if (isNull(derivingConversionNode)) {
5533
0
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5534
0
    }
5535
5536
0
    return DerivedParametricCRS::create(
5537
0
        buildProperties(node), buildParametricCRS(baseParamCRSNode),
5538
0
        buildConversion(derivingConversionNode, UnitOfMeasure::NONE,
5539
0
                        UnitOfMeasure::NONE),
5540
0
        buildParametricCS(node));
5541
0
}
5542
5543
// ---------------------------------------------------------------------------
5544
5545
DerivedProjectedCRSNNPtr
5546
1
WKTParser::Private::buildDerivedProjectedCRS(const WKTNodeNNPtr &node) {
5547
1
    const auto *nodeP = node->GP();
5548
1
    auto &baseProjCRSNode = nodeP->lookForChild(WKTConstants::BASEPROJCRS);
5549
1
    if (isNull(baseProjCRSNode)) {
5550
1
        ThrowNotEnoughChildren(WKTConstants::BASEPROJCRS);
5551
1
    }
5552
0
    auto baseProjCRS = buildProjectedCRS(baseProjCRSNode);
5553
5554
0
    auto &conversionNode =
5555
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5556
0
    if (isNull(conversionNode)) {
5557
0
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5558
0
    }
5559
5560
0
    auto linearUnit = buildUnitInSubNode(node);
5561
0
    const auto &angularUnit =
5562
0
        baseProjCRS->baseCRS()->coordinateSystem()->axisList()[0]->unit();
5563
5564
0
    auto conversion = buildConversion(conversionNode, linearUnit, angularUnit);
5565
5566
0
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5567
0
    if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::PROJCS)) {
5568
0
        ThrowMissing(WKTConstants::CS_);
5569
0
    }
5570
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5571
5572
0
    if (cs->axisList().size() == 3 &&
5573
0
        baseProjCRS->coordinateSystem()->axisList().size() == 2) {
5574
0
        baseProjCRS = NN_NO_CHECK(util::nn_dynamic_pointer_cast<ProjectedCRS>(
5575
0
            baseProjCRS->promoteTo3D(std::string(), dbContext_)));
5576
0
    }
5577
5578
0
    return DerivedProjectedCRS::create(buildProperties(node), baseProjCRS,
5579
0
                                       conversion, cs);
5580
0
}
5581
5582
// ---------------------------------------------------------------------------
5583
5584
CoordinateMetadataNNPtr
5585
0
WKTParser::Private::buildCoordinateMetadata(const WKTNodeNNPtr &node) {
5586
0
    const auto *nodeP = node->GP();
5587
5588
0
    const auto &l_children = nodeP->children();
5589
0
    if (l_children.empty()) {
5590
0
        ThrowNotEnoughChildren(WKTConstants::COORDINATEMETADATA);
5591
0
    }
5592
5593
0
    auto crs = buildCRS(l_children[0]);
5594
0
    if (!crs) {
5595
0
        throw ParsingException("Invalid content in CRS node");
5596
0
    }
5597
5598
0
    auto &epochNode = nodeP->lookForChild(WKTConstants::EPOCH);
5599
0
    if (!isNull(epochNode)) {
5600
0
        const auto &epochChildren = epochNode->GP()->children();
5601
0
        if (epochChildren.empty()) {
5602
0
            ThrowMissing(WKTConstants::EPOCH);
5603
0
        }
5604
0
        double coordinateEpoch;
5605
0
        try {
5606
0
            coordinateEpoch = asDouble(epochChildren[0]);
5607
0
        } catch (const std::exception &) {
5608
0
            throw ParsingException("Invalid EPOCH node");
5609
0
        }
5610
0
        return CoordinateMetadata::create(NN_NO_CHECK(crs), coordinateEpoch,
5611
0
                                          dbContext_);
5612
0
    }
5613
5614
0
    return CoordinateMetadata::create(NN_NO_CHECK(crs));
5615
0
}
5616
5617
// ---------------------------------------------------------------------------
5618
5619
25.9k
static bool isGeodeticCRS(const std::string &name) {
5620
25.9k
    return ci_equal(name, WKTConstants::GEODCRS) ||       // WKT2
5621
25.9k
           ci_equal(name, WKTConstants::GEODETICCRS) ||   // WKT2
5622
25.9k
           ci_equal(name, WKTConstants::GEOGCRS) ||       // WKT2 2019
5623
25.9k
           ci_equal(name, WKTConstants::GEOGRAPHICCRS) || // WKT2 2019
5624
25.9k
           ci_equal(name, WKTConstants::GEOGCS) ||        // WKT1
5625
23.8k
           ci_equal(name, WKTConstants::GEOCCS);          // WKT1
5626
25.9k
}
5627
5628
// ---------------------------------------------------------------------------
5629
5630
25.9k
CRSPtr WKTParser::Private::buildCRS(const WKTNodeNNPtr &node) {
5631
25.9k
    const auto *nodeP = node->GP();
5632
25.9k
    const std::string &name(nodeP->value());
5633
5634
25.9k
    const auto applyHorizontalBoundCRSParams = [&](const CRSNNPtr &crs) {
5635
2.87k
        if (!toWGS84Parameters_.empty()) {
5636
0
            auto ret = BoundCRS::createFromTOWGS84(crs, toWGS84Parameters_);
5637
0
            toWGS84Parameters_.clear();
5638
0
            return util::nn_static_pointer_cast<CRS>(ret);
5639
2.87k
        } else if (!datumPROJ4Grids_.empty()) {
5640
0
            auto ret = BoundCRS::createFromNadgrids(crs, datumPROJ4Grids_);
5641
0
            datumPROJ4Grids_.clear();
5642
0
            return util::nn_static_pointer_cast<CRS>(ret);
5643
0
        }
5644
2.87k
        return crs;
5645
2.87k
    };
5646
5647
25.9k
    if (isGeodeticCRS(name)) {
5648
2.15k
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEGEOGCRS,
5649
2.15k
                                        WKTConstants::BASEGEODCRS))) {
5650
1
            return util::nn_static_pointer_cast<CRS>(
5651
1
                applyHorizontalBoundCRSParams(buildDerivedGeodeticCRS(node)));
5652
2.15k
        } else {
5653
2.15k
            return util::nn_static_pointer_cast<CRS>(
5654
2.15k
                applyHorizontalBoundCRSParams(buildGeodeticCRS(node)));
5655
2.15k
        }
5656
2.15k
    }
5657
5658
23.7k
    if (ci_equal(name, WKTConstants::PROJCS) ||
5659
22.6k
        ci_equal(name, WKTConstants::PROJCRS) ||
5660
22.6k
        ci_equal(name, WKTConstants::PROJECTEDCRS)) {
5661
        // Get the EXTENSION "PROJ4" node before attempting to call
5662
        // buildProjectedCRS() since formulations of WKT1_GDAL from GDAL 2.x
5663
        // with the netCDF driver and the lack the required UNIT[] node
5664
1.09k
        std::string projString = getExtensionProj4(nodeP);
5665
1.09k
        if (!projString.empty() &&
5666
0
            (starts_with(projString, "+proj=ob_tran +o_proj=longlat") ||
5667
0
             starts_with(projString, "+proj=ob_tran +o_proj=lonlat") ||
5668
0
             starts_with(projString, "+proj=ob_tran +o_proj=latlong") ||
5669
0
             starts_with(projString, "+proj=ob_tran +o_proj=latlon"))) {
5670
            // Those are not a projected CRS, but a DerivedGeographic one...
5671
0
            if (projString.find(" +type=crs") == std::string::npos) {
5672
0
                projString += " +type=crs";
5673
0
            }
5674
0
            try {
5675
0
                auto projObj =
5676
0
                    PROJStringParser().createFromPROJString(projString);
5677
0
                auto crs = nn_dynamic_pointer_cast<CRS>(projObj);
5678
0
                if (crs) {
5679
0
                    return util::nn_static_pointer_cast<CRS>(
5680
0
                        applyHorizontalBoundCRSParams(NN_NO_CHECK(crs)));
5681
0
                }
5682
0
            } catch (const io::ParsingException &) {
5683
0
            }
5684
0
        }
5685
1.09k
        return util::nn_static_pointer_cast<CRS>(
5686
1.09k
            applyHorizontalBoundCRSParams(buildProjectedCRS(node)));
5687
1.09k
    }
5688
5689
22.6k
    if (ci_equal(name, WKTConstants::VERT_CS) ||
5690
21.9k
        ci_equal(name, WKTConstants::VERTCS) ||
5691
20.3k
        ci_equal(name, WKTConstants::VERTCRS) ||
5692
20.3k
        ci_equal(name, WKTConstants::VERTICALCRS)) {
5693
2.30k
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEVERTCRS))) {
5694
0
            return util::nn_static_pointer_cast<CRS>(
5695
0
                buildDerivedVerticalCRS(node));
5696
2.30k
        } else {
5697
2.30k
            return util::nn_static_pointer_cast<CRS>(buildVerticalCRS(node));
5698
2.30k
        }
5699
2.30k
    }
5700
5701
20.3k
    if (ci_equal(name, WKTConstants::COMPD_CS) ||
5702
19.3k
        ci_equal(name, WKTConstants::COMPOUNDCRS)) {
5703
2.05k
        return util::nn_static_pointer_cast<CRS>(buildCompoundCRS(node));
5704
2.05k
    }
5705
5706
18.2k
    if (ci_equal(name, WKTConstants::BOUNDCRS)) {
5707
3
        return util::nn_static_pointer_cast<CRS>(buildBoundCRS(node));
5708
3
    }
5709
5710
18.2k
    if (ci_equal(name, WKTConstants::TIMECRS)) {
5711
4
        if (!isNull(nodeP->lookForChild(WKTConstants::BASETIMECRS))) {
5712
0
            return util::nn_static_pointer_cast<CRS>(
5713
0
                buildDerivedTemporalCRS(node));
5714
4
        } else {
5715
4
            return util::nn_static_pointer_cast<CRS>(buildTemporalCRS(node));
5716
4
        }
5717
4
    }
5718
5719
18.2k
    if (ci_equal(name, WKTConstants::DERIVEDPROJCRS)) {
5720
1
        return util::nn_static_pointer_cast<CRS>(
5721
1
            buildDerivedProjectedCRS(node));
5722
1
    }
5723
5724
18.2k
    if (ci_equal(name, WKTConstants::ENGCRS) ||
5725
18.2k
        ci_equal(name, WKTConstants::ENGINEERINGCRS)) {
5726
12
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEENGCRS))) {
5727
0
            return util::nn_static_pointer_cast<CRS>(
5728
0
                buildDerivedEngineeringCRS(node));
5729
12
        } else {
5730
12
            return util::nn_static_pointer_cast<CRS>(buildEngineeringCRS(node));
5731
12
        }
5732
12
    }
5733
5734
18.2k
    if (ci_equal(name, WKTConstants::LOCAL_CS)) {
5735
3.79k
        return util::nn_static_pointer_cast<CRS>(
5736
3.79k
            buildEngineeringCRSFromLocalCS(node));
5737
3.79k
    }
5738
5739
14.4k
    if (ci_equal(name, WKTConstants::PARAMETRICCRS)) {
5740
1
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEPARAMCRS))) {
5741
0
            return util::nn_static_pointer_cast<CRS>(
5742
0
                buildDerivedParametricCRS(node));
5743
1
        } else {
5744
1
            return util::nn_static_pointer_cast<CRS>(buildParametricCRS(node));
5745
1
        }
5746
1
    }
5747
5748
14.4k
    return nullptr;
5749
14.4k
}
5750
5751
// ---------------------------------------------------------------------------
5752
5753
8.56k
BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) {
5754
8.56k
    const auto *nodeP = node->GP();
5755
8.56k
    const std::string &name(nodeP->value());
5756
5757
8.56k
    auto crs = buildCRS(node);
5758
8.56k
    if (crs) {
5759
3.25k
        return util::nn_static_pointer_cast<BaseObject>(NN_NO_CHECK(crs));
5760
3.25k
    }
5761
5762
    // Datum handled by caller code WKTParser::createFromWKT()
5763
5764
5.31k
    if (ci_equal(name, WKTConstants::ENSEMBLE)) {
5765
103
        return util::nn_static_pointer_cast<BaseObject>(buildDatumEnsemble(
5766
103
            node, PrimeMeridian::GREENWICH,
5767
103
            !isNull(nodeP->lookForChild(WKTConstants::ELLIPSOID))));
5768
103
    }
5769
5770
5.20k
    if (ci_equal(name, WKTConstants::VDATUM) ||
5771
3.73k
        ci_equal(name, WKTConstants::VERT_DATUM) ||
5772
3.73k
        ci_equal(name, WKTConstants::VERTICALDATUM) ||
5773
3.72k
        ci_equal(name, WKTConstants::VRF)) {
5774
263
        return util::nn_static_pointer_cast<BaseObject>(
5775
263
            buildVerticalReferenceFrame(node, null_node));
5776
263
    }
5777
5778
4.94k
    if (ci_equal(name, WKTConstants::TDATUM) ||
5779
3.68k
        ci_equal(name, WKTConstants::TIMEDATUM)) {
5780
115
        return util::nn_static_pointer_cast<BaseObject>(
5781
115
            buildTemporalDatum(node));
5782
115
    }
5783
5784
4.82k
    if (ci_equal(name, WKTConstants::EDATUM) ||
5785
3.51k
        ci_equal(name, WKTConstants::ENGINEERINGDATUM)) {
5786
97
        return util::nn_static_pointer_cast<BaseObject>(
5787
97
            buildEngineeringDatum(node));
5788
97
    }
5789
5790
4.73k
    if (ci_equal(name, WKTConstants::PDATUM) ||
5791
3.26k
        ci_equal(name, WKTConstants::PARAMETRICDATUM)) {
5792
243
        return util::nn_static_pointer_cast<BaseObject>(
5793
243
            buildParametricDatum(node));
5794
243
    }
5795
5796
4.48k
    if (ci_equal(name, WKTConstants::ELLIPSOID) ||
5797
3.26k
        ci_equal(name, WKTConstants::SPHEROID)) {
5798
9
        return util::nn_static_pointer_cast<BaseObject>(buildEllipsoid(node));
5799
9
    }
5800
5801
4.48k
    if (ci_equal(name, WKTConstants::COORDINATEOPERATION)) {
5802
0
        auto transf = buildCoordinateOperation(node);
5803
5804
0
        const char *prefixes[] = {
5805
0
            "PROJ-based operation method: ",
5806
0
            "PROJ-based operation method (approximate): "};
5807
0
        for (const char *prefix : prefixes) {
5808
0
            if (starts_with(transf->method()->nameStr(), prefix)) {
5809
0
                auto projString =
5810
0
                    transf->method()->nameStr().substr(strlen(prefix));
5811
0
                return util::nn_static_pointer_cast<BaseObject>(
5812
0
                    PROJBasedOperation::create(
5813
0
                        PropertyMap(), projString, transf->sourceCRS(),
5814
0
                        transf->targetCRS(),
5815
0
                        transf->coordinateOperationAccuracies()));
5816
0
            }
5817
0
        }
5818
5819
0
        return util::nn_static_pointer_cast<BaseObject>(transf);
5820
0
    }
5821
5822
4.48k
    if (ci_equal(name, WKTConstants::CONVERSION)) {
5823
1.87k
        auto conv =
5824
1.87k
            buildConversion(node, UnitOfMeasure::METRE, UnitOfMeasure::DEGREE);
5825
5826
1.87k
        if (starts_with(conv->method()->nameStr(),
5827
1.87k
                        "PROJ-based operation method: ")) {
5828
0
            auto projString = conv->method()->nameStr().substr(
5829
0
                strlen("PROJ-based operation method: "));
5830
0
            return util::nn_static_pointer_cast<BaseObject>(
5831
0
                PROJBasedOperation::create(PropertyMap(), projString, nullptr,
5832
0
                                           nullptr, {}));
5833
0
        }
5834
5835
1.87k
        return util::nn_static_pointer_cast<BaseObject>(conv);
5836
1.87k
    }
5837
5838
2.60k
    if (ci_equal(name, WKTConstants::CONCATENATEDOPERATION)) {
5839
0
        return util::nn_static_pointer_cast<BaseObject>(
5840
0
            buildConcatenatedOperation(node));
5841
0
    }
5842
5843
2.60k
    if (ci_equal(name, WKTConstants::POINTMOTIONOPERATION)) {
5844
0
        return util::nn_static_pointer_cast<BaseObject>(
5845
0
            buildPointMotionOperation(node));
5846
0
    }
5847
5848
2.60k
    if (ci_equal(name, WKTConstants::ID) ||
5849
1.37k
        ci_equal(name, WKTConstants::AUTHORITY)) {
5850
1.37k
        return util::nn_static_pointer_cast<BaseObject>(
5851
1.37k
            NN_NO_CHECK(buildId(node, node, false, false)));
5852
1.37k
    }
5853
5854
1.22k
    if (ci_equal(name, WKTConstants::COORDINATEMETADATA)) {
5855
0
        return util::nn_static_pointer_cast<BaseObject>(
5856
0
            buildCoordinateMetadata(node));
5857
0
    }
5858
5859
1.22k
    throw ParsingException(concat("unhandled keyword: ", name));
5860
1.22k
}
5861
//! @endcond
5862
5863
// ---------------------------------------------------------------------------
5864
5865
//! @cond Doxygen_Suppress
5866
class JSONParser {
5867
    DatabaseContextPtr dbContext_{};
5868
    std::string deformationModelName_{};
5869
5870
    static std::string getString(const json &j, const char *key);
5871
    static json getObject(const json &j, const char *key);
5872
    static json getArray(const json &j, const char *key);
5873
    static int getInteger(const json &j, const char *key);
5874
    static double getNumber(const json &j, const char *key);
5875
    static UnitOfMeasure getUnit(const json &j, const char *key);
5876
    static std::string getName(const json &j);
5877
    static std::string getType(const json &j);
5878
    static Length getLength(const json &j, const char *key);
5879
    static Measure getMeasure(const json &j);
5880
5881
    IdentifierNNPtr buildId(const json &parentJ, const json &j,
5882
                            bool removeInverseOf);
5883
    static ObjectDomainPtr buildObjectDomain(const json &j);
5884
    PropertyMap buildProperties(const json &j, bool removeInverseOf = false,
5885
                                bool nameRequired = true);
5886
5887
    GeographicCRSNNPtr buildGeographicCRS(const json &j);
5888
    GeodeticCRSNNPtr buildGeodeticCRS(const json &j);
5889
    ProjectedCRSNNPtr buildProjectedCRS(const json &j);
5890
    ConversionNNPtr buildConversion(const json &j);
5891
    DatumEnsembleNNPtr buildDatumEnsemble(const json &j);
5892
    GeodeticReferenceFrameNNPtr buildGeodeticReferenceFrame(const json &j);
5893
    VerticalReferenceFrameNNPtr buildVerticalReferenceFrame(const json &j);
5894
    DynamicGeodeticReferenceFrameNNPtr
5895
    buildDynamicGeodeticReferenceFrame(const json &j);
5896
    DynamicVerticalReferenceFrameNNPtr
5897
    buildDynamicVerticalReferenceFrame(const json &j);
5898
    EllipsoidNNPtr buildEllipsoid(const json &j);
5899
    PrimeMeridianNNPtr buildPrimeMeridian(const json &j);
5900
    CoordinateSystemNNPtr buildCS(const json &j);
5901
    MeridianNNPtr buildMeridian(const json &j);
5902
    CoordinateSystemAxisNNPtr buildAxis(const json &j);
5903
    VerticalCRSNNPtr buildVerticalCRS(const json &j);
5904
    CRSNNPtr buildCRS(const json &j);
5905
    CompoundCRSNNPtr buildCompoundCRS(const json &j);
5906
    BoundCRSNNPtr buildBoundCRS(const json &j);
5907
    TransformationNNPtr buildTransformation(const json &j);
5908
    PointMotionOperationNNPtr buildPointMotionOperation(const json &j);
5909
    ConcatenatedOperationNNPtr buildConcatenatedOperation(const json &j);
5910
    CoordinateMetadataNNPtr buildCoordinateMetadata(const json &j);
5911
5912
    void buildGeodeticDatumOrDatumEnsemble(const json &j,
5913
                                           GeodeticReferenceFramePtr &datum,
5914
                                           DatumEnsemblePtr &datumEnsemble);
5915
5916
0
    static util::optional<std::string> getAnchor(const json &j) {
5917
0
        util::optional<std::string> anchor;
5918
0
        if (j.contains("anchor")) {
5919
0
            anchor = getString(j, "anchor");
5920
0
        }
5921
0
        return anchor;
5922
0
    }
5923
5924
0
    static util::optional<common::Measure> getAnchorEpoch(const json &j) {
5925
0
        if (j.contains("anchor_epoch")) {
5926
0
            return util::optional<common::Measure>(common::Measure(
5927
0
                getNumber(j, "anchor_epoch"), common::UnitOfMeasure::YEAR));
5928
0
        }
5929
0
        return util::optional<common::Measure>();
5930
0
    }
5931
5932
0
    EngineeringDatumNNPtr buildEngineeringDatum(const json &j) {
5933
0
        return EngineeringDatum::create(buildProperties(j), getAnchor(j));
5934
0
    }
5935
5936
0
    ParametricDatumNNPtr buildParametricDatum(const json &j) {
5937
0
        return ParametricDatum::create(buildProperties(j), getAnchor(j));
5938
0
    }
5939
5940
0
    TemporalDatumNNPtr buildTemporalDatum(const json &j) {
5941
0
        auto calendar = getString(j, "calendar");
5942
0
        auto origin = DateTime::create(j.contains("time_origin")
5943
0
                                           ? getString(j, "time_origin")
5944
0
                                           : std::string());
5945
0
        return TemporalDatum::create(buildProperties(j), origin, calendar);
5946
0
    }
5947
5948
    template <class TargetCRS, class DatumBuilderType,
5949
              class CSClass = CoordinateSystem>
5950
    util::nn<std::shared_ptr<TargetCRS>> buildCRS(const json &j,
5951
0
                                                  DatumBuilderType f) {
5952
0
        auto datum = (this->*f)(getObject(j, "datum"));
5953
0
        auto cs = buildCS(getObject(j, "coordinate_system"));
5954
0
        auto csCast = util::nn_dynamic_pointer_cast<CSClass>(cs);
5955
0
        if (!csCast) {
5956
0
            throw ParsingException("coordinate_system not of expected type");
5957
0
        }
5958
0
        return TargetCRS::create(buildProperties(j), datum,
5959
0
                                 NN_NO_CHECK(csCast));
5960
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&))
5961
5962
    template <class TargetCRS, class BaseCRS, class CSClass = CoordinateSystem>
5963
0
    util::nn<std::shared_ptr<TargetCRS>> buildDerivedCRS(const json &j) {
5964
0
        auto baseCRSObj = create(getObject(j, "base_crs"));
5965
0
        auto baseCRS = util::nn_dynamic_pointer_cast<BaseCRS>(baseCRSObj);
5966
0
        if (!baseCRS) {
5967
0
            throw ParsingException("base_crs not of expected type");
5968
0
        }
5969
0
        auto cs = buildCS(getObject(j, "coordinate_system"));
5970
0
        auto csCast = util::nn_dynamic_pointer_cast<CSClass>(cs);
5971
0
        if (!csCast) {
5972
0
            throw ParsingException("coordinate_system not of expected type");
5973
0
        }
5974
0
        auto conv = buildConversion(getObject(j, "conversion"));
5975
0
        return TargetCRS::create(buildProperties(j), NN_NO_CHECK(baseCRS), conv,
5976
0
                                 NN_NO_CHECK(csCast));
5977
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&)
5978
5979
  public:
5980
43
    JSONParser() = default;
5981
5982
43
    JSONParser &attachDatabaseContext(const DatabaseContextPtr &dbContext) {
5983
43
        dbContext_ = dbContext;
5984
43
        return *this;
5985
43
    }
5986
5987
    BaseObjectNNPtr create(const json &j);
5988
};
5989
5990
// ---------------------------------------------------------------------------
5991
5992
43
std::string JSONParser::getString(const json &j, const char *key) {
5993
43
    if (!j.contains(key)) {
5994
9
        throw ParsingException(std::string("Missing \"") + key + "\" key");
5995
9
    }
5996
34
    auto v = j[key];
5997
34
    if (!v.is_string()) {
5998
34
        throw ParsingException(std::string("The value of \"") + key +
5999
34
                               "\" should be a string");
6000
34
    }
6001
0
    return v.get<std::string>();
6002
34
}
6003
6004
// ---------------------------------------------------------------------------
6005
6006
0
json JSONParser::getObject(const json &j, const char *key) {
6007
0
    if (!j.contains(key)) {
6008
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6009
0
    }
6010
0
    auto v = j[key];
6011
0
    if (!v.is_object()) {
6012
0
        throw ParsingException(std::string("The value of \"") + key +
6013
0
                               "\" should be a object");
6014
0
    }
6015
0
    return v.get<json>();
6016
0
}
6017
6018
// ---------------------------------------------------------------------------
6019
6020
0
json JSONParser::getArray(const json &j, const char *key) {
6021
0
    if (!j.contains(key)) {
6022
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6023
0
    }
6024
0
    auto v = j[key];
6025
0
    if (!v.is_array()) {
6026
0
        throw ParsingException(std::string("The value of \"") + key +
6027
0
                               "\" should be a array");
6028
0
    }
6029
0
    return v.get<json>();
6030
0
}
6031
6032
// ---------------------------------------------------------------------------
6033
6034
0
int JSONParser::getInteger(const json &j, const char *key) {
6035
0
    if (!j.contains(key)) {
6036
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6037
0
    }
6038
0
    auto v = j[key];
6039
0
    if (!v.is_number()) {
6040
0
        throw ParsingException(std::string("The value of \"") + key +
6041
0
                               "\" should be an integer");
6042
0
    }
6043
0
    const double dbl = v.get<double>();
6044
0
    if (!(dbl >= std::numeric_limits<int>::min() &&
6045
0
          dbl <= std::numeric_limits<int>::max() &&
6046
0
          static_cast<int>(dbl) == dbl)) {
6047
0
        throw ParsingException(std::string("The value of \"") + key +
6048
0
                               "\" should be an integer");
6049
0
    }
6050
0
    return static_cast<int>(dbl);
6051
0
}
6052
6053
// ---------------------------------------------------------------------------
6054
6055
0
double JSONParser::getNumber(const json &j, const char *key) {
6056
0
    if (!j.contains(key)) {
6057
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6058
0
    }
6059
0
    auto v = j[key];
6060
0
    if (!v.is_number()) {
6061
0
        throw ParsingException(std::string("The value of \"") + key +
6062
0
                               "\" should be a number");
6063
0
    }
6064
0
    return v.get<double>();
6065
0
}
6066
6067
// ---------------------------------------------------------------------------
6068
6069
0
UnitOfMeasure JSONParser::getUnit(const json &j, const char *key) {
6070
0
    if (!j.contains(key)) {
6071
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6072
0
    }
6073
0
    auto v = j[key];
6074
0
    if (v.is_string()) {
6075
0
        auto vStr = v.get<std::string>();
6076
0
        for (const auto &unit : {UnitOfMeasure::METRE, UnitOfMeasure::DEGREE,
6077
0
                                 UnitOfMeasure::SCALE_UNITY}) {
6078
0
            if (vStr == unit.name())
6079
0
                return unit;
6080
0
        }
6081
0
        throw ParsingException("Unknown unit name: " + vStr);
6082
0
    }
6083
0
    if (!v.is_object()) {
6084
0
        throw ParsingException(std::string("The value of \"") + key +
6085
0
                               "\" should be a string or an object");
6086
0
    }
6087
0
    auto typeStr = getType(v);
6088
0
    UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN;
6089
0
    if (typeStr == "LinearUnit") {
6090
0
        type = UnitOfMeasure::Type::LINEAR;
6091
0
    } else if (typeStr == "AngularUnit") {
6092
0
        type = UnitOfMeasure::Type::ANGULAR;
6093
0
    } else if (typeStr == "ScaleUnit") {
6094
0
        type = UnitOfMeasure::Type::SCALE;
6095
0
    } else if (typeStr == "TimeUnit") {
6096
0
        type = UnitOfMeasure::Type::TIME;
6097
0
    } else if (typeStr == "ParametricUnit") {
6098
0
        type = UnitOfMeasure::Type::PARAMETRIC;
6099
0
    } else if (typeStr == "Unit") {
6100
0
        type = UnitOfMeasure::Type::UNKNOWN;
6101
0
    } else {
6102
0
        throw ParsingException("Unsupported value of \"type\"");
6103
0
    }
6104
0
    auto nameStr = getName(v);
6105
0
    auto convFactor = getNumber(v, "conversion_factor");
6106
0
    std::string authorityStr;
6107
0
    std::string codeStr;
6108
0
    if (v.contains("authority") && v.contains("code")) {
6109
0
        authorityStr = getString(v, "authority");
6110
0
        auto code = v["code"];
6111
0
        if (code.is_string()) {
6112
0
            codeStr = code.get<std::string>();
6113
0
        } else if (code.is_number_integer()) {
6114
0
            codeStr = internal::toString(code.get<int>());
6115
0
        } else {
6116
0
            throw ParsingException("Unexpected type for value of \"code\"");
6117
0
        }
6118
0
    }
6119
0
    return UnitOfMeasure(nameStr, convFactor, type, authorityStr, codeStr);
6120
0
}
6121
6122
// ---------------------------------------------------------------------------
6123
6124
0
std::string JSONParser::getName(const json &j) { return getString(j, "name"); }
6125
6126
// ---------------------------------------------------------------------------
6127
6128
0
std::string JSONParser::getType(const json &j) { return getString(j, "type"); }
6129
6130
// ---------------------------------------------------------------------------
6131
6132
0
Length JSONParser::getLength(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_number()) {
6138
0
        return Length(v.get<double>(), UnitOfMeasure::METRE);
6139
0
    }
6140
0
    if (v.is_object()) {
6141
0
        return Length(getMeasure(v));
6142
0
    }
6143
0
    throw ParsingException(std::string("The value of \"") + key +
6144
0
                           "\" should be a number or an object");
6145
0
}
6146
6147
// ---------------------------------------------------------------------------
6148
6149
0
Measure JSONParser::getMeasure(const json &j) {
6150
0
    return Measure(getNumber(j, "value"), getUnit(j, "unit"));
6151
0
}
6152
6153
// ---------------------------------------------------------------------------
6154
6155
0
ObjectDomainPtr JSONParser::buildObjectDomain(const json &j) {
6156
0
    optional<std::string> scope;
6157
0
    if (j.contains("scope")) {
6158
0
        scope = getString(j, "scope");
6159
0
    }
6160
0
    std::string area;
6161
0
    if (j.contains("area")) {
6162
0
        area = getString(j, "area");
6163
0
    }
6164
0
    std::vector<GeographicExtentNNPtr> geogExtent;
6165
0
    if (j.contains("bbox")) {
6166
0
        auto bbox = getObject(j, "bbox");
6167
0
        double south = getNumber(bbox, "south_latitude");
6168
0
        double west = getNumber(bbox, "west_longitude");
6169
0
        double north = getNumber(bbox, "north_latitude");
6170
0
        double east = getNumber(bbox, "east_longitude");
6171
0
        try {
6172
0
            geogExtent.emplace_back(
6173
0
                GeographicBoundingBox::create(west, south, east, north));
6174
0
        } catch (const std::exception &e) {
6175
0
            throw ParsingException(
6176
0
                std::string("Invalid bbox node: ").append(e.what()));
6177
0
        }
6178
0
    }
6179
6180
0
    std::vector<VerticalExtentNNPtr> verticalExtent;
6181
0
    if (j.contains("vertical_extent")) {
6182
0
        const auto vertical_extent = getObject(j, "vertical_extent");
6183
0
        const auto min = getNumber(vertical_extent, "minimum");
6184
0
        const auto max = getNumber(vertical_extent, "maximum");
6185
0
        const auto unit = vertical_extent.contains("unit")
6186
0
                              ? getUnit(vertical_extent, "unit")
6187
0
                              : UnitOfMeasure::METRE;
6188
0
        verticalExtent.emplace_back(VerticalExtent::create(
6189
0
            min, max, util::nn_make_shared<UnitOfMeasure>(unit)));
6190
0
    }
6191
6192
0
    std::vector<TemporalExtentNNPtr> temporalExtent;
6193
0
    if (j.contains("temporal_extent")) {
6194
0
        const auto temporal_extent = getObject(j, "temporal_extent");
6195
0
        const auto start = getString(temporal_extent, "start");
6196
0
        const auto end = getString(temporal_extent, "end");
6197
0
        temporalExtent.emplace_back(TemporalExtent::create(start, end));
6198
0
    }
6199
6200
0
    if (scope.has_value() || !area.empty() || !geogExtent.empty() ||
6201
0
        !verticalExtent.empty() || !temporalExtent.empty()) {
6202
0
        util::optional<std::string> description;
6203
0
        if (!area.empty())
6204
0
            description = area;
6205
0
        ExtentPtr extent;
6206
0
        if (description.has_value() || !geogExtent.empty() ||
6207
0
            !verticalExtent.empty() || !temporalExtent.empty()) {
6208
0
            extent = Extent::create(description, geogExtent, verticalExtent,
6209
0
                                    temporalExtent)
6210
0
                         .as_nullable();
6211
0
        }
6212
0
        return ObjectDomain::create(scope, extent).as_nullable();
6213
0
    }
6214
0
    return nullptr;
6215
0
}
6216
6217
// ---------------------------------------------------------------------------
6218
6219
IdentifierNNPtr JSONParser::buildId(const json &parentJ, const json &j,
6220
0
                                    bool removeInverseOf) {
6221
6222
0
    PropertyMap propertiesId;
6223
0
    auto codeSpace(getString(j, "authority"));
6224
0
    if (removeInverseOf && starts_with(codeSpace, "INVERSE(") &&
6225
0
        codeSpace.back() == ')') {
6226
0
        codeSpace = codeSpace.substr(strlen("INVERSE("));
6227
0
        codeSpace.resize(codeSpace.size() - 1);
6228
0
    }
6229
6230
0
    std::string version;
6231
0
    if (j.contains("version")) {
6232
0
        auto versionJ = j["version"];
6233
0
        if (versionJ.is_string()) {
6234
0
            version = versionJ.get<std::string>();
6235
0
        } else if (versionJ.is_number()) {
6236
0
            const double dblVersion = versionJ.get<double>();
6237
0
            if (dblVersion >= std::numeric_limits<int>::min() &&
6238
0
                dblVersion <= std::numeric_limits<int>::max() &&
6239
0
                static_cast<int>(dblVersion) == dblVersion) {
6240
0
                version = internal::toString(static_cast<int>(dblVersion));
6241
0
            } else {
6242
0
                version = internal::toString(dblVersion, /*precision=*/15);
6243
0
            }
6244
0
        } else {
6245
0
            throw ParsingException("Unexpected type for value of \"version\"");
6246
0
        }
6247
0
    }
6248
6249
    // IAU + 2015 -> IAU_2015
6250
0
    if (dbContext_ && !version.empty()) {
6251
0
        std::string codeSpaceOut;
6252
0
        if (dbContext_->getVersionedAuthority(codeSpace, version,
6253
0
                                              codeSpaceOut)) {
6254
0
            codeSpace = std::move(codeSpaceOut);
6255
0
            version.clear();
6256
0
        }
6257
0
    }
6258
6259
0
    propertiesId.set(metadata::Identifier::CODESPACE_KEY, codeSpace);
6260
0
    propertiesId.set(metadata::Identifier::AUTHORITY_KEY, codeSpace);
6261
0
    if (!j.contains("code")) {
6262
0
        throw ParsingException("Missing \"code\" key");
6263
0
    }
6264
0
    std::string code;
6265
0
    auto codeJ = j["code"];
6266
0
    if (codeJ.is_string()) {
6267
0
        code = codeJ.get<std::string>();
6268
0
    } else if (codeJ.is_number_integer()) {
6269
0
        code = internal::toString(codeJ.get<int>());
6270
0
    } else {
6271
0
        throw ParsingException("Unexpected type for value of \"code\"");
6272
0
    }
6273
6274
    // Prior to PROJ 9.5, when synthetizing an ID for a CONVERSION UTM Zone
6275
    // south, we generated a wrong value. Auto-fix that
6276
0
    if (parentJ.contains("type") && getType(parentJ) == "Conversion" &&
6277
0
        codeSpace == Identifier::EPSG && parentJ.contains("name")) {
6278
0
        const auto parentNodeName(getName(parentJ));
6279
0
        if (ci_starts_with(parentNodeName, "UTM Zone ") &&
6280
0
            parentNodeName.find('S') != std::string::npos) {
6281
0
            const int nZone =
6282
0
                atoi(parentNodeName.c_str() + strlen("UTM Zone "));
6283
0
            if (nZone >= 1 && nZone <= 60) {
6284
0
                code = internal::toString(16100 + nZone);
6285
0
            }
6286
0
        }
6287
0
    }
6288
6289
0
    if (!version.empty()) {
6290
0
        propertiesId.set(Identifier::VERSION_KEY, version);
6291
0
    }
6292
6293
0
    if (j.contains("authority_citation")) {
6294
0
        propertiesId.set(Identifier::AUTHORITY_KEY,
6295
0
                         getString(j, "authority_citation"));
6296
0
    }
6297
6298
0
    if (j.contains("uri")) {
6299
0
        propertiesId.set(Identifier::URI_KEY, getString(j, "uri"));
6300
0
    }
6301
6302
0
    return Identifier::create(code, propertiesId);
6303
0
}
6304
6305
// ---------------------------------------------------------------------------
6306
6307
PropertyMap JSONParser::buildProperties(const json &j, bool removeInverseOf,
6308
0
                                        bool nameRequired) {
6309
0
    PropertyMap map;
6310
6311
0
    if (j.contains("name") || nameRequired) {
6312
0
        std::string name(getName(j));
6313
0
        if (removeInverseOf && starts_with(name, "Inverse of ")) {
6314
0
            name = name.substr(strlen("Inverse of "));
6315
0
        }
6316
0
        map.set(IdentifiedObject::NAME_KEY, name);
6317
0
    }
6318
6319
0
    if (j.contains("ids")) {
6320
0
        auto idsJ = getArray(j, "ids");
6321
0
        auto identifiers = ArrayOfBaseObject::create();
6322
0
        for (const auto &idJ : idsJ) {
6323
0
            if (!idJ.is_object()) {
6324
0
                throw ParsingException(
6325
0
                    "Unexpected type for value of \"ids\" child");
6326
0
            }
6327
0
            identifiers->add(buildId(j, idJ, removeInverseOf));
6328
0
        }
6329
0
        map.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
6330
0
    } else if (j.contains("id")) {
6331
0
        auto idJ = getObject(j, "id");
6332
0
        auto identifiers = ArrayOfBaseObject::create();
6333
0
        identifiers->add(buildId(j, idJ, removeInverseOf));
6334
0
        map.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
6335
0
    }
6336
6337
0
    if (j.contains("remarks")) {
6338
0
        map.set(IdentifiedObject::REMARKS_KEY, getString(j, "remarks"));
6339
0
    }
6340
6341
0
    if (j.contains("usages")) {
6342
0
        ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create();
6343
0
        auto usages = j["usages"];
6344
0
        if (!usages.is_array()) {
6345
0
            throw ParsingException("Unexpected type for value of \"usages\"");
6346
0
        }
6347
0
        for (const auto &usage : usages) {
6348
0
            if (!usage.is_object()) {
6349
0
                throw ParsingException(
6350
0
                    "Unexpected type for value of \"usages\" child");
6351
0
            }
6352
0
            auto objectDomain = buildObjectDomain(usage);
6353
0
            if (!objectDomain) {
6354
0
                throw ParsingException("missing children in \"usages\" child");
6355
0
            }
6356
0
            array->add(NN_NO_CHECK(objectDomain));
6357
0
        }
6358
0
        if (!array->empty()) {
6359
0
            map.set(ObjectUsage::OBJECT_DOMAIN_KEY, array);
6360
0
        }
6361
0
    } else {
6362
0
        auto objectDomain = buildObjectDomain(j);
6363
0
        if (objectDomain) {
6364
0
            map.set(ObjectUsage::OBJECT_DOMAIN_KEY, NN_NO_CHECK(objectDomain));
6365
0
        }
6366
0
    }
6367
6368
0
    return map;
6369
0
}
6370
6371
// ---------------------------------------------------------------------------
6372
6373
BaseObjectNNPtr JSONParser::create(const json &j)
6374
6375
43
{
6376
43
    if (!j.is_object()) {
6377
0
        throw ParsingException("JSON object expected");
6378
0
    }
6379
43
    auto type = getString(j, "type");
6380
43
    if (type == "GeographicCRS") {
6381
0
        return buildGeographicCRS(j);
6382
0
    }
6383
43
    if (type == "GeodeticCRS") {
6384
0
        return buildGeodeticCRS(j);
6385
0
    }
6386
43
    if (type == "ProjectedCRS") {
6387
0
        return buildProjectedCRS(j);
6388
0
    }
6389
43
    if (type == "VerticalCRS") {
6390
0
        return buildVerticalCRS(j);
6391
0
    }
6392
43
    if (type == "CompoundCRS") {
6393
0
        return buildCompoundCRS(j);
6394
0
    }
6395
43
    if (type == "BoundCRS") {
6396
0
        return buildBoundCRS(j);
6397
0
    }
6398
43
    if (type == "EngineeringCRS") {
6399
0
        return buildCRS<EngineeringCRS>(j, &JSONParser::buildEngineeringDatum);
6400
0
    }
6401
43
    if (type == "ParametricCRS") {
6402
0
        return buildCRS<ParametricCRS,
6403
0
                        decltype(&JSONParser::buildParametricDatum),
6404
0
                        ParametricCS>(j, &JSONParser::buildParametricDatum);
6405
0
    }
6406
43
    if (type == "TemporalCRS") {
6407
0
        return buildCRS<TemporalCRS, decltype(&JSONParser::buildTemporalDatum),
6408
0
                        TemporalCS>(j, &JSONParser::buildTemporalDatum);
6409
0
    }
6410
43
    if (type == "DerivedGeodeticCRS") {
6411
0
        auto baseCRSObj = create(getObject(j, "base_crs"));
6412
0
        auto baseCRS = util::nn_dynamic_pointer_cast<GeodeticCRS>(baseCRSObj);
6413
0
        if (!baseCRS) {
6414
0
            throw ParsingException("base_crs not of expected type");
6415
0
        }
6416
0
        auto cs = buildCS(getObject(j, "coordinate_system"));
6417
0
        auto conv = buildConversion(getObject(j, "conversion"));
6418
0
        auto csCartesian = util::nn_dynamic_pointer_cast<CartesianCS>(cs);
6419
0
        if (csCartesian)
6420
0
            return DerivedGeodeticCRS::create(buildProperties(j),
6421
0
                                              NN_NO_CHECK(baseCRS), conv,
6422
0
                                              NN_NO_CHECK(csCartesian));
6423
0
        auto csSpherical = util::nn_dynamic_pointer_cast<SphericalCS>(cs);
6424
0
        if (csSpherical)
6425
0
            return DerivedGeodeticCRS::create(buildProperties(j),
6426
0
                                              NN_NO_CHECK(baseCRS), conv,
6427
0
                                              NN_NO_CHECK(csSpherical));
6428
0
        throw ParsingException("coordinate_system not of expected type");
6429
0
    }
6430
43
    if (type == "DerivedGeographicCRS") {
6431
0
        return buildDerivedCRS<DerivedGeographicCRS, GeodeticCRS,
6432
0
                               EllipsoidalCS>(j);
6433
0
    }
6434
43
    if (type == "DerivedProjectedCRS") {
6435
0
        return buildDerivedCRS<DerivedProjectedCRS, ProjectedCRS>(j);
6436
0
    }
6437
43
    if (type == "DerivedVerticalCRS") {
6438
0
        return buildDerivedCRS<DerivedVerticalCRS, VerticalCRS, VerticalCS>(j);
6439
0
    }
6440
43
    if (type == "DerivedEngineeringCRS") {
6441
0
        return buildDerivedCRS<DerivedEngineeringCRS, EngineeringCRS>(j);
6442
0
    }
6443
43
    if (type == "DerivedParametricCRS") {
6444
0
        return buildDerivedCRS<DerivedParametricCRS, ParametricCRS,
6445
0
                               ParametricCS>(j);
6446
0
    }
6447
43
    if (type == "DerivedTemporalCRS") {
6448
0
        return buildDerivedCRS<DerivedTemporalCRS, TemporalCRS, TemporalCS>(j);
6449
0
    }
6450
43
    if (type == "DatumEnsemble") {
6451
0
        return buildDatumEnsemble(j);
6452
0
    }
6453
43
    if (type == "GeodeticReferenceFrame") {
6454
0
        return buildGeodeticReferenceFrame(j);
6455
0
    }
6456
43
    if (type == "VerticalReferenceFrame") {
6457
0
        return buildVerticalReferenceFrame(j);
6458
0
    }
6459
43
    if (type == "DynamicGeodeticReferenceFrame") {
6460
0
        return buildDynamicGeodeticReferenceFrame(j);
6461
0
    }
6462
43
    if (type == "DynamicVerticalReferenceFrame") {
6463
0
        return buildDynamicVerticalReferenceFrame(j);
6464
0
    }
6465
43
    if (type == "EngineeringDatum") {
6466
0
        return buildEngineeringDatum(j);
6467
0
    }
6468
43
    if (type == "ParametricDatum") {
6469
0
        return buildParametricDatum(j);
6470
0
    }
6471
43
    if (type == "TemporalDatum") {
6472
0
        return buildTemporalDatum(j);
6473
0
    }
6474
43
    if (type == "Ellipsoid") {
6475
0
        return buildEllipsoid(j);
6476
0
    }
6477
43
    if (type == "PrimeMeridian") {
6478
0
        return buildPrimeMeridian(j);
6479
0
    }
6480
43
    if (type == "CoordinateSystem") {
6481
0
        return buildCS(j);
6482
0
    }
6483
43
    if (type == "Conversion") {
6484
0
        return buildConversion(j);
6485
0
    }
6486
43
    if (type == "Transformation") {
6487
0
        return buildTransformation(j);
6488
0
    }
6489
43
    if (type == "PointMotionOperation") {
6490
0
        return buildPointMotionOperation(j);
6491
0
    }
6492
43
    if (type == "ConcatenatedOperation") {
6493
0
        return buildConcatenatedOperation(j);
6494
0
    }
6495
43
    if (type == "CoordinateMetadata") {
6496
0
        return buildCoordinateMetadata(j);
6497
0
    }
6498
43
    if (type == "Axis") {
6499
0
        return buildAxis(j);
6500
0
    }
6501
43
    throw ParsingException("Unsupported value of \"type\"");
6502
43
}
6503
6504
// ---------------------------------------------------------------------------
6505
6506
void JSONParser::buildGeodeticDatumOrDatumEnsemble(
6507
    const json &j, GeodeticReferenceFramePtr &datum,
6508
0
    DatumEnsemblePtr &datumEnsemble) {
6509
0
    if (j.contains("datum")) {
6510
0
        auto datumJ = getObject(j, "datum");
6511
6512
0
        if (j.contains("deformation_models")) {
6513
0
            auto deformationModelsJ = getArray(j, "deformation_models");
6514
0
            if (!deformationModelsJ.empty()) {
6515
0
                const auto &deformationModelJ = deformationModelsJ[0];
6516
0
                deformationModelName_ = getString(deformationModelJ, "name");
6517
                // We can handle only one for now
6518
0
            }
6519
0
        }
6520
6521
0
        datum = util::nn_dynamic_pointer_cast<GeodeticReferenceFrame>(
6522
0
            create(datumJ));
6523
0
        if (!datum) {
6524
0
            throw ParsingException("datum of wrong type");
6525
0
        }
6526
6527
0
        deformationModelName_.clear();
6528
0
    } else {
6529
0
        datumEnsemble =
6530
0
            buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable();
6531
0
    }
6532
0
}
6533
6534
// ---------------------------------------------------------------------------
6535
6536
0
GeographicCRSNNPtr JSONParser::buildGeographicCRS(const json &j) {
6537
0
    GeodeticReferenceFramePtr datum;
6538
0
    DatumEnsemblePtr datumEnsemble;
6539
0
    buildGeodeticDatumOrDatumEnsemble(j, datum, datumEnsemble);
6540
0
    auto csJ = getObject(j, "coordinate_system");
6541
0
    auto ellipsoidalCS =
6542
0
        util::nn_dynamic_pointer_cast<EllipsoidalCS>(buildCS(csJ));
6543
0
    if (!ellipsoidalCS) {
6544
0
        throw ParsingException("expected an ellipsoidal CS");
6545
0
    }
6546
0
    return GeographicCRS::create(buildProperties(j), datum, datumEnsemble,
6547
0
                                 NN_NO_CHECK(ellipsoidalCS));
6548
0
}
6549
6550
// ---------------------------------------------------------------------------
6551
6552
0
GeodeticCRSNNPtr JSONParser::buildGeodeticCRS(const json &j) {
6553
0
    GeodeticReferenceFramePtr datum;
6554
0
    DatumEnsemblePtr datumEnsemble;
6555
0
    buildGeodeticDatumOrDatumEnsemble(j, datum, datumEnsemble);
6556
0
    auto csJ = getObject(j, "coordinate_system");
6557
0
    auto cs = buildCS(csJ);
6558
0
    auto props = buildProperties(j);
6559
0
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
6560
0
    if (cartesianCS) {
6561
0
        if (cartesianCS->axisList().size() != 3) {
6562
0
            throw ParsingException(
6563
0
                "Cartesian CS for a GeodeticCRS should have 3 axis");
6564
0
        }
6565
0
        try {
6566
0
            return GeodeticCRS::create(props, datum, datumEnsemble,
6567
0
                                       NN_NO_CHECK(cartesianCS));
6568
0
        } catch (const util::Exception &e) {
6569
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
6570
0
                                   e.what());
6571
0
        }
6572
0
    }
6573
6574
0
    auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
6575
0
    if (sphericalCS) {
6576
0
        try {
6577
0
            return GeodeticCRS::create(props, datum, datumEnsemble,
6578
0
                                       NN_NO_CHECK(sphericalCS));
6579
0
        } catch (const util::Exception &e) {
6580
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
6581
0
                                   e.what());
6582
0
        }
6583
0
    }
6584
0
    throw ParsingException("expected a Cartesian or spherical CS");
6585
0
}
6586
6587
// ---------------------------------------------------------------------------
6588
6589
0
ProjectedCRSNNPtr JSONParser::buildProjectedCRS(const json &j) {
6590
0
    auto jBaseCRS = getObject(j, "base_crs");
6591
0
    auto jBaseCS = getObject(jBaseCRS, "coordinate_system");
6592
0
    auto baseCS = buildCS(jBaseCS);
6593
0
    auto baseCRS = dynamic_cast<EllipsoidalCS *>(baseCS.get()) != nullptr
6594
0
                       ? util::nn_static_pointer_cast<GeodeticCRS>(
6595
0
                             buildGeographicCRS(jBaseCRS))
6596
0
                       : buildGeodeticCRS(jBaseCRS);
6597
0
    auto csJ = getObject(j, "coordinate_system");
6598
0
    auto cartesianCS = util::nn_dynamic_pointer_cast<CartesianCS>(buildCS(csJ));
6599
0
    if (!cartesianCS) {
6600
0
        throw ParsingException("expected a Cartesian CS");
6601
0
    }
6602
0
    auto conv = buildConversion(getObject(j, "conversion"));
6603
0
    return ProjectedCRS::create(buildProperties(j), baseCRS, conv,
6604
0
                                NN_NO_CHECK(cartesianCS));
6605
0
}
6606
6607
// ---------------------------------------------------------------------------
6608
6609
0
VerticalCRSNNPtr JSONParser::buildVerticalCRS(const json &j) {
6610
0
    VerticalReferenceFramePtr datum;
6611
0
    DatumEnsemblePtr datumEnsemble;
6612
0
    if (j.contains("datum")) {
6613
0
        auto datumJ = getObject(j, "datum");
6614
6615
0
        if (j.contains("deformation_models")) {
6616
0
            auto deformationModelsJ = getArray(j, "deformation_models");
6617
0
            if (!deformationModelsJ.empty()) {
6618
0
                const auto &deformationModelJ = deformationModelsJ[0];
6619
0
                deformationModelName_ = getString(deformationModelJ, "name");
6620
                // We can handle only one for now
6621
0
            }
6622
0
        }
6623
6624
0
        datum = util::nn_dynamic_pointer_cast<VerticalReferenceFrame>(
6625
0
            create(datumJ));
6626
0
        if (!datum) {
6627
0
            throw ParsingException("datum of wrong type");
6628
0
        }
6629
0
    } else {
6630
0
        datumEnsemble =
6631
0
            buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable();
6632
0
    }
6633
0
    auto csJ = getObject(j, "coordinate_system");
6634
0
    auto verticalCS = util::nn_dynamic_pointer_cast<VerticalCS>(buildCS(csJ));
6635
0
    if (!verticalCS) {
6636
0
        throw ParsingException("expected a vertical CS");
6637
0
    }
6638
6639
0
    const auto buildGeoidModel = [this, &datum, &datumEnsemble,
6640
0
                                  &verticalCS](const json &geoidModelJ) {
6641
0
        auto propsModel = buildProperties(geoidModelJ);
6642
0
        const auto dummyCRS = VerticalCRS::create(
6643
0
            PropertyMap(), datum, datumEnsemble, NN_NO_CHECK(verticalCS));
6644
0
        CRSPtr interpolationCRS;
6645
0
        if (geoidModelJ.contains("interpolation_crs")) {
6646
0
            auto interpolationCRSJ =
6647
0
                getObject(geoidModelJ, "interpolation_crs");
6648
0
            interpolationCRS = buildCRS(interpolationCRSJ).as_nullable();
6649
0
        }
6650
0
        return Transformation::create(
6651
0
            propsModel, dummyCRS,
6652
0
            GeographicCRS::EPSG_4979, // arbitrarily chosen. Ignored,
6653
0
            interpolationCRS,
6654
0
            OperationMethod::create(PropertyMap(),
6655
0
                                    std::vector<OperationParameterNNPtr>()),
6656
0
            {}, {});
6657
0
    };
6658
6659
0
    auto props = buildProperties(j);
6660
0
    if (j.contains("geoid_model")) {
6661
0
        auto geoidModelJ = getObject(j, "geoid_model");
6662
0
        props.set("GEOID_MODEL", buildGeoidModel(geoidModelJ));
6663
0
    } else if (j.contains("geoid_models")) {
6664
0
        auto geoidModelsJ = getArray(j, "geoid_models");
6665
0
        auto geoidModels = ArrayOfBaseObject::create();
6666
0
        for (const auto &geoidModelJ : geoidModelsJ) {
6667
0
            geoidModels->add(buildGeoidModel(geoidModelJ));
6668
0
        }
6669
0
        props.set("GEOID_MODEL", geoidModels);
6670
0
    }
6671
6672
0
    return VerticalCRS::create(props, datum, datumEnsemble,
6673
0
                               NN_NO_CHECK(verticalCS));
6674
0
}
6675
6676
// ---------------------------------------------------------------------------
6677
6678
0
CRSNNPtr JSONParser::buildCRS(const json &j) {
6679
0
    auto crs = util::nn_dynamic_pointer_cast<CRS>(create(j));
6680
0
    if (crs) {
6681
0
        return NN_NO_CHECK(crs);
6682
0
    }
6683
0
    throw ParsingException("Object is not a CRS");
6684
0
}
6685
6686
// ---------------------------------------------------------------------------
6687
6688
0
CompoundCRSNNPtr JSONParser::buildCompoundCRS(const json &j) {
6689
0
    auto componentsJ = getArray(j, "components");
6690
0
    std::vector<CRSNNPtr> components;
6691
0
    for (const auto &componentJ : componentsJ) {
6692
0
        if (!componentJ.is_object()) {
6693
0
            throw ParsingException(
6694
0
                "Unexpected type for a \"components\" child");
6695
0
        }
6696
0
        components.push_back(buildCRS(componentJ));
6697
0
    }
6698
0
    return CompoundCRS::create(buildProperties(j), components);
6699
0
}
6700
6701
// ---------------------------------------------------------------------------
6702
6703
0
ConversionNNPtr JSONParser::buildConversion(const json &j) {
6704
0
    auto methodJ = getObject(j, "method");
6705
0
    auto convProps = buildProperties(j);
6706
0
    auto methodProps = buildProperties(methodJ);
6707
0
    if (!j.contains("parameters")) {
6708
0
        return Conversion::create(convProps, methodProps, {}, {});
6709
0
    }
6710
6711
0
    auto parametersJ = getArray(j, "parameters");
6712
0
    std::vector<OperationParameterNNPtr> parameters;
6713
0
    std::vector<ParameterValueNNPtr> values;
6714
0
    for (const auto &param : parametersJ) {
6715
0
        if (!param.is_object()) {
6716
0
            throw ParsingException(
6717
0
                "Unexpected type for a \"parameters\" child");
6718
0
        }
6719
0
        parameters.emplace_back(
6720
0
            OperationParameter::create(buildProperties(param)));
6721
0
        if (isIntegerParameter(parameters.back())) {
6722
0
            values.emplace_back(
6723
0
                ParameterValue::create(getInteger(param, "value")));
6724
0
        } else {
6725
0
            values.emplace_back(ParameterValue::create(getMeasure(param)));
6726
0
        }
6727
0
    }
6728
6729
0
    auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
6730
0
        dbContext_, parameters, values);
6731
6732
0
    std::string convName;
6733
0
    std::string methodName;
6734
0
    if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) &&
6735
0
        methodProps.getStringValue(IdentifiedObject::NAME_KEY, methodName) &&
6736
0
        starts_with(convName, "Inverse of ") &&
6737
0
        starts_with(methodName, "Inverse of ")) {
6738
6739
0
        auto invConvProps = buildProperties(j, true);
6740
0
        auto invMethodProps = buildProperties(methodJ, true);
6741
0
        auto conv = NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(
6742
0
            Conversion::create(invConvProps, invMethodProps, parameters, values)
6743
0
                ->inverse()));
6744
0
        if (interpolationCRS)
6745
0
            conv->setInterpolationCRS(interpolationCRS);
6746
0
        return conv;
6747
0
    }
6748
0
    auto conv = Conversion::create(convProps, methodProps, parameters, values);
6749
0
    if (interpolationCRS)
6750
0
        conv->setInterpolationCRS(interpolationCRS);
6751
0
    return conv;
6752
0
}
6753
6754
// ---------------------------------------------------------------------------
6755
6756
0
BoundCRSNNPtr JSONParser::buildBoundCRS(const json &j) {
6757
6758
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6759
0
    auto targetCRS = buildCRS(getObject(j, "target_crs"));
6760
0
    auto transformationJ = getObject(j, "transformation");
6761
0
    auto methodJ = getObject(transformationJ, "method");
6762
0
    auto parametersJ = getArray(transformationJ, "parameters");
6763
0
    std::vector<OperationParameterNNPtr> parameters;
6764
0
    std::vector<ParameterValueNNPtr> values;
6765
0
    for (const auto &param : parametersJ) {
6766
0
        if (!param.is_object()) {
6767
0
            throw ParsingException(
6768
0
                "Unexpected type for a \"parameters\" child");
6769
0
        }
6770
0
        parameters.emplace_back(
6771
0
            OperationParameter::create(buildProperties(param)));
6772
0
        if (param.contains("value")) {
6773
0
            auto v = param["value"];
6774
0
            if (v.is_string()) {
6775
0
                values.emplace_back(
6776
0
                    ParameterValue::createFilename(v.get<std::string>()));
6777
0
                continue;
6778
0
            }
6779
0
        }
6780
0
        values.emplace_back(ParameterValue::create(getMeasure(param)));
6781
0
    }
6782
6783
0
    const auto transformation = [&]() {
6784
        // Unofficial extension / mostly for testing purposes.
6785
        // Allow to explicitly specify the source_crs of the transformation of
6786
        // the boundCRS if it is not the source_crs of the BoundCRS. Cf
6787
        // https://github.com/OSGeo/PROJ/issues/3428 use case
6788
0
        if (transformationJ.contains("source_crs")) {
6789
0
            auto sourceTransformationCRS =
6790
0
                buildCRS(getObject(transformationJ, "source_crs"));
6791
0
            auto interpolationCRS =
6792
0
                dealWithEPSGCodeForInterpolationCRSParameter(
6793
0
                    dbContext_, parameters, values);
6794
0
            return Transformation::create(
6795
0
                buildProperties(transformationJ), sourceTransformationCRS,
6796
0
                targetCRS, interpolationCRS, buildProperties(methodJ),
6797
0
                parameters, values, std::vector<PositionalAccuracyNNPtr>());
6798
0
        }
6799
6800
0
        return buildTransformationForBoundCRS(
6801
0
            dbContext_, buildProperties(transformationJ),
6802
0
            buildProperties(methodJ), sourceCRS, targetCRS, parameters, values);
6803
0
    }();
6804
6805
0
    return BoundCRS::create(buildProperties(j,
6806
0
                                            /* removeInverseOf= */ false,
6807
0
                                            /* nameRequired=*/false),
6808
0
                            sourceCRS, targetCRS, transformation);
6809
0
}
6810
6811
// ---------------------------------------------------------------------------
6812
6813
0
TransformationNNPtr JSONParser::buildTransformation(const json &j) {
6814
6815
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6816
0
    auto targetCRS = buildCRS(getObject(j, "target_crs"));
6817
0
    auto methodJ = getObject(j, "method");
6818
0
    auto parametersJ = getArray(j, "parameters");
6819
0
    std::vector<OperationParameterNNPtr> parameters;
6820
0
    std::vector<ParameterValueNNPtr> values;
6821
0
    for (const auto &param : parametersJ) {
6822
0
        if (!param.is_object()) {
6823
0
            throw ParsingException(
6824
0
                "Unexpected type for a \"parameters\" child");
6825
0
        }
6826
0
        parameters.emplace_back(
6827
0
            OperationParameter::create(buildProperties(param)));
6828
0
        if (param.contains("value")) {
6829
0
            auto v = param["value"];
6830
0
            if (v.is_string()) {
6831
0
                values.emplace_back(
6832
0
                    ParameterValue::createFilename(v.get<std::string>()));
6833
0
                continue;
6834
0
            }
6835
0
        }
6836
0
        values.emplace_back(ParameterValue::create(getMeasure(param)));
6837
0
    }
6838
0
    CRSPtr interpolationCRS;
6839
0
    if (j.contains("interpolation_crs")) {
6840
0
        interpolationCRS =
6841
0
            buildCRS(getObject(j, "interpolation_crs")).as_nullable();
6842
0
    }
6843
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
6844
0
    if (j.contains("accuracy")) {
6845
0
        accuracies.push_back(
6846
0
            PositionalAccuracy::create(getString(j, "accuracy")));
6847
0
    }
6848
6849
0
    return Transformation::create(buildProperties(j), sourceCRS, targetCRS,
6850
0
                                  interpolationCRS, buildProperties(methodJ),
6851
0
                                  parameters, values, accuracies);
6852
0
}
6853
6854
// ---------------------------------------------------------------------------
6855
6856
0
PointMotionOperationNNPtr JSONParser::buildPointMotionOperation(const json &j) {
6857
6858
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6859
0
    auto methodJ = getObject(j, "method");
6860
0
    auto parametersJ = getArray(j, "parameters");
6861
0
    std::vector<OperationParameterNNPtr> parameters;
6862
0
    std::vector<ParameterValueNNPtr> values;
6863
0
    for (const auto &param : parametersJ) {
6864
0
        if (!param.is_object()) {
6865
0
            throw ParsingException(
6866
0
                "Unexpected type for a \"parameters\" child");
6867
0
        }
6868
0
        parameters.emplace_back(
6869
0
            OperationParameter::create(buildProperties(param)));
6870
0
        if (param.contains("value")) {
6871
0
            auto v = param["value"];
6872
0
            if (v.is_string()) {
6873
0
                values.emplace_back(
6874
0
                    ParameterValue::createFilename(v.get<std::string>()));
6875
0
                continue;
6876
0
            }
6877
0
        }
6878
0
        values.emplace_back(ParameterValue::create(getMeasure(param)));
6879
0
    }
6880
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
6881
0
    if (j.contains("accuracy")) {
6882
0
        accuracies.push_back(
6883
0
            PositionalAccuracy::create(getString(j, "accuracy")));
6884
0
    }
6885
6886
0
    return PointMotionOperation::create(buildProperties(j), sourceCRS,
6887
0
                                        buildProperties(methodJ), parameters,
6888
0
                                        values, accuracies);
6889
0
}
6890
6891
// ---------------------------------------------------------------------------
6892
6893
ConcatenatedOperationNNPtr
6894
0
JSONParser::buildConcatenatedOperation(const json &j) {
6895
6896
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6897
0
    auto targetCRS = buildCRS(getObject(j, "target_crs"));
6898
0
    auto stepsJ = getArray(j, "steps");
6899
0
    std::vector<CoordinateOperationNNPtr> operations;
6900
0
    for (const auto &stepJ : stepsJ) {
6901
0
        if (!stepJ.is_object()) {
6902
0
            throw ParsingException("Unexpected type for a \"steps\" child");
6903
0
        }
6904
0
        auto op = nn_dynamic_pointer_cast<CoordinateOperation>(create(stepJ));
6905
0
        if (!op) {
6906
0
            throw ParsingException("Invalid content in a \"steps\" child");
6907
0
        }
6908
0
        operations.emplace_back(NN_NO_CHECK(op));
6909
0
    }
6910
6911
0
    ConcatenatedOperation::fixSteps(sourceCRS, targetCRS, operations,
6912
0
                                    dbContext_,
6913
0
                                    /* fixDirectionAllowed = */ true);
6914
6915
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
6916
0
    if (j.contains("accuracy")) {
6917
0
        accuracies.push_back(
6918
0
            PositionalAccuracy::create(getString(j, "accuracy")));
6919
0
    }
6920
6921
0
    try {
6922
0
        return ConcatenatedOperation::create(buildProperties(j), operations,
6923
0
                                             accuracies);
6924
0
    } catch (const InvalidOperation &e) {
6925
0
        throw ParsingException(
6926
0
            std::string("Cannot build concatenated operation: ") + e.what());
6927
0
    }
6928
0
}
6929
6930
// ---------------------------------------------------------------------------
6931
6932
0
CoordinateMetadataNNPtr JSONParser::buildCoordinateMetadata(const json &j) {
6933
6934
0
    auto crs = buildCRS(getObject(j, "crs"));
6935
0
    if (j.contains("coordinateEpoch")) {
6936
0
        auto jCoordinateEpoch = j["coordinateEpoch"];
6937
0
        if (jCoordinateEpoch.is_number()) {
6938
0
            return CoordinateMetadata::create(
6939
0
                crs, jCoordinateEpoch.get<double>(), dbContext_);
6940
0
        }
6941
0
        throw ParsingException(
6942
0
            "Unexpected type for value of \"coordinateEpoch\"");
6943
0
    }
6944
0
    return CoordinateMetadata::create(crs);
6945
0
}
6946
6947
// ---------------------------------------------------------------------------
6948
6949
0
MeridianNNPtr JSONParser::buildMeridian(const json &j) {
6950
0
    if (!j.contains("longitude")) {
6951
0
        throw ParsingException("Missing \"longitude\" key");
6952
0
    }
6953
0
    auto longitude = j["longitude"];
6954
0
    if (longitude.is_number()) {
6955
0
        return Meridian::create(
6956
0
            Angle(longitude.get<double>(), UnitOfMeasure::DEGREE));
6957
0
    } else if (longitude.is_object()) {
6958
0
        return Meridian::create(Angle(getMeasure(longitude)));
6959
0
    }
6960
0
    throw ParsingException("Unexpected type for value of \"longitude\"");
6961
0
}
6962
6963
// ---------------------------------------------------------------------------
6964
6965
0
CoordinateSystemAxisNNPtr JSONParser::buildAxis(const json &j) {
6966
0
    auto dirString = getString(j, "direction");
6967
0
    auto abbreviation = getString(j, "abbreviation");
6968
0
    const UnitOfMeasure unit(
6969
0
        j.contains("unit")
6970
0
            ? getUnit(j, "unit")
6971
0
            : UnitOfMeasure(std::string(), 1.0, UnitOfMeasure::Type::NONE));
6972
0
    auto direction = AxisDirection::valueOf(dirString);
6973
0
    if (!direction) {
6974
0
        throw ParsingException(concat("unhandled axis direction: ", dirString));
6975
0
    }
6976
0
    auto meridian = j.contains("meridian")
6977
0
                        ? buildMeridian(getObject(j, "meridian")).as_nullable()
6978
0
                        : nullptr;
6979
6980
0
    util::optional<double> minVal;
6981
0
    if (j.contains("minimum_value")) {
6982
0
        minVal = getNumber(j, "minimum_value");
6983
0
    }
6984
6985
0
    util::optional<double> maxVal;
6986
0
    if (j.contains("maximum_value")) {
6987
0
        maxVal = getNumber(j, "maximum_value");
6988
0
    }
6989
6990
0
    util::optional<RangeMeaning> rangeMeaning;
6991
0
    if (j.contains("range_meaning")) {
6992
0
        const auto val = getString(j, "range_meaning");
6993
0
        const RangeMeaning *meaning = RangeMeaning::valueOf(val);
6994
0
        if (meaning == nullptr) {
6995
0
            throw ParsingException(
6996
0
                concat("buildAxis: invalid range_meaning value: ", val));
6997
0
        }
6998
0
        rangeMeaning = util::optional<RangeMeaning>(*meaning);
6999
0
    }
7000
7001
0
    return CoordinateSystemAxis::create(buildProperties(j), abbreviation,
7002
0
                                        *direction, unit, minVal, maxVal,
7003
0
                                        rangeMeaning, meridian);
7004
0
}
7005
7006
// ---------------------------------------------------------------------------
7007
7008
0
CoordinateSystemNNPtr JSONParser::buildCS(const json &j) {
7009
0
    auto subtype = getString(j, "subtype");
7010
0
    if (!j.contains("axis")) {
7011
0
        throw ParsingException("Missing \"axis\" key");
7012
0
    }
7013
0
    auto jAxisList = j["axis"];
7014
0
    if (!jAxisList.is_array()) {
7015
0
        throw ParsingException("Unexpected type for value of \"axis\"");
7016
0
    }
7017
0
    std::vector<CoordinateSystemAxisNNPtr> axisList;
7018
0
    for (const auto &axis : jAxisList) {
7019
0
        if (!axis.is_object()) {
7020
0
            throw ParsingException(
7021
0
                "Unexpected type for value of a \"axis\" member");
7022
0
        }
7023
0
        axisList.emplace_back(buildAxis(axis));
7024
0
    }
7025
0
    const PropertyMap &csMap = emptyPropertyMap;
7026
0
    const auto axisCount = axisList.size();
7027
0
    if (subtype == EllipsoidalCS::WKT2_TYPE) {
7028
0
        if (axisCount == 2) {
7029
0
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1]);
7030
0
        }
7031
0
        if (axisCount == 3) {
7032
0
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1],
7033
0
                                         axisList[2]);
7034
0
        }
7035
0
        throw ParsingException("Expected 2 or 3 axis");
7036
0
    }
7037
0
    if (subtype == CartesianCS::WKT2_TYPE) {
7038
0
        if (axisCount == 2) {
7039
0
            return CartesianCS::create(csMap, axisList[0], axisList[1]);
7040
0
        }
7041
0
        if (axisCount == 3) {
7042
0
            return CartesianCS::create(csMap, axisList[0], axisList[1],
7043
0
                                       axisList[2]);
7044
0
        }
7045
0
        throw ParsingException("Expected 2 or 3 axis");
7046
0
    }
7047
0
    if (subtype == AffineCS::WKT2_TYPE) {
7048
0
        if (axisCount == 2) {
7049
0
            return AffineCS::create(csMap, axisList[0], axisList[1]);
7050
0
        }
7051
0
        if (axisCount == 3) {
7052
0
            return AffineCS::create(csMap, axisList[0], axisList[1],
7053
0
                                    axisList[2]);
7054
0
        }
7055
0
        throw ParsingException("Expected 2 or 3 axis");
7056
0
    }
7057
0
    if (subtype == VerticalCS::WKT2_TYPE) {
7058
0
        if (axisCount == 1) {
7059
0
            return VerticalCS::create(csMap, axisList[0]);
7060
0
        }
7061
0
        throw ParsingException("Expected 1 axis");
7062
0
    }
7063
0
    if (subtype == SphericalCS::WKT2_TYPE) {
7064
0
        if (axisCount == 2) {
7065
            // Extension to ISO19111 to support (planet)-ocentric CS with
7066
            // geocentric latitude
7067
0
            return SphericalCS::create(csMap, axisList[0], axisList[1]);
7068
0
        } else if (axisCount == 3) {
7069
0
            return SphericalCS::create(csMap, axisList[0], axisList[1],
7070
0
                                       axisList[2]);
7071
0
        }
7072
0
        throw ParsingException("Expected 2 or 3 axis");
7073
0
    }
7074
0
    if (subtype == OrdinalCS::WKT2_TYPE) {
7075
0
        return OrdinalCS::create(csMap, axisList);
7076
0
    }
7077
0
    if (subtype == ParametricCS::WKT2_TYPE) {
7078
0
        if (axisCount == 1) {
7079
0
            return ParametricCS::create(csMap, axisList[0]);
7080
0
        }
7081
0
        throw ParsingException("Expected 1 axis");
7082
0
    }
7083
0
    if (subtype == DateTimeTemporalCS::WKT2_2019_TYPE) {
7084
0
        if (axisCount == 1) {
7085
0
            return DateTimeTemporalCS::create(csMap, axisList[0]);
7086
0
        }
7087
0
        throw ParsingException("Expected 1 axis");
7088
0
    }
7089
0
    if (subtype == TemporalCountCS::WKT2_2019_TYPE) {
7090
0
        if (axisCount == 1) {
7091
0
            return TemporalCountCS::create(csMap, axisList[0]);
7092
0
        }
7093
0
        throw ParsingException("Expected 1 axis");
7094
0
    }
7095
0
    if (subtype == TemporalMeasureCS::WKT2_2019_TYPE) {
7096
0
        if (axisCount == 1) {
7097
0
            return TemporalMeasureCS::create(csMap, axisList[0]);
7098
0
        }
7099
0
        throw ParsingException("Expected 1 axis");
7100
0
    }
7101
0
    throw ParsingException("Unhandled value for subtype");
7102
0
}
7103
7104
// ---------------------------------------------------------------------------
7105
7106
0
DatumEnsembleNNPtr JSONParser::buildDatumEnsemble(const json &j) {
7107
0
    auto membersJ = getArray(j, "members");
7108
0
    std::vector<DatumNNPtr> datums;
7109
0
    const bool hasEllipsoid(j.contains("ellipsoid"));
7110
0
    for (const auto &memberJ : membersJ) {
7111
0
        if (!memberJ.is_object()) {
7112
0
            throw ParsingException(
7113
0
                "Unexpected type for value of a \"members\" member");
7114
0
        }
7115
0
        auto datumName(getName(memberJ));
7116
0
        bool datumAdded = false;
7117
0
        if (dbContext_ && memberJ.contains("id")) {
7118
0
            auto id = getObject(memberJ, "id");
7119
0
            auto authority = getString(id, "authority");
7120
0
            auto authFactory =
7121
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext_), authority);
7122
0
            auto code = id["code"];
7123
0
            std::string codeStr;
7124
0
            if (code.is_string()) {
7125
0
                codeStr = code.get<std::string>();
7126
0
            } else if (code.is_number_integer()) {
7127
0
                codeStr = internal::toString(code.get<int>());
7128
0
            } else {
7129
0
                throw ParsingException("Unexpected type for value of \"code\"");
7130
0
            }
7131
0
            try {
7132
0
                datums.push_back(authFactory->createDatum(codeStr));
7133
0
                datumAdded = true;
7134
0
            } catch (const std::exception &) {
7135
                // Silently ignore, as this isn't necessary an error.
7136
                // If an older PROJ version parses a DatumEnsemble object of
7137
                // a more recent PROJ version where the datum ensemble got
7138
                // a new member, it might be unknown from the older PROJ.
7139
0
            }
7140
0
        }
7141
7142
0
        if (dbContext_ && !datumAdded) {
7143
0
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
7144
0
                                                        std::string());
7145
0
            auto list = authFactory->createObjectsFromName(
7146
0
                datumName, {AuthorityFactory::ObjectType::DATUM},
7147
0
                false /* approximate=false*/);
7148
0
            if (!list.empty()) {
7149
0
                auto datum = util::nn_dynamic_pointer_cast<Datum>(list.front());
7150
0
                if (!datum)
7151
0
                    throw ParsingException(
7152
0
                        "DatumEnsemble member is not a datum");
7153
0
                datums.push_back(NN_NO_CHECK(datum));
7154
0
                datumAdded = true;
7155
0
            }
7156
0
        }
7157
7158
0
        if (!datumAdded) {
7159
            // Fallback if no db match
7160
0
            if (hasEllipsoid) {
7161
0
                datums.emplace_back(GeodeticReferenceFrame::create(
7162
0
                    buildProperties(memberJ),
7163
0
                    buildEllipsoid(getObject(j, "ellipsoid")),
7164
0
                    optional<std::string>(), PrimeMeridian::GREENWICH));
7165
0
            } else {
7166
0
                datums.emplace_back(
7167
0
                    VerticalReferenceFrame::create(buildProperties(memberJ)));
7168
0
            }
7169
0
        }
7170
0
    }
7171
0
    return DatumEnsemble::create(
7172
0
        buildProperties(j), datums,
7173
0
        PositionalAccuracy::create(getString(j, "accuracy")));
7174
0
}
7175
7176
// ---------------------------------------------------------------------------
7177
7178
GeodeticReferenceFrameNNPtr
7179
0
JSONParser::buildGeodeticReferenceFrame(const json &j) {
7180
0
    auto ellipsoidJ = getObject(j, "ellipsoid");
7181
0
    auto pm = j.contains("prime_meridian")
7182
0
                  ? buildPrimeMeridian(getObject(j, "prime_meridian"))
7183
0
                  : PrimeMeridian::GREENWICH;
7184
0
    return GeodeticReferenceFrame::create(buildProperties(j),
7185
0
                                          buildEllipsoid(ellipsoidJ),
7186
0
                                          getAnchor(j), getAnchorEpoch(j), pm);
7187
0
}
7188
7189
// ---------------------------------------------------------------------------
7190
7191
DynamicGeodeticReferenceFrameNNPtr
7192
0
JSONParser::buildDynamicGeodeticReferenceFrame(const json &j) {
7193
0
    auto ellipsoidJ = getObject(j, "ellipsoid");
7194
0
    auto pm = j.contains("prime_meridian")
7195
0
                  ? buildPrimeMeridian(getObject(j, "prime_meridian"))
7196
0
                  : PrimeMeridian::GREENWICH;
7197
0
    Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"),
7198
0
                                UnitOfMeasure::YEAR);
7199
0
    optional<std::string> deformationModel;
7200
0
    if (j.contains("deformation_model")) {
7201
        // Before PROJJSON v0.5 / PROJ 9.1
7202
0
        deformationModel = getString(j, "deformation_model");
7203
0
    } else if (!deformationModelName_.empty()) {
7204
0
        deformationModel = deformationModelName_;
7205
0
    }
7206
0
    return DynamicGeodeticReferenceFrame::create(
7207
0
        buildProperties(j), buildEllipsoid(ellipsoidJ), getAnchor(j), pm,
7208
0
        frameReferenceEpoch, deformationModel);
7209
0
}
7210
7211
// ---------------------------------------------------------------------------
7212
7213
VerticalReferenceFrameNNPtr
7214
0
JSONParser::buildVerticalReferenceFrame(const json &j) {
7215
0
    return VerticalReferenceFrame::create(buildProperties(j), getAnchor(j),
7216
0
                                          getAnchorEpoch(j));
7217
0
}
7218
7219
// ---------------------------------------------------------------------------
7220
7221
DynamicVerticalReferenceFrameNNPtr
7222
0
JSONParser::buildDynamicVerticalReferenceFrame(const json &j) {
7223
0
    Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"),
7224
0
                                UnitOfMeasure::YEAR);
7225
0
    optional<std::string> deformationModel;
7226
0
    if (j.contains("deformation_model")) {
7227
        // Before PROJJSON v0.5 / PROJ 9.1
7228
0
        deformationModel = getString(j, "deformation_model");
7229
0
    } else if (!deformationModelName_.empty()) {
7230
0
        deformationModel = deformationModelName_;
7231
0
    }
7232
0
    return DynamicVerticalReferenceFrame::create(
7233
0
        buildProperties(j), getAnchor(j), util::optional<RealizationMethod>(),
7234
0
        frameReferenceEpoch, deformationModel);
7235
0
}
7236
7237
// ---------------------------------------------------------------------------
7238
7239
0
PrimeMeridianNNPtr JSONParser::buildPrimeMeridian(const json &j) {
7240
0
    if (!j.contains("longitude")) {
7241
0
        throw ParsingException("Missing \"longitude\" key");
7242
0
    }
7243
0
    auto longitude = j["longitude"];
7244
0
    if (longitude.is_number()) {
7245
0
        return PrimeMeridian::create(
7246
0
            buildProperties(j),
7247
0
            Angle(longitude.get<double>(), UnitOfMeasure::DEGREE));
7248
0
    } else if (longitude.is_object()) {
7249
0
        return PrimeMeridian::create(buildProperties(j),
7250
0
                                     Angle(getMeasure(longitude)));
7251
0
    }
7252
0
    throw ParsingException("Unexpected type for value of \"longitude\"");
7253
0
}
7254
7255
// ---------------------------------------------------------------------------
7256
7257
0
EllipsoidNNPtr JSONParser::buildEllipsoid(const json &j) {
7258
0
    if (j.contains("semi_major_axis")) {
7259
0
        auto semiMajorAxis = getLength(j, "semi_major_axis");
7260
0
        const auto ellpsProperties = buildProperties(j);
7261
0
        std::string ellpsName;
7262
0
        ellpsProperties.getStringValue(IdentifiedObject::NAME_KEY, ellpsName);
7263
0
        const auto celestialBody(Ellipsoid::guessBodyName(
7264
0
            dbContext_, semiMajorAxis.getSIValue(), ellpsName));
7265
0
        if (j.contains("semi_minor_axis")) {
7266
0
            return Ellipsoid::createTwoAxis(ellpsProperties, semiMajorAxis,
7267
0
                                            getLength(j, "semi_minor_axis"),
7268
0
                                            celestialBody);
7269
0
        } else if (j.contains("inverse_flattening")) {
7270
0
            return Ellipsoid::createFlattenedSphere(
7271
0
                ellpsProperties, semiMajorAxis,
7272
0
                Scale(getNumber(j, "inverse_flattening")), celestialBody);
7273
0
        } else {
7274
0
            throw ParsingException(
7275
0
                "Missing semi_minor_axis or inverse_flattening");
7276
0
        }
7277
0
    } else if (j.contains("radius")) {
7278
0
        auto radius = getLength(j, "radius");
7279
0
        const auto celestialBody(
7280
0
            Ellipsoid::guessBodyName(dbContext_, radius.getSIValue()));
7281
0
        return Ellipsoid::createSphere(buildProperties(j), radius,
7282
0
                                       celestialBody);
7283
0
    }
7284
0
    throw ParsingException("Missing semi_major_axis or radius");
7285
0
}
7286
7287
//! @endcond
7288
7289
// ---------------------------------------------------------------------------
7290
7291
//! @cond Doxygen_Suppress
7292
7293
// import a CRS encoded as OGC Best Practice document 11-135.
7294
7295
static const char *const crsURLPrefixes[] = {
7296
    "http://opengis.net/def/crs",     "https://opengis.net/def/crs",
7297
    "http://www.opengis.net/def/crs", "https://www.opengis.net/def/crs",
7298
    "www.opengis.net/def/crs",
7299
};
7300
7301
6.15k
static bool isCRSURL(const std::string &text) {
7302
30.5k
    for (const auto crsURLPrefix : crsURLPrefixes) {
7303
30.5k
        if (starts_with(text, crsURLPrefix)) {
7304
87
            return true;
7305
87
        }
7306
30.5k
    }
7307
6.06k
    return false;
7308
6.15k
}
7309
7310
static CRSNNPtr importFromCRSURL(const std::string &text,
7311
112
                                 const DatabaseContextNNPtr &dbContext) {
7312
    // e.g http://www.opengis.net/def/crs/EPSG/0/4326
7313
112
    std::vector<std::string> parts;
7314
383
    for (const auto crsURLPrefix : crsURLPrefixes) {
7315
383
        if (starts_with(text, crsURLPrefix)) {
7316
87
            parts = split(text.substr(strlen(crsURLPrefix)), '/');
7317
87
            break;
7318
87
        }
7319
383
    }
7320
7321
    // e.g
7322
    // "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"
7323
112
    if (!parts.empty() && starts_with(parts[0], "-compound?")) {
7324
64
        parts = split(text.substr(text.find('?') + 1), '&');
7325
64
        std::map<int, std::string> mapParts;
7326
336
        for (const auto &part : parts) {
7327
336
            const auto queryParam = split(part, '=');
7328
336
            if (queryParam.size() != 2) {
7329
21
                throw ParsingException("invalid OGC CRS URL");
7330
21
            }
7331
315
            try {
7332
315
                mapParts[std::stoi(queryParam[0])] = queryParam[1];
7333
315
            } catch (const std::exception &) {
7334
4
                throw ParsingException("invalid OGC CRS URL");
7335
4
            }
7336
315
        }
7337
39
        std::vector<CRSNNPtr> components;
7338
39
        std::string name;
7339
67
        for (size_t i = 1; i <= mapParts.size(); ++i) {
7340
39
            const auto iter = mapParts.find(static_cast<int>(i));
7341
39
            if (iter == mapParts.end()) {
7342
11
                throw ParsingException("invalid OGC CRS URL");
7343
11
            }
7344
28
            components.emplace_back(importFromCRSURL(iter->second, dbContext));
7345
28
            if (!name.empty()) {
7346
0
                name += " + ";
7347
0
            }
7348
28
            name += components.back()->nameStr();
7349
28
        }
7350
28
        return CompoundCRS::create(
7351
28
            util::PropertyMap().set(IdentifiedObject::NAME_KEY, name),
7352
28
            components);
7353
39
    }
7354
7355
48
    if (parts.size() < 4) {
7356
41
        throw ParsingException("invalid OGC CRS URL");
7357
41
    }
7358
7359
7
    const auto &auth_name = parts[1];
7360
7
    const auto &code = parts[3];
7361
7
    try {
7362
7
        auto factoryCRS = AuthorityFactory::create(dbContext, auth_name);
7363
7
        return factoryCRS->createCoordinateReferenceSystem(code, true);
7364
7
    } catch (...) {
7365
7
        const auto &version = parts[2];
7366
7
        if (version.empty() || version == "0") {
7367
3
            const auto authoritiesFromAuthName =
7368
3
                dbContext->getVersionedAuthoritiesFromName(auth_name);
7369
3
            for (const auto &authNameVersioned : authoritiesFromAuthName) {
7370
0
                try {
7371
0
                    auto factoryCRS =
7372
0
                        AuthorityFactory::create(dbContext, authNameVersioned);
7373
0
                    return factoryCRS->createCoordinateReferenceSystem(code,
7374
0
                                                                       true);
7375
0
                } catch (...) {
7376
0
                }
7377
0
            }
7378
3
            throw;
7379
3
        }
7380
4
        std::string authNameWithVersion;
7381
4
        if (!dbContext->getVersionedAuthority(auth_name, version,
7382
4
                                              authNameWithVersion)) {
7383
4
            throw;
7384
4
        }
7385
0
        auto factoryCRS =
7386
0
            AuthorityFactory::create(dbContext, authNameWithVersion);
7387
0
        return factoryCRS->createCoordinateReferenceSystem(code, true);
7388
4
    }
7389
7
}
7390
7391
// ---------------------------------------------------------------------------
7392
7393
/* Import a CRS encoded as WMSAUTO string.
7394
 *
7395
 * Note that the WMS 1.3 specification does not include the
7396
 * units code, while apparently earlier specs do.  We try to
7397
 * guess around this.
7398
 *
7399
 * (code derived from GDAL's importFromWMSAUTO())
7400
 */
7401
7402
40
static CRSNNPtr importFromWMSAUTO(const std::string &text) {
7403
7404
40
    int nUnitsId = 9001;
7405
40
    double dfRefLong;
7406
40
    double dfRefLat = 0.0;
7407
7408
40
    assert(ci_starts_with(text, "AUTO:"));
7409
40
    const auto parts = split(text.substr(strlen("AUTO:")), ',');
7410
7411
40
    try {
7412
40
        constexpr int AUTO_MOLLWEIDE = 42005;
7413
40
        if (parts.size() == 4) {
7414
4
            nUnitsId = std::stoi(parts[1]);
7415
4
            dfRefLong = c_locale_stod(parts[2]);
7416
4
            dfRefLat = c_locale_stod(parts[3]);
7417
36
        } else if (parts.size() == 3 && std::stoi(parts[0]) == AUTO_MOLLWEIDE) {
7418
0
            nUnitsId = std::stoi(parts[1]);
7419
0
            dfRefLong = c_locale_stod(parts[2]);
7420
36
        } else if (parts.size() == 3) {
7421
10
            dfRefLong = c_locale_stod(parts[1]);
7422
10
            dfRefLat = c_locale_stod(parts[2]);
7423
26
        } else if (parts.size() == 2 && std::stoi(parts[0]) == AUTO_MOLLWEIDE) {
7424
0
            dfRefLong = c_locale_stod(parts[1]);
7425
26
        } else {
7426
26
            throw ParsingException("invalid WMS AUTO CRS definition");
7427
26
        }
7428
7429
14
        const auto getConversion = [dfRefLong, dfRefLat, &parts]() {
7430
1
            const int nProjId = std::stoi(parts[0]);
7431
1
            switch (nProjId) {
7432
0
            case 42001: // Auto UTM
7433
0
                if (!(dfRefLong >= -180 && dfRefLong < 180)) {
7434
0
                    throw ParsingException("invalid WMS AUTO CRS definition: "
7435
0
                                           "invalid longitude");
7436
0
                }
7437
0
                return Conversion::createUTM(
7438
0
                    util::PropertyMap(),
7439
0
                    static_cast<int>(floor((dfRefLong + 180.0) / 6.0)) + 1,
7440
0
                    dfRefLat >= 0.0);
7441
7442
0
            case 42002: // Auto TM (strangely very UTM-like).
7443
0
                return Conversion::createTransverseMercator(
7444
0
                    util::PropertyMap(), common::Angle(0),
7445
0
                    common::Angle(dfRefLong), common::Scale(0.9996),
7446
0
                    common::Length(500000),
7447
0
                    common::Length((dfRefLat >= 0.0) ? 0.0 : 10000000.0));
7448
7449
0
            case 42003: // Auto Orthographic.
7450
0
                return Conversion::createOrthographic(
7451
0
                    util::PropertyMap(), common::Angle(dfRefLat),
7452
0
                    common::Angle(dfRefLong), common::Length(0),
7453
0
                    common::Length(0));
7454
7455
0
            case 42004: // Auto Equirectangular
7456
0
                return Conversion::createEquidistantCylindrical(
7457
0
                    util::PropertyMap(), common::Angle(dfRefLat),
7458
0
                    common::Angle(dfRefLong), common::Length(0),
7459
0
                    common::Length(0));
7460
7461
0
            case 42005: // MSVC 2015 thinks that AUTO_MOLLWEIDE is not constant
7462
0
                return Conversion::createMollweide(
7463
0
                    util::PropertyMap(), common::Angle(dfRefLong),
7464
0
                    common::Length(0), common::Length(0));
7465
7466
1
            default:
7467
1
                throw ParsingException("invalid WMS AUTO CRS definition: "
7468
1
                                       "unsupported projection id");
7469
1
            }
7470
1
        };
7471
7472
14
        const auto getUnits = [nUnitsId]() -> const UnitOfMeasure & {
7473
0
            switch (nUnitsId) {
7474
0
            case 9001:
7475
0
                return UnitOfMeasure::METRE;
7476
7477
0
            case 9002:
7478
0
                return UnitOfMeasure::FOOT;
7479
7480
0
            case 9003:
7481
0
                return UnitOfMeasure::US_FOOT;
7482
7483
0
            default:
7484
0
                throw ParsingException("invalid WMS AUTO CRS definition: "
7485
0
                                       "unsupported units code");
7486
0
            }
7487
0
        };
7488
7489
14
        return crs::ProjectedCRS::create(
7490
14
            util::PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
7491
14
            crs::GeographicCRS::EPSG_4326, getConversion(),
7492
14
            cs::CartesianCS::createEastingNorthing(getUnits()));
7493
7494
40
    } catch (const std::exception &) {
7495
40
        throw ParsingException("invalid WMS AUTO CRS definition");
7496
40
    }
7497
40
}
7498
7499
// ---------------------------------------------------------------------------
7500
7501
static BaseObjectNNPtr createFromURNPart(const DatabaseContextPtr &dbContext,
7502
                                         const std::string &type,
7503
                                         const std::string &authName,
7504
                                         const std::string &version,
7505
284
                                         const std::string &code) {
7506
284
    if (!dbContext) {
7507
3
        throw ParsingException("no database context specified");
7508
3
    }
7509
281
    try {
7510
281
        auto factory =
7511
281
            AuthorityFactory::create(NN_NO_CHECK(dbContext), authName);
7512
281
        if (type == "crs") {
7513
248
            return factory->createCoordinateReferenceSystem(code);
7514
248
        }
7515
33
        if (type == "coordinateOperation") {
7516
0
            return factory->createCoordinateOperation(code, true);
7517
0
        }
7518
33
        if (type == "datum") {
7519
4
            return factory->createDatum(code);
7520
4
        }
7521
29
        if (type == "ensemble") {
7522
0
            return factory->createDatumEnsemble(code);
7523
0
        }
7524
29
        if (type == "ellipsoid") {
7525
0
            return factory->createEllipsoid(code);
7526
0
        }
7527
29
        if (type == "meridian") {
7528
0
            return factory->createPrimeMeridian(code);
7529
0
        }
7530
        // Extension of OGC URN syntax to CoordinateMetadata
7531
29
        if (type == "coordinateMetadata") {
7532
0
            return factory->createCoordinateMetadata(code);
7533
0
        }
7534
29
        throw ParsingException(concat("unhandled object type: ", type));
7535
50
    } catch (...) {
7536
50
        if (version.empty()) {
7537
14
            const auto authoritiesFromAuthName =
7538
14
                dbContext->getVersionedAuthoritiesFromName(authName);
7539
14
            for (const auto &authNameVersioned : authoritiesFromAuthName) {
7540
0
                try {
7541
0
                    return createFromURNPart(dbContext, type, authNameVersioned,
7542
0
                                             std::string(), code);
7543
0
                } catch (...) {
7544
0
                }
7545
0
            }
7546
14
            throw;
7547
14
        }
7548
36
        std::string authNameWithVersion;
7549
36
        if (!dbContext->getVersionedAuthority(authName, version,
7550
36
                                              authNameWithVersion)) {
7551
36
            throw;
7552
36
        }
7553
0
        return createFromURNPart(dbContext, type, authNameWithVersion,
7554
0
                                 std::string(), code);
7555
36
    }
7556
281
}
7557
7558
// ---------------------------------------------------------------------------
7559
7560
static BaseObjectNNPtr createFromUserInput(const std::string &text,
7561
                                           const DatabaseContextPtr &dbContext,
7562
                                           bool usePROJ4InitRules,
7563
                                           PJ_CONTEXT *ctx,
7564
69.9k
                                           bool ignoreCoordinateEpoch) {
7565
69.9k
    std::size_t idxFirstCharNotSpace = text.find_first_not_of(" \t\r\n");
7566
69.9k
    if (idxFirstCharNotSpace > 0 && idxFirstCharNotSpace != std::string::npos) {
7567
0
        return createFromUserInput(text.substr(idxFirstCharNotSpace), dbContext,
7568
0
                                   usePROJ4InitRules, ctx,
7569
0
                                   ignoreCoordinateEpoch);
7570
0
    }
7571
7572
    // Parse strings like "ITRF2014 @ 2025.0"
7573
69.9k
    const auto posAt = text.find('@');
7574
69.9k
    if (!ignoreCoordinateEpoch && posAt != std::string::npos) {
7575
7576
        // Try first as if belonged to the name
7577
8.21k
        try {
7578
8.21k
            return createFromUserInput(text, dbContext, usePROJ4InitRules, ctx,
7579
8.21k
                                       /* ignoreCoordinateEpoch = */ true);
7580
8.21k
        } catch (...) {
7581
3.58k
        }
7582
7583
3.58k
        std::string leftPart = text.substr(0, posAt);
7584
3.99k
        while (!leftPart.empty() && leftPart.back() == ' ')
7585
404
            leftPart.resize(leftPart.size() - 1);
7586
3.58k
        const auto nonSpacePos = text.find_first_not_of(' ', posAt + 1);
7587
3.58k
        if (nonSpacePos != std::string::npos) {
7588
3.34k
            auto obj =
7589
3.34k
                createFromUserInput(leftPart, dbContext, usePROJ4InitRules, ctx,
7590
3.34k
                                    /* ignoreCoordinateEpoch = */ true);
7591
3.34k
            auto crs = nn_dynamic_pointer_cast<CRS>(obj);
7592
3.34k
            if (crs) {
7593
268
                double epoch;
7594
268
                try {
7595
268
                    epoch = c_locale_stod(text.substr(nonSpacePos));
7596
268
                } catch (const std::exception &) {
7597
192
                    throw ParsingException("non-numeric value after @");
7598
192
                }
7599
76
                try {
7600
76
                    return CoordinateMetadata::create(NN_NO_CHECK(crs), epoch,
7601
76
                                                      dbContext);
7602
76
                } catch (const std::exception &e) {
7603
40
                    throw ParsingException(
7604
40
                        std::string(
7605
40
                            "CoordinateMetadata::create() failed with: ") +
7606
40
                        e.what());
7607
40
                }
7608
76
            }
7609
3.34k
        }
7610
3.58k
    }
7611
7612
65.0k
    if (!text.empty() && text[0] == '{') {
7613
1.43k
        json j;
7614
1.43k
        try {
7615
1.43k
            j = json::parse(text);
7616
1.43k
        } catch (const std::exception &e) {
7617
1.38k
            throw ParsingException(e.what());
7618
1.38k
        }
7619
43
        return JSONParser().attachDatabaseContext(dbContext).create(j);
7620
1.43k
    }
7621
7622
63.6k
    if (!ci_starts_with(text, "step proj=") &&
7623
60.8k
        !ci_starts_with(text, "step +proj=")) {
7624
5.70M
        for (const auto &wktConstant : WKTConstants::constants()) {
7625
5.70M
            if (ci_starts_with(text, wktConstant)) {
7626
10.8k
                for (auto wkt = text.c_str() + wktConstant.size(); *wkt != '\0';
7627
10.8k
                     ++wkt) {
7628
10.8k
                    if (isspace(static_cast<unsigned char>(*wkt)))
7629
1.60k
                        continue;
7630
9.24k
                    if (*wkt == '[') {
7631
8.97k
                        return WKTParser()
7632
8.97k
                            .attachDatabaseContext(dbContext)
7633
8.97k
                            .setStrict(false)
7634
8.97k
                            .createFromWKT(text);
7635
8.97k
                    }
7636
265
                    break;
7637
9.24k
                }
7638
9.24k
            }
7639
5.70M
        }
7640
60.8k
    }
7641
7642
54.6k
    const char *textWithoutPlusPrefix = text.c_str();
7643
54.6k
    if (textWithoutPlusPrefix[0] == '+')
7644
1.26k
        textWithoutPlusPrefix++;
7645
7646
54.6k
    if (strncmp(textWithoutPlusPrefix, "proj=", strlen("proj=")) == 0 ||
7647
35.6k
        text.find(" +proj=") != std::string::npos ||
7648
35.5k
        text.find(" proj=") != std::string::npos ||
7649
11.1k
        strncmp(textWithoutPlusPrefix, "init=", strlen("init=")) == 0 ||
7650
9.13k
        text.find(" +init=") != std::string::npos ||
7651
8.66k
        text.find(" init=") != std::string::npos ||
7652
45.8k
        strncmp(textWithoutPlusPrefix, "title=", strlen("title=")) == 0) {
7653
45.8k
        return PROJStringParser()
7654
45.8k
            .attachDatabaseContext(dbContext)
7655
45.8k
            .attachContext(ctx)
7656
45.8k
            .setUsePROJ4InitRules(ctx != nullptr
7657
45.8k
                                      ? (proj_context_get_use_proj4_init_rules(
7658
34.3k
                                             ctx, false) == TRUE)
7659
45.8k
                                      : usePROJ4InitRules)
7660
45.8k
            .createFromPROJString(text);
7661
45.8k
    }
7662
7663
8.84k
    if (isCRSURL(text) && dbContext) {
7664
84
        return importFromCRSURL(text, NN_NO_CHECK(dbContext));
7665
84
    }
7666
7667
8.76k
    if (ci_starts_with(text, "AUTO:")) {
7668
29
        return importFromWMSAUTO(text);
7669
29
    }
7670
7671
8.73k
    auto tokens = split(text, ':');
7672
8.73k
    if (tokens.size() == 2) {
7673
3.44k
        if (!dbContext) {
7674
4
            throw ParsingException("no database context specified");
7675
4
        }
7676
3.44k
        DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext));
7677
3.44k
        const auto &authName = tokens[0];
7678
3.44k
        const auto &code = tokens[1];
7679
3.44k
        auto factory = AuthorityFactory::create(dbContextNNPtr, authName);
7680
3.44k
        try {
7681
3.44k
            return factory->createCoordinateReferenceSystem(code);
7682
3.44k
        } catch (...) {
7683
7684
            // Convenience for well-known misused code
7685
            // See https://github.com/OSGeo/PROJ/issues/1730
7686
954
            if (ci_equal(authName, "EPSG") && code == "102100") {
7687
11
                factory = AuthorityFactory::create(dbContextNNPtr, "ESRI");
7688
11
                return factory->createCoordinateReferenceSystem(code);
7689
11
            }
7690
7691
943
            const auto authoritiesFromAuthName =
7692
943
                dbContextNNPtr->getVersionedAuthoritiesFromName(authName);
7693
943
            for (const auto &authNameVersioned : authoritiesFromAuthName) {
7694
0
                factory =
7695
0
                    AuthorityFactory::create(dbContextNNPtr, authNameVersioned);
7696
0
                try {
7697
0
                    return factory->createCoordinateReferenceSystem(code);
7698
0
                } catch (...) {
7699
0
                }
7700
0
            }
7701
7702
943
            const auto allAuthorities = dbContextNNPtr->getAuthorities();
7703
3.35k
            for (const auto &authCandidate : allAuthorities) {
7704
3.35k
                if (ci_equal(authCandidate, authName)) {
7705
858
                    factory =
7706
858
                        AuthorityFactory::create(dbContextNNPtr, authCandidate);
7707
858
                    try {
7708
858
                        return factory->createCoordinateReferenceSystem(code);
7709
858
                    } catch (...) {
7710
                        // EPSG:4326+3855
7711
853
                        auto tokensCode = split(code, '+');
7712
853
                        if (tokensCode.size() == 2) {
7713
221
                            auto crs1(factory->createCoordinateReferenceSystem(
7714
221
                                tokensCode[0], false));
7715
221
                            auto crs2(factory->createCoordinateReferenceSystem(
7716
221
                                tokensCode[1], false));
7717
221
                            return CompoundCRS::createLax(
7718
221
                                util::PropertyMap().set(
7719
221
                                    IdentifiedObject::NAME_KEY,
7720
221
                                    crs1->nameStr() + " + " + crs2->nameStr()),
7721
221
                                {crs1, crs2}, dbContext);
7722
221
                        }
7723
632
                        throw;
7724
853
                    }
7725
858
                }
7726
3.35k
            }
7727
85
            throw;
7728
943
        }
7729
5.28k
    } else if (tokens.size() == 3) {
7730
        // ESRI:103668+EPSG:5703 ... compound
7731
132
        auto tokensCenter = split(tokens[1], '+');
7732
132
        if (tokensCenter.size() == 2) {
7733
34
            if (!dbContext) {
7734
2
                throw ParsingException("no database context specified");
7735
2
            }
7736
32
            DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext));
7737
7738
32
            const auto &authName1 = tokens[0];
7739
32
            const auto &code1 = tokensCenter[0];
7740
32
            const auto &authName2 = tokensCenter[1];
7741
32
            const auto &code2 = tokens[2];
7742
7743
32
            auto factory1 = AuthorityFactory::create(dbContextNNPtr, authName1);
7744
32
            auto crs1 = factory1->createCoordinateReferenceSystem(code1, false);
7745
32
            auto factory2 = AuthorityFactory::create(dbContextNNPtr, authName2);
7746
32
            auto crs2 = factory2->createCoordinateReferenceSystem(code2, false);
7747
32
            return CompoundCRS::createLax(
7748
32
                util::PropertyMap().set(IdentifiedObject::NAME_KEY,
7749
32
                                        crs1->nameStr() + " + " +
7750
32
                                            crs2->nameStr()),
7751
32
                {crs1, crs2}, dbContext);
7752
34
        }
7753
132
    }
7754
7755
5.25k
    if (starts_with(text, "urn:ogc:def:crs,")) {
7756
5
        if (!dbContext) {
7757
1
            throw ParsingException("no database context specified");
7758
1
        }
7759
4
        auto tokensComma = split(text, ',');
7760
4
        if (tokensComma.size() == 4 && starts_with(tokensComma[1], "crs:") &&
7761
0
            starts_with(tokensComma[2], "cs:") &&
7762
0
            starts_with(tokensComma[3], "coordinateOperation:")) {
7763
            // OGC 07-092r2: para 7.5.4
7764
            // URN combined references for projected or derived CRSs
7765
0
            const auto &crsPart = tokensComma[1];
7766
0
            const auto tokensCRS = split(crsPart, ':');
7767
0
            if (tokensCRS.size() != 4) {
7768
0
                throw ParsingException(
7769
0
                    concat("invalid crs component: ", crsPart));
7770
0
            }
7771
0
            auto factoryCRS =
7772
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensCRS[1]);
7773
0
            auto baseCRS =
7774
0
                factoryCRS->createCoordinateReferenceSystem(tokensCRS[3], true);
7775
7776
0
            const auto &csPart = tokensComma[2];
7777
0
            auto tokensCS = split(csPart, ':');
7778
0
            if (tokensCS.size() != 4) {
7779
0
                throw ParsingException(
7780
0
                    concat("invalid cs component: ", csPart));
7781
0
            }
7782
0
            auto factoryCS =
7783
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensCS[1]);
7784
0
            auto cs = factoryCS->createCoordinateSystem(tokensCS[3]);
7785
7786
0
            const auto &opPart = tokensComma[3];
7787
0
            auto tokensOp = split(opPart, ':');
7788
0
            if (tokensOp.size() != 4) {
7789
0
                throw ParsingException(
7790
0
                    concat("invalid coordinateOperation component: ", opPart));
7791
0
            }
7792
0
            auto factoryOp =
7793
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensOp[1]);
7794
0
            auto op = factoryOp->createCoordinateOperation(tokensOp[3], true);
7795
7796
0
            const auto &baseName = baseCRS->nameStr();
7797
0
            std::string name(baseName);
7798
0
            auto geogCRS =
7799
0
                util::nn_dynamic_pointer_cast<GeographicCRS>(baseCRS);
7800
0
            if (geogCRS &&
7801
0
                geogCRS->coordinateSystem()->axisList().size() == 3 &&
7802
0
                baseName.find("3D") == std::string::npos) {
7803
0
                name += " (3D)";
7804
0
            }
7805
0
            name += " / ";
7806
0
            name += op->nameStr();
7807
0
            auto props =
7808
0
                util::PropertyMap().set(IdentifiedObject::NAME_KEY, name);
7809
7810
0
            if (auto conv = util::nn_dynamic_pointer_cast<Conversion>(op)) {
7811
0
                auto convNN = NN_NO_CHECK(conv);
7812
0
                if (geogCRS != nullptr) {
7813
0
                    auto geogCRSNN = NN_NO_CHECK(geogCRS);
7814
0
                    if (CartesianCSPtr ccs =
7815
0
                            util::nn_dynamic_pointer_cast<CartesianCS>(cs)) {
7816
0
                        return ProjectedCRS::create(props, geogCRSNN, convNN,
7817
0
                                                    NN_NO_CHECK(ccs));
7818
0
                    }
7819
0
                    if (EllipsoidalCSPtr ecs =
7820
0
                            util::nn_dynamic_pointer_cast<EllipsoidalCS>(cs)) {
7821
0
                        return DerivedGeographicCRS::create(
7822
0
                            props, geogCRSNN, convNN, NN_NO_CHECK(ecs));
7823
0
                    }
7824
0
                } else if (dynamic_cast<GeodeticCRS *>(baseCRS.get()) &&
7825
0
                           dynamic_cast<CartesianCS *>(cs.get())) {
7826
0
                    return DerivedGeodeticCRS::create(
7827
0
                        props,
7828
0
                        NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
7829
0
                            baseCRS)),
7830
0
                        convNN,
7831
0
                        NN_NO_CHECK(
7832
0
                            util::nn_dynamic_pointer_cast<CartesianCS>(cs)));
7833
0
                } else if (auto pcrs =
7834
0
                               util::nn_dynamic_pointer_cast<ProjectedCRS>(
7835
0
                                   baseCRS)) {
7836
0
                    return DerivedProjectedCRS::create(props, NN_NO_CHECK(pcrs),
7837
0
                                                       convNN, cs);
7838
0
                } else if (auto vertBaseCRS =
7839
0
                               util::nn_dynamic_pointer_cast<VerticalCRS>(
7840
0
                                   baseCRS)) {
7841
0
                    if (auto vertCS =
7842
0
                            util::nn_dynamic_pointer_cast<VerticalCS>(cs)) {
7843
0
                        const int methodCode = convNN->method()->getEPSGCode();
7844
0
                        std::string newName(baseName);
7845
0
                        std::string unitNameSuffix;
7846
0
                        for (const char *suffix : {" (ft)", " (ftUS)"}) {
7847
0
                            if (ends_with(newName, suffix)) {
7848
0
                                unitNameSuffix = suffix;
7849
0
                                newName.resize(newName.size() - strlen(suffix));
7850
0
                                break;
7851
0
                            }
7852
0
                        }
7853
0
                        bool newNameOk = false;
7854
0
                        if (methodCode ==
7855
0
                                EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR ||
7856
0
                            methodCode ==
7857
0
                                EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
7858
0
                            const auto &unitName =
7859
0
                                vertCS->axisList()[0]->unit().name();
7860
0
                            if (unitName == UnitOfMeasure::METRE.name()) {
7861
0
                                newNameOk = true;
7862
0
                            } else if (unitName == UnitOfMeasure::FOOT.name()) {
7863
0
                                newName += " (ft)";
7864
0
                                newNameOk = true;
7865
0
                            } else if (unitName ==
7866
0
                                       UnitOfMeasure::US_FOOT.name()) {
7867
0
                                newName += " (ftUS)";
7868
0
                                newNameOk = true;
7869
0
                            }
7870
0
                        } else if (methodCode ==
7871
0
                                   EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
7872
0
                            if (ends_with(newName, " height")) {
7873
0
                                newName.resize(newName.size() -
7874
0
                                               strlen(" height"));
7875
0
                                newName += " depth";
7876
0
                                newName += unitNameSuffix;
7877
0
                                newNameOk = true;
7878
0
                            } else if (ends_with(newName, " depth")) {
7879
0
                                newName.resize(newName.size() -
7880
0
                                               strlen(" depth"));
7881
0
                                newName += " height";
7882
0
                                newName += unitNameSuffix;
7883
0
                                newNameOk = true;
7884
0
                            }
7885
0
                        }
7886
0
                        if (newNameOk) {
7887
0
                            props.set(IdentifiedObject::NAME_KEY, newName);
7888
0
                        }
7889
0
                        return DerivedVerticalCRS::create(
7890
0
                            props, NN_NO_CHECK(vertBaseCRS), convNN,
7891
0
                            NN_NO_CHECK(vertCS));
7892
0
                    }
7893
0
                }
7894
0
            }
7895
7896
0
            throw ParsingException("unsupported combination of baseCRS, CS "
7897
0
                                   "and coordinateOperation for a "
7898
0
                                   "DerivedCRS");
7899
0
        }
7900
7901
        // OGC 07-092r2: para 7.5.2
7902
        // URN combined references for compound coordinate reference systems
7903
4
        std::vector<CRSNNPtr> components;
7904
4
        std::string name;
7905
4
        for (size_t i = 1; i < tokensComma.size(); i++) {
7906
4
            tokens = split(tokensComma[i], ':');
7907
4
            if (tokens.size() != 4) {
7908
4
                throw ParsingException(
7909
4
                    concat("invalid crs component: ", tokensComma[i]));
7910
4
            }
7911
0
            const auto &type = tokens[0];
7912
0
            auto factory =
7913
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[1]);
7914
0
            const auto &code = tokens[3];
7915
0
            if (type == "crs") {
7916
0
                auto crs(factory->createCoordinateReferenceSystem(code, false));
7917
0
                components.emplace_back(crs);
7918
0
                if (!name.empty()) {
7919
0
                    name += " + ";
7920
0
                }
7921
0
                name += crs->nameStr();
7922
0
            } else {
7923
0
                throw ParsingException(
7924
0
                    concat("unexpected object type: ", type));
7925
0
            }
7926
0
        }
7927
0
        return CompoundCRS::create(
7928
0
            util::PropertyMap().set(IdentifiedObject::NAME_KEY, name),
7929
0
            components);
7930
4
    }
7931
7932
    // OGC 07-092r2: para 7.5.3
7933
    // 7.5.3 URN combined references for concatenated operations
7934
5.24k
    if (starts_with(text, "urn:ogc:def:coordinateOperation,")) {
7935
0
        if (!dbContext) {
7936
0
            throw ParsingException("no database context specified");
7937
0
        }
7938
0
        auto tokensComma = split(text, ',');
7939
0
        std::vector<CoordinateOperationNNPtr> components;
7940
0
        for (size_t i = 1; i < tokensComma.size(); i++) {
7941
0
            tokens = split(tokensComma[i], ':');
7942
0
            if (tokens.size() != 4) {
7943
0
                throw ParsingException(concat(
7944
0
                    "invalid coordinateOperation component: ", tokensComma[i]));
7945
0
            }
7946
0
            const auto &type = tokens[0];
7947
0
            auto factory =
7948
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[1]);
7949
0
            const auto &code = tokens[3];
7950
0
            if (type == "coordinateOperation") {
7951
0
                auto op(factory->createCoordinateOperation(code, false));
7952
0
                components.emplace_back(op);
7953
0
            } else {
7954
0
                throw ParsingException(
7955
0
                    concat("unexpected object type: ", type));
7956
0
            }
7957
0
        }
7958
0
        return ConcatenatedOperation::createComputeMetadata(components, true);
7959
0
    }
7960
7961
    // urn:ogc:def:crs:EPSG::4326
7962
5.24k
    if (tokens.size() == 7 && tolower(tokens[0]) == "urn") {
7963
7964
53
        const std::string type(tokens[3] == "CRS" ? "crs" : tokens[3]);
7965
53
        const auto &authName = tokens[4];
7966
53
        const auto &version = tokens[5];
7967
53
        const auto &code = tokens[6];
7968
53
        return createFromURNPart(dbContext, type, authName, version, code);
7969
53
    }
7970
7971
    // urn:ogc:def:crs:OGC::AUTO42001:-117:33
7972
5.19k
    if (tokens.size() > 7 && tokens[0] == "urn" && tokens[4] == "OGC" &&
7973
12
        ci_starts_with(tokens[6], "AUTO")) {
7974
11
        const auto textAUTO = text.substr(text.find(":AUTO") + 5);
7975
11
        return importFromWMSAUTO("AUTO:" + replaceAll(textAUTO, ":", ","));
7976
11
    }
7977
7978
    // Legacy urn:opengis:crs:EPSG:0:4326 (note the missing def: compared to
7979
    // above)
7980
5.18k
    if (tokens.size() == 6 && tokens[0] == "urn" && tokens[2] != "def") {
7981
82
        const auto &type = tokens[2];
7982
82
        const auto &authName = tokens[3];
7983
82
        const auto &version = tokens[4];
7984
82
        const auto &code = tokens[5];
7985
82
        return createFromURNPart(dbContext, type, authName, version, code);
7986
82
    }
7987
7988
    // Legacy urn:x-ogc:def:crs:EPSG:4326 (note the missing version)
7989
5.10k
    if (tokens.size() == 6 && tokens[0] == "urn") {
7990
149
        const auto &type = tokens[3];
7991
149
        const auto &authName = tokens[4];
7992
149
        const auto &code = tokens[5];
7993
149
        return createFromURNPart(dbContext, type, authName, std::string(),
7994
149
                                 code);
7995
149
    }
7996
7997
4.95k
    if (dbContext) {
7998
2.22k
        auto factory =
7999
2.22k
            AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string());
8000
8001
2.22k
        const auto searchObject =
8002
2.22k
            [&factory](
8003
2.22k
                const std::string &objectName, bool approximateMatch,
8004
2.22k
                const std::vector<AuthorityFactory::ObjectType> &objectTypes)
8005
7.68k
            -> IdentifiedObjectPtr {
8006
7.68k
            constexpr size_t limitResultCount = 10;
8007
7.68k
            auto res = factory->createObjectsFromName(
8008
7.68k
                objectName, objectTypes, approximateMatch, limitResultCount);
8009
7.68k
            if (res.size() == 1) {
8010
191
                return res.front().as_nullable();
8011
191
            }
8012
7.49k
            if (res.size() > 1) {
8013
1.41k
                if (objectTypes.size() == 1 &&
8014
1.20k
                    objectTypes[0] == AuthorityFactory::ObjectType::CRS) {
8015
1.38k
                    for (size_t ndim = 2; ndim <= 3; ndim++) {
8016
3.83k
                        for (const auto &obj : res) {
8017
3.83k
                            auto crs =
8018
3.83k
                                dynamic_cast<crs::GeographicCRS *>(obj.get());
8019
3.83k
                            if (crs &&
8020
1.86k
                                crs->coordinateSystem()->axisList().size() ==
8021
1.86k
                                    ndim) {
8022
1.14k
                                return obj.as_nullable();
8023
1.14k
                            }
8024
3.83k
                        }
8025
1.32k
                    }
8026
1.20k
                }
8027
8028
                // If there's exactly only one object whose name is equivalent
8029
                // to the user input, return it.
8030
806
                for (int pass = 0; pass <= 1; ++pass) {
8031
538
                    IdentifiedObjectPtr identifiedObj;
8032
4.48k
                    for (const auto &obj : res) {
8033
4.48k
                        if (Identifier::isEquivalentName(
8034
4.48k
                                obj->nameStr().c_str(), objectName.c_str(),
8035
4.48k
                                /* biggerDifferencesAllowed = */ pass == 1)) {
8036
1
                            if (identifiedObj == nullptr) {
8037
1
                                identifiedObj = obj.as_nullable();
8038
1
                            } else {
8039
0
                                identifiedObj = nullptr;
8040
0
                                break;
8041
0
                            }
8042
1
                        }
8043
4.48k
                    }
8044
538
                    if (identifiedObj) {
8045
1
                        return identifiedObj;
8046
1
                    }
8047
538
                }
8048
8049
268
                std::string msg("several objects matching this name: ");
8050
268
                bool first = true;
8051
1.90k
                for (const auto &obj : res) {
8052
1.90k
                    if (msg.size() > 200) {
8053
156
                        msg += ", ...";
8054
156
                        break;
8055
156
                    }
8056
1.74k
                    if (!first) {
8057
1.47k
                        msg += ", ";
8058
1.47k
                    }
8059
1.74k
                    first = false;
8060
1.74k
                    msg += obj->nameStr();
8061
1.74k
                }
8062
268
                throw ParsingException(msg);
8063
269
            }
8064
6.08k
            return nullptr;
8065
7.49k
        };
8066
8067
2.22k
        const auto searchCRS = [&searchObject](const std::string &objectName) {
8068
24
            const auto objectTypes = std::vector<AuthorityFactory::ObjectType>{
8069
24
                AuthorityFactory::ObjectType::CRS};
8070
24
            {
8071
24
                constexpr bool approximateMatch = false;
8072
24
                auto ret =
8073
24
                    searchObject(objectName, approximateMatch, objectTypes);
8074
24
                if (ret)
8075
0
                    return ret;
8076
24
            }
8077
8078
24
            constexpr bool approximateMatch = true;
8079
24
            return searchObject(objectName, approximateMatch, objectTypes);
8080
24
        };
8081
8082
        // strings like "WGS 84 + EGM96 height"
8083
2.22k
        CompoundCRSPtr compoundCRS;
8084
2.22k
        try {
8085
2.22k
            const auto tokensCompound = split(text, " + ");
8086
2.22k
            if (tokensCompound.size() == 2) {
8087
12
                auto obj1 = searchCRS(tokensCompound[0]);
8088
12
                auto obj2 = searchCRS(tokensCompound[1]);
8089
12
                auto crs1 = std::dynamic_pointer_cast<CRS>(obj1);
8090
12
                auto crs2 = std::dynamic_pointer_cast<CRS>(obj2);
8091
12
                if (crs1 && crs2) {
8092
0
                    compoundCRS =
8093
0
                        CompoundCRS::create(
8094
0
                            util::PropertyMap().set(IdentifiedObject::NAME_KEY,
8095
0
                                                    crs1->nameStr() + " + " +
8096
0
                                                        crs2->nameStr()),
8097
0
                            {NN_NO_CHECK(crs1), NN_NO_CHECK(crs2)})
8098
0
                            .as_nullable();
8099
0
                }
8100
12
            }
8101
2.22k
        } catch (const std::exception &) {
8102
0
        }
8103
8104
        // First pass: exact match on CRS objects
8105
        // Second pass: exact match on other objects
8106
        // Third pass: approximate match on CRS objects
8107
        // Fourth pass: approximate match on other objects
8108
        // But only allow approximate matching if the size of the text is
8109
        // large enough (>= 5), otherwise we get a lot of false positives:
8110
        // "foo" -> "Amersfoort", "bar" -> "Barbados 1938"
8111
        // Also only accept approximate matching if the ratio between the
8112
        // input and match size is not too small, so that "omerc" doesn't match
8113
        // with "WGS 84 / Pseudo-Mercator"
8114
2.22k
        const int maxNumberPasses = text.size() <= 4 ? 2 : 4;
8115
8.67k
        for (int pass = 0; pass < maxNumberPasses; ++pass) {
8116
7.64k
            const bool approximateMatch = (pass >= 2);
8117
7.64k
            auto ret = searchObject(
8118
7.64k
                text, approximateMatch,
8119
7.64k
                (pass == 0 || pass == 2)
8120
7.64k
                    ? std::vector<
8121
4.36k
                          AuthorityFactory::ObjectType>{AuthorityFactory::
8122
4.36k
                                                            ObjectType::CRS}
8123
7.64k
                    : std::vector<AuthorityFactory::ObjectType>{
8124
3.27k
                          AuthorityFactory::ObjectType::ELLIPSOID,
8125
3.27k
                          AuthorityFactory::ObjectType::DATUM,
8126
3.27k
                          AuthorityFactory::ObjectType::DATUM_ENSEMBLE,
8127
3.27k
                          AuthorityFactory::ObjectType::COORDINATE_OPERATION});
8128
7.64k
            if (ret) {
8129
1.33k
                if (!approximateMatch ||
8130
1.32k
                    ret->nameStr().size() < 2 * text.size())
8131
1.18k
                    return NN_NO_CHECK(ret);
8132
1.33k
            }
8133
6.45k
            if (compoundCRS) {
8134
0
                if (!approximateMatch ||
8135
0
                    compoundCRS->nameStr().size() < 2 * text.size())
8136
0
                    return NN_NO_CHECK(compoundCRS);
8137
0
            }
8138
6.45k
        }
8139
2.22k
    }
8140
8141
3.76k
    throw ParsingException("unrecognized format / unknown name");
8142
4.95k
}
8143
//! @endcond
8144
8145
// ---------------------------------------------------------------------------
8146
8147
/** \brief Instantiate a sub-class of BaseObject from a user specified text.
8148
 *
8149
 * The text can be a:
8150
 * <ul>
8151
 * <li>WKT string</li>
8152
 * <li>PROJ string</li>
8153
 * <li>database code, prefixed by its authority. e.g. "EPSG:4326"</li>
8154
 * <li>OGC URN. e.g. "urn:ogc:def:crs:EPSG::4326",
8155
 *     "urn:ogc:def:coordinateOperation:EPSG::1671",
8156
 *     "urn:ogc:def:ellipsoid:EPSG::7001"
8157
 *     or "urn:ogc:def:datum:EPSG::6326"</li>
8158
 * <li> OGC URN combining references for compound coordinate reference systems
8159
 *      e.g. "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717"
8160
 *      We also accept a custom abbreviated syntax EPSG:2393+5717
8161
 *      or ESRI:103668+EPSG:5703
8162
 * </li>
8163
 * <li> OGC URN combining references for references for projected or derived
8164
 * CRSs
8165
 *      e.g. for Projected 3D CRS "UTM zone 31N / WGS 84 (3D)"
8166
 *      "urn:ogc:def:crs,crs:EPSG::4979,cs:PROJ::ENh,coordinateOperation:EPSG::16031"
8167
 * </li>
8168
 * <li>Extension of OGC URN for CoordinateMetadata.
8169
 *     e.g.
8170
 * "urn:ogc:def:coordinateMetadata:NRCAN::NAD83_CSRS_1997_MTM11_HT2_1997"</li>
8171
 * <li> OGC URN combining references for concatenated operations
8172
 *      e.g.
8173
 * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</li>
8174
 * <li>OGC URL for a single CRS. e.g.
8175
 * "http://www.opengis.net/def/crs/EPSG/0/4326"</li>
8176
 * <li>OGC URL for a compound
8177
 * CRS. e.g
8178
 * "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>
8179
 * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
8180
 *     uniqueness is not guaranteed, the function may apply heuristics to
8181
 *     determine the appropriate best match.</li>
8182
 * <li>a CRS name and a coordinate epoch, separated with '@'. For example
8183
 *     "ITRF2014@2025.0". (added in PROJ 9.2)</li>
8184
 * <li>a compound CRS made from two object names separated with " + ".
8185
 *     e.g. "WGS 84 + EGM96 height"</li>
8186
 * <li>PROJJSON string</li>
8187
 * </ul>
8188
 *
8189
 * @param text One of the above mentioned text format
8190
 * @param dbContext Database context, or nullptr (in which case database
8191
 * lookups will not work)
8192
 * @param usePROJ4InitRules When set to true,
8193
 * init=epsg:XXXX syntax will be allowed and will be interpreted according to
8194
 * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
8195
 * order and will expect/output coordinates in radians. ProjectedCRS will have
8196
 * easting, northing axis order (except the ones with Transverse Mercator South
8197
 * Orientated projection). In that mode, the epsg:XXXX syntax will be also
8198
 * interpreted the same way.
8199
 * @throw ParsingException if the string cannot be parsed.
8200
 */
8201
BaseObjectNNPtr createFromUserInput(const std::string &text,
8202
                                    const DatabaseContextPtr &dbContext,
8203
14.2k
                                    bool usePROJ4InitRules) {
8204
14.2k
    return createFromUserInput(text, dbContext, usePROJ4InitRules, nullptr,
8205
14.2k
                               /* ignoreCoordinateEpoch = */ false);
8206
14.2k
}
8207
8208
// ---------------------------------------------------------------------------
8209
8210
/** \brief Instantiate a sub-class of BaseObject from a user specified text.
8211
 *
8212
 * The text can be a:
8213
 * <ul>
8214
 * <li>WKT string</li>
8215
 * <li>PROJ string</li>
8216
 * <li>database code, prefixed by its authority. e.g. "EPSG:4326"</li>
8217
 * <li>OGC URN. e.g. "urn:ogc:def:crs:EPSG::4326",
8218
 *     "urn:ogc:def:coordinateOperation:EPSG::1671",
8219
 *     "urn:ogc:def:ellipsoid:EPSG::7001"
8220
 *     or "urn:ogc:def:datum:EPSG::6326"</li>
8221
 * <li> OGC URN combining references for compound coordinate reference systems
8222
 *      e.g. "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717"
8223
 *      We also accept a custom abbreviated syntax EPSG:2393+5717
8224
 * </li>
8225
 * <li> OGC URN combining references for references for projected or derived
8226
 * CRSs
8227
 *      e.g. for Projected 3D CRS "UTM zone 31N / WGS 84 (3D)"
8228
 *      "urn:ogc:def:crs,crs:EPSG::4979,cs:PROJ::ENh,coordinateOperation:EPSG::16031"
8229
 * </li>
8230
 * <li>Extension of OGC URN for CoordinateMetadata.
8231
 *     e.g.
8232
 * "urn:ogc:def:coordinateMetadata:NRCAN::NAD83_CSRS_1997_MTM11_HT2_1997"</li>
8233
 * <li> OGC URN combining references for concatenated operations
8234
 *      e.g.
8235
 * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</li>
8236
 * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
8237
 *     uniqueness is not guaranteed, the function may apply heuristics to
8238
 *     determine the appropriate best match.</li>
8239
 * <li>a compound CRS made from two object names separated with " + ".
8240
 *     e.g. "WGS 84 + EGM96 height"</li>
8241
 * <li>PROJJSON string</li>
8242
 * </ul>
8243
 *
8244
 * @param text One of the above mentioned text format
8245
 * @param ctx PROJ context
8246
 * @throw ParsingException if the string cannot be parsed.
8247
 */
8248
44.1k
BaseObjectNNPtr createFromUserInput(const std::string &text, PJ_CONTEXT *ctx) {
8249
44.1k
    DatabaseContextPtr dbContext;
8250
44.1k
    try {
8251
44.1k
        if (ctx != nullptr) {
8252
            // Only connect to proj.db if needed
8253
44.1k
            if (text.find("proj=") == std::string::npos ||
8254
30.7k
                text.find("init=") != std::string::npos) {
8255
23.6k
                dbContext =
8256
23.6k
                    ctx->get_cpp_context()->getDatabaseContext().as_nullable();
8257
23.6k
            }
8258
44.1k
        }
8259
44.1k
    } catch (const std::exception &) {
8260
0
    }
8261
44.1k
    return createFromUserInput(text, dbContext, false, ctx,
8262
44.1k
                               /* ignoreCoordinateEpoch = */ false);
8263
44.1k
}
8264
8265
// ---------------------------------------------------------------------------
8266
8267
/** \brief Instantiate a sub-class of BaseObject from a WKT string.
8268
 *
8269
 * By default, validation is strict (to the extent of the checks that are
8270
 * actually implemented. Currently only WKT1 strict grammar is checked), and
8271
 * any issue detected will cause an exception to be thrown, unless
8272
 * setStrict(false) is called priorly.
8273
 *
8274
 * In non-strict mode, non-fatal issues will be recovered and simply listed
8275
 * in warningList(). This does not prevent more severe errors to cause an
8276
 * exception to be thrown.
8277
 *
8278
 * @throw ParsingException if the string cannot be parsed.
8279
 */
8280
8.97k
BaseObjectNNPtr WKTParser::createFromWKT(const std::string &wkt) {
8281
8282
8.97k
    const auto dialect = guessDialect(wkt);
8283
8.97k
    d->maybeEsriStyle_ = (dialect == WKTGuessedDialect::WKT1_ESRI);
8284
8.97k
    if (d->maybeEsriStyle_) {
8285
2.02k
        if (wkt.find("PARAMETER[\"X_Scale\",") != std::string::npos) {
8286
0
            d->esriStyle_ = true;
8287
0
            d->maybeEsriStyle_ = false;
8288
0
        }
8289
2.02k
    }
8290
8291
8.97k
    const auto build = [this, &wkt]() -> BaseObjectNNPtr {
8292
8.97k
        size_t indexEnd;
8293
8.97k
        WKTNodeNNPtr root = WKTNode::createFrom(wkt, 0, 0, indexEnd);
8294
8.97k
        const std::string &name(root->GP()->value());
8295
8.97k
        if (ci_equal(name, WKTConstants::DATUM) ||
8296
8.56k
            ci_equal(name, WKTConstants::GEODETICDATUM) ||
8297
8.56k
            ci_equal(name, WKTConstants::TRF)) {
8298
8299
25
            auto primeMeridian = PrimeMeridian::GREENWICH;
8300
25
            if (indexEnd < wkt.size()) {
8301
21
                indexEnd = skipSpace(wkt, indexEnd);
8302
21
                if (indexEnd < wkt.size() && wkt[indexEnd] == ',') {
8303
15
                    ++indexEnd;
8304
15
                    indexEnd = skipSpace(wkt, indexEnd);
8305
15
                    if (indexEnd < wkt.size() &&
8306
13
                        ci_starts_with(wkt.c_str() + indexEnd,
8307
13
                                       WKTConstants::PRIMEM.c_str())) {
8308
7
                        primeMeridian = d->buildPrimeMeridian(
8309
7
                            WKTNode::createFrom(wkt, indexEnd, 0, indexEnd),
8310
7
                            UnitOfMeasure::DEGREE);
8311
7
                    }
8312
15
                }
8313
21
            }
8314
25
            return d->buildGeodeticReferenceFrame(root, primeMeridian,
8315
25
                                                  null_node);
8316
8.95k
        } else if (ci_equal(name, WKTConstants::GEOGCS) ||
8317
7.65k
                   ci_equal(name, WKTConstants::PROJCS)) {
8318
            // Parse implicit compoundCRS from ESRI that is
8319
            // "PROJCS[...],VERTCS[...]" or "GEOGCS[...],VERTCS[...]"
8320
977
            if (indexEnd < wkt.size()) {
8321
207
                indexEnd = skipSpace(wkt, indexEnd);
8322
207
                if (indexEnd < wkt.size() && wkt[indexEnd] == ',') {
8323
81
                    ++indexEnd;
8324
81
                    indexEnd = skipSpace(wkt, indexEnd);
8325
81
                    if (indexEnd < wkt.size() &&
8326
3
                        ci_starts_with(wkt.c_str() + indexEnd,
8327
3
                                       WKTConstants::VERTCS.c_str())) {
8328
0
                        auto horizCRS = d->buildCRS(root);
8329
0
                        if (horizCRS) {
8330
0
                            auto vertCRS =
8331
0
                                d->buildVerticalCRS(WKTNode::createFrom(
8332
0
                                    wkt, indexEnd, 0, indexEnd));
8333
0
                            return CompoundCRS::createLax(
8334
0
                                util::PropertyMap().set(
8335
0
                                    IdentifiedObject::NAME_KEY,
8336
0
                                    horizCRS->nameStr() + " + " +
8337
0
                                        vertCRS->nameStr()),
8338
0
                                {NN_NO_CHECK(horizCRS), vertCRS},
8339
0
                                d->dbContext_);
8340
0
                        }
8341
0
                    }
8342
81
                }
8343
207
            }
8344
977
        }
8345
8.95k
        return d->build(root);
8346
8.97k
    };
8347
8348
8.97k
    auto obj = build();
8349
8350
8.97k
    if (dialect == WKTGuessedDialect::WKT1_GDAL ||
8351
5.77k
        dialect == WKTGuessedDialect::WKT1_ESRI) {
8352
3.25k
        auto errorMsg = pj_wkt1_parse(wkt);
8353
3.25k
        if (!errorMsg.empty()) {
8354
3.00k
            d->emitGrammarError(errorMsg);
8355
3.00k
        }
8356
5.72k
    } else if (dialect == WKTGuessedDialect::WKT2_2015 ||
8357
3.89k
               dialect == WKTGuessedDialect::WKT2_2019) {
8358
3.89k
        auto errorMsg = pj_wkt2_parse(wkt);
8359
3.89k
        if (!errorMsg.empty()) {
8360
3.82k
            d->emitGrammarError(errorMsg);
8361
3.82k
        }
8362
3.89k
    }
8363
8364
8.97k
    return obj;
8365
8.97k
}
8366
8367
// ---------------------------------------------------------------------------
8368
8369
/** \brief Attach a database context, to allow queries in it if needed.
8370
 */
8371
WKTParser &
8372
8.97k
WKTParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) {
8373
8.97k
    d->dbContext_ = dbContext;
8374
8.97k
    return *this;
8375
8.97k
}
8376
8377
// ---------------------------------------------------------------------------
8378
8379
/** \brief Guess the "dialect" of the WKT string.
8380
 */
8381
WKTParser::WKTGuessedDialect
8382
8.97k
WKTParser::guessDialect(const std::string &inputWkt) noexcept {
8383
8384
    // cppcheck complains (rightly) that the method could be static
8385
8.97k
    (void)this;
8386
8387
8.97k
    std::string wkt = inputWkt;
8388
8.97k
    std::size_t idxFirstCharNotSpace = wkt.find_first_not_of(" \t\r\n");
8389
8.97k
    if (idxFirstCharNotSpace > 0 && idxFirstCharNotSpace != std::string::npos) {
8390
0
        wkt = wkt.substr(idxFirstCharNotSpace);
8391
0
    }
8392
8.97k
    if (ci_starts_with(wkt, WKTConstants::VERTCS)) {
8393
234
        return WKTGuessedDialect::WKT1_ESRI;
8394
234
    }
8395
8.74k
    const std::string *const wkt1_keywords[] = {
8396
8.74k
        &WKTConstants::GEOCCS, &WKTConstants::GEOGCS,  &WKTConstants::COMPD_CS,
8397
8.74k
        &WKTConstants::PROJCS, &WKTConstants::VERT_CS, &WKTConstants::LOCAL_CS};
8398
47.4k
    for (const auto &pointerKeyword : wkt1_keywords) {
8399
47.4k
        if (ci_starts_with(wkt, *pointerKeyword)) {
8400
8401
3.18k
            if ((ci_find(wkt, "GEOGCS[\"GCS_") != std::string::npos ||
8402
3.11k
                 (!ci_starts_with(wkt, WKTConstants::LOCAL_CS) &&
8403
1.76k
                  ci_find(wkt, "AXIS[") == std::string::npos &&
8404
1.75k
                  ci_find(wkt, "AUTHORITY[") == std::string::npos)) &&
8405
                // WKT1:GDAL and WKT1:ESRI have both a
8406
                // Hotine_Oblique_Mercator_Azimuth_Center If providing a
8407
                // WKT1:GDAL without AXIS, we may wrongly detect it as WKT1:ESRI
8408
                // and skip the rectified_grid_angle parameter cf
8409
                // https://github.com/OSGeo/PROJ/issues/3279
8410
1.79k
                ci_find(wkt, "PARAMETER[\"rectified_grid_angle") ==
8411
1.79k
                    std::string::npos) {
8412
1.79k
                return WKTGuessedDialect::WKT1_ESRI;
8413
1.79k
            }
8414
8415
1.38k
            return WKTGuessedDialect::WKT1_GDAL;
8416
3.18k
        }
8417
47.4k
    }
8418
8419
5.56k
    const std::string *const wkt2_2019_only_keywords[] = {
8420
5.56k
        &WKTConstants::GEOGCRS,
8421
        // contained in previous one
8422
        // &WKTConstants::BASEGEOGCRS,
8423
5.56k
        &WKTConstants::CONCATENATEDOPERATION, &WKTConstants::USAGE,
8424
5.56k
        &WKTConstants::DYNAMIC, &WKTConstants::FRAMEEPOCH, &WKTConstants::MODEL,
8425
5.56k
        &WKTConstants::VELOCITYGRID, &WKTConstants::ENSEMBLE,
8426
5.56k
        &WKTConstants::DERIVEDPROJCRS, &WKTConstants::BASEPROJCRS,
8427
5.56k
        &WKTConstants::GEOGRAPHICCRS, &WKTConstants::TRF, &WKTConstants::VRF,
8428
5.56k
        &WKTConstants::POINTMOTIONOPERATION};
8429
8430
76.8k
    for (const auto &pointerKeyword : wkt2_2019_only_keywords) {
8431
76.8k
        auto pos = ci_find(wkt, *pointerKeyword);
8432
76.8k
        if (pos != std::string::npos &&
8433
448
            wkt[pos + pointerKeyword->size()] == '[') {
8434
163
            return WKTGuessedDialect::WKT2_2019;
8435
163
        }
8436
76.8k
    }
8437
5.39k
    static const char *const wkt2_2019_only_substrings[] = {
8438
5.39k
        "CS[TemporalDateTime,",
8439
5.39k
        "CS[TemporalCount,",
8440
5.39k
        "CS[TemporalMeasure,",
8441
5.39k
    };
8442
16.1k
    for (const auto &substrings : wkt2_2019_only_substrings) {
8443
16.1k
        if (ci_find(wkt, substrings) != std::string::npos) {
8444
70
            return WKTGuessedDialect::WKT2_2019;
8445
70
        }
8446
16.1k
    }
8447
8448
215k
    for (const auto &wktConstant : WKTConstants::constants()) {
8449
215k
        if (ci_starts_with(wkt, wktConstant)) {
8450
5.32k
            for (auto wktPtr = wkt.c_str() + wktConstant.size();
8451
6.70k
                 *wktPtr != '\0'; ++wktPtr) {
8452
6.70k
                if (isspace(static_cast<unsigned char>(*wktPtr)))
8453
1.37k
                    continue;
8454
5.32k
                if (*wktPtr == '[') {
8455
5.32k
                    return WKTGuessedDialect::WKT2_2015;
8456
5.32k
                }
8457
0
                break;
8458
5.32k
            }
8459
5.32k
        }
8460
215k
    }
8461
8462
0
    return WKTGuessedDialect::NOT_WKT;
8463
5.32k
}
8464
8465
// ---------------------------------------------------------------------------
8466
8467
//! @cond Doxygen_Suppress
8468
FormattingException::FormattingException(const char *message)
8469
153
    : Exception(message) {}
8470
8471
// ---------------------------------------------------------------------------
8472
8473
FormattingException::FormattingException(const std::string &message)
8474
856
    : Exception(message) {}
8475
8476
// ---------------------------------------------------------------------------
8477
8478
0
FormattingException::FormattingException(const FormattingException &) = default;
8479
8480
// ---------------------------------------------------------------------------
8481
8482
1.00k
FormattingException::~FormattingException() = default;
8483
8484
// ---------------------------------------------------------------------------
8485
8486
5
void FormattingException::Throw(const char *msg) {
8487
5
    throw FormattingException(msg);
8488
5
}
8489
8490
// ---------------------------------------------------------------------------
8491
8492
0
void FormattingException::Throw(const std::string &msg) {
8493
0
    throw FormattingException(msg);
8494
0
}
8495
8496
// ---------------------------------------------------------------------------
8497
8498
3.42k
ParsingException::ParsingException(const char *message) : Exception(message) {}
8499
8500
// ---------------------------------------------------------------------------
8501
8502
ParsingException::ParsingException(const std::string &message)
8503
3.77k
    : Exception(message) {}
8504
8505
// ---------------------------------------------------------------------------
8506
8507
0
ParsingException::ParsingException(const ParsingException &) = default;
8508
8509
// ---------------------------------------------------------------------------
8510
8511
7.19k
ParsingException::~ParsingException() = default;
8512
8513
// ---------------------------------------------------------------------------
8514
8515
206k
IPROJStringExportable::~IPROJStringExportable() = default;
8516
8517
// ---------------------------------------------------------------------------
8518
8519
std::string IPROJStringExportable::exportToPROJString(
8520
21.8k
    PROJStringFormatter *formatter) const {
8521
21.8k
    const bool bIsCRS = dynamic_cast<const crs::CRS *>(this) != nullptr;
8522
21.8k
    if (bIsCRS) {
8523
10.4k
        formatter->setCRSExport(true);
8524
10.4k
    }
8525
21.8k
    _exportToPROJString(formatter);
8526
21.8k
    if (formatter->getAddNoDefs() && bIsCRS) {
8527
9.01k
        if (!formatter->hasParam("no_defs")) {
8528
8.43k
            formatter->addParam("no_defs");
8529
8.43k
        }
8530
9.01k
    }
8531
21.8k
    if (bIsCRS) {
8532
10.4k
        if (!formatter->hasParam("type")) {
8533
10.4k
            formatter->addParam("type", "crs");
8534
10.4k
        }
8535
10.4k
        formatter->setCRSExport(false);
8536
10.4k
    }
8537
21.8k
    return formatter->toString();
8538
21.8k
}
8539
//! @endcond
8540
8541
// ---------------------------------------------------------------------------
8542
8543
//! @cond Doxygen_Suppress
8544
8545
struct Step {
8546
    std::string name{};
8547
    bool isInit = false;
8548
    bool inverted{false};
8549
8550
    struct KeyValue {
8551
        std::string key{};
8552
        std::string value{};
8553
        bool usedByParser = false; // only for PROJStringParser used
8554
8555
416k
        explicit KeyValue(const std::string &keyIn) : key(keyIn) {}
8556
8557
        KeyValue(const char *keyIn, const std::string &valueIn);
8558
8559
        KeyValue(const std::string &keyIn, const std::string &valueIn)
8560
389k
            : key(keyIn), value(valueIn) {}
8561
8562
        // cppcheck-suppress functionStatic
8563
162k
        bool keyEquals(const char *otherKey) const noexcept {
8564
162k
            return key == otherKey;
8565
162k
        }
8566
8567
        // cppcheck-suppress functionStatic
8568
11.8k
        bool equals(const char *otherKey, const char *otherVal) const noexcept {
8569
11.8k
            return key == otherKey && value == otherVal;
8570
11.8k
        }
8571
8572
66
        bool operator==(const KeyValue &other) const noexcept {
8573
66
            return key == other.key && value == other.value;
8574
66
        }
8575
8576
18.5k
        bool operator!=(const KeyValue &other) const noexcept {
8577
18.5k
            return key != other.key || value != other.value;
8578
18.5k
        }
8579
    };
8580
8581
    std::vector<KeyValue> paramValues{};
8582
8583
32.3k
    bool hasKey(const char *keyName) const {
8584
249k
        for (const auto &kv : paramValues) {
8585
249k
            if (kv.key == keyName) {
8586
74
                return true;
8587
74
            }
8588
249k
        }
8589
32.2k
        return false;
8590
32.3k
    }
8591
};
8592
8593
Step::KeyValue::KeyValue(const char *keyIn, const std::string &valueIn)
8594
3.31k
    : key(keyIn), value(valueIn) {}
8595
8596
struct PROJStringFormatter::Private {
8597
    PROJStringFormatter::Convention convention_ =
8598
        PROJStringFormatter::Convention::PROJ_5;
8599
    std::vector<double> toWGS84Parameters_{};
8600
    std::string vDatumExtension_{};
8601
    std::string geoidCRSValue_{};
8602
    std::string hDatumExtension_{};
8603
    crs::GeographicCRSPtr geogCRSOfCompoundCRS_{};
8604
8605
    std::list<Step> steps_{};
8606
    std::vector<Step::KeyValue> globalParamValues_{};
8607
8608
    struct InversionStackElt {
8609
        std::list<Step>::iterator startIter{};
8610
        bool iterValid = false;
8611
        bool currentInversionState = false;
8612
    };
8613
    std::vector<InversionStackElt> inversionStack_{InversionStackElt()};
8614
    bool omitProjLongLatIfPossible_ = false;
8615
    std::vector<bool> omitZUnitConversion_{false};
8616
    std::vector<bool> omitHorizontalConversionInVertTransformation_{false};
8617
    DatabaseContextPtr dbContext_{};
8618
    bool useApproxTMerc_ = false;
8619
    bool addNoDefs_ = true;
8620
    bool coordOperationOptimizations_ = false;
8621
    bool crsExport_ = false;
8622
    bool legacyCRSToCRSContext_ = false;
8623
    bool multiLine_ = false;
8624
    bool normalizeOutput_ = false;
8625
    int indentWidth_ = 2;
8626
    int indentLevel_ = 0;
8627
    int maxLineLength_ = 80;
8628
8629
    std::string result_{};
8630
8631
    // cppcheck-suppress functionStatic
8632
    void appendToResult(const char *str);
8633
8634
    // cppcheck-suppress functionStatic
8635
    void addStep();
8636
};
8637
8638
//! @endcond
8639
8640
// ---------------------------------------------------------------------------
8641
8642
//! @cond Doxygen_Suppress
8643
PROJStringFormatter::PROJStringFormatter(Convention conventionIn,
8644
                                         const DatabaseContextPtr &dbContext)
8645
32.6k
    : d(std::make_unique<Private>()) {
8646
32.6k
    d->convention_ = conventionIn;
8647
32.6k
    d->dbContext_ = dbContext;
8648
32.6k
}
8649
//! @endcond
8650
8651
// ---------------------------------------------------------------------------
8652
8653
//! @cond Doxygen_Suppress
8654
32.6k
PROJStringFormatter::~PROJStringFormatter() = default;
8655
//! @endcond
8656
8657
// ---------------------------------------------------------------------------
8658
8659
/** \brief Constructs a new formatter.
8660
 *
8661
 * A formatter can be used only once (its internal state is mutated)
8662
 *
8663
 * Its default behavior can be adjusted with the different setters.
8664
 *
8665
 * @param conventionIn PROJ string flavor. Defaults to Convention::PROJ_5
8666
 * @param dbContext Database context (can help to find alternative grid names).
8667
 * May be nullptr
8668
 * @return new formatter.
8669
 */
8670
PROJStringFormatterNNPtr
8671
PROJStringFormatter::create(Convention conventionIn,
8672
32.6k
                            DatabaseContextPtr dbContext) {
8673
32.6k
    return NN_NO_CHECK(PROJStringFormatter::make_unique<PROJStringFormatter>(
8674
32.6k
        conventionIn, dbContext));
8675
32.6k
}
8676
8677
// ---------------------------------------------------------------------------
8678
8679
/** \brief Set whether approximate Transverse Mercator or UTM should be used */
8680
0
void PROJStringFormatter::setUseApproxTMerc(bool flag) {
8681
0
    d->useApproxTMerc_ = flag;
8682
0
}
8683
8684
// ---------------------------------------------------------------------------
8685
8686
/** \brief Whether to use multi line output or not. */
8687
PROJStringFormatter &
8688
0
PROJStringFormatter::setMultiLine(bool multiLine) noexcept {
8689
0
    d->multiLine_ = multiLine;
8690
0
    return *this;
8691
0
}
8692
8693
// ---------------------------------------------------------------------------
8694
8695
/** \brief Set number of spaces for each indentation level (defaults to 2).
8696
 */
8697
PROJStringFormatter &
8698
0
PROJStringFormatter::setIndentationWidth(int width) noexcept {
8699
0
    d->indentWidth_ = width;
8700
0
    return *this;
8701
0
}
8702
8703
// ---------------------------------------------------------------------------
8704
8705
/** \brief Set the maximum size of a line (when multiline output is enable).
8706
 * Can be set to 0 for unlimited length.
8707
 */
8708
PROJStringFormatter &
8709
0
PROJStringFormatter::setMaxLineLength(int maxLineLength) noexcept {
8710
0
    d->maxLineLength_ = maxLineLength;
8711
0
    return *this;
8712
0
}
8713
8714
// ---------------------------------------------------------------------------
8715
8716
/** \brief Returns the PROJ string. */
8717
20.8k
const std::string &PROJStringFormatter::toString() const {
8718
8719
20.8k
    assert(d->inversionStack_.size() == 1);
8720
8721
20.8k
    d->result_.clear();
8722
8723
20.8k
    auto &steps = d->steps_;
8724
8725
20.8k
    if (d->normalizeOutput_) {
8726
        // Sort +key=value options of each step in lexicographic order.
8727
0
        for (auto &step : steps) {
8728
0
            std::sort(step.paramValues.begin(), step.paramValues.end(),
8729
0
                      [](const Step::KeyValue &a, const Step::KeyValue &b) {
8730
0
                          return a.key < b.key;
8731
0
                      });
8732
0
        }
8733
0
    }
8734
8735
76.7k
    for (auto iter = steps.begin(); iter != steps.end();) {
8736
        // Remove no-op helmert
8737
55.9k
        auto &step = *iter;
8738
55.9k
        const auto paramCount = step.paramValues.size();
8739
55.9k
        if (step.name == "helmert" && (paramCount == 3 || paramCount == 8) &&
8740
640
            step.paramValues[0].equals("x", "0") &&
8741
112
            step.paramValues[1].equals("y", "0") &&
8742
89
            step.paramValues[2].equals("z", "0") &&
8743
82
            (paramCount == 3 ||
8744
37
             (step.paramValues[3].equals("rx", "0") &&
8745
37
              step.paramValues[4].equals("ry", "0") &&
8746
37
              step.paramValues[5].equals("rz", "0") &&
8747
37
              step.paramValues[6].equals("s", "0") &&
8748
82
              step.paramValues[7].keyEquals("convention")))) {
8749
82
            iter = steps.erase(iter);
8750
55.8k
        } else if (d->coordOperationOptimizations_ &&
8751
857
                   step.name == "unitconvert" && paramCount == 2 &&
8752
256
                   step.paramValues[0].keyEquals("xy_in") &&
8753
256
                   step.paramValues[1].keyEquals("xy_out") &&
8754
256
                   step.paramValues[0].value == step.paramValues[1].value) {
8755
0
            iter = steps.erase(iter);
8756
55.8k
        } else if (step.name == "push" && step.inverted) {
8757
340
            step.name = "pop";
8758
340
            step.inverted = false;
8759
340
            ++iter;
8760
55.4k
        } else if (step.name == "pop" && step.inverted) {
8761
204
            step.name = "push";
8762
204
            step.inverted = false;
8763
204
            ++iter;
8764
55.2k
        } else if (step.name == "noop" && steps.size() > 1) {
8765
0
            iter = steps.erase(iter);
8766
55.2k
        } else {
8767
55.2k
            ++iter;
8768
55.2k
        }
8769
55.9k
    }
8770
8771
55.8k
    for (auto &step : steps) {
8772
55.8k
        if (!step.inverted) {
8773
37.4k
            continue;
8774
37.4k
        }
8775
8776
18.3k
        const auto paramCount = step.paramValues.size();
8777
8778
        // axisswap order=2,1 (or 1,-2) is its own inverse
8779
18.3k
        if (step.name == "axisswap" && paramCount == 1 &&
8780
816
            (step.paramValues[0].equals("order", "2,1") ||
8781
482
             step.paramValues[0].equals("order", "1,-2"))) {
8782
482
            step.inverted = false;
8783
482
            continue;
8784
482
        }
8785
8786
        // axisswap inv order=2,-1 ==> axisswap order -2,1
8787
17.8k
        if (step.name == "axisswap" && paramCount == 1 &&
8788
334
            step.paramValues[0].equals("order", "2,-1")) {
8789
79
            step.inverted = false;
8790
79
            step.paramValues[0] = Step::KeyValue("order", "-2,1");
8791
79
            continue;
8792
79
        }
8793
8794
        // axisswap order=1,2,-3 is its own inverse
8795
17.7k
        if (step.name == "axisswap" && paramCount == 1 &&
8796
255
            step.paramValues[0].equals("order", "1,2,-3")) {
8797
0
            step.inverted = false;
8798
0
            continue;
8799
0
        }
8800
8801
        // handle unitconvert inverse
8802
17.7k
        if (step.name == "unitconvert" && paramCount == 2 &&
8803
686
            step.paramValues[0].keyEquals("xy_in") &&
8804
380
            step.paramValues[1].keyEquals("xy_out")) {
8805
344
            std::swap(step.paramValues[0].value, step.paramValues[1].value);
8806
344
            step.inverted = false;
8807
344
            continue;
8808
344
        }
8809
8810
17.4k
        if (step.name == "unitconvert" && paramCount == 2 &&
8811
342
            step.paramValues[0].keyEquals("z_in") &&
8812
193
            step.paramValues[1].keyEquals("z_out")) {
8813
140
            std::swap(step.paramValues[0].value, step.paramValues[1].value);
8814
140
            step.inverted = false;
8815
140
            continue;
8816
140
        }
8817
8818
17.3k
        if (step.name == "unitconvert" && paramCount == 4 &&
8819
174
            step.paramValues[0].keyEquals("xy_in") &&
8820
97
            step.paramValues[1].keyEquals("z_in") &&
8821
39
            step.paramValues[2].keyEquals("xy_out") &&
8822
13
            step.paramValues[3].keyEquals("z_out")) {
8823
0
            std::swap(step.paramValues[0].value, step.paramValues[2].value);
8824
0
            std::swap(step.paramValues[1].value, step.paramValues[3].value);
8825
0
            step.inverted = false;
8826
0
            continue;
8827
0
        }
8828
17.3k
    }
8829
8830
20.8k
    {
8831
20.8k
        auto iterCur = steps.begin();
8832
20.8k
        if (iterCur != steps.end()) {
8833
20.3k
            ++iterCur;
8834
20.3k
        }
8835
64.4k
        while (iterCur != steps.end()) {
8836
8837
43.6k
            assert(iterCur != steps.begin());
8838
43.6k
            auto iterPrev = std::prev(iterCur);
8839
43.6k
            auto &prevStep = *iterPrev;
8840
43.6k
            auto &curStep = *iterCur;
8841
8842
43.6k
            const auto curStepParamCount = curStep.paramValues.size();
8843
43.6k
            const auto prevStepParamCount = prevStep.paramValues.size();
8844
8845
43.6k
            const auto deletePrevAndCurIter = [&steps, &iterPrev, &iterCur]() {
8846
9.75k
                iterCur = steps.erase(iterPrev, std::next(iterCur));
8847
9.75k
                if (iterCur != steps.begin())
8848
9.37k
                    iterCur = std::prev(iterCur);
8849
9.75k
                if (iterCur == steps.begin() && iterCur != steps.end())
8850
1.05k
                    ++iterCur;
8851
9.75k
            };
8852
8853
            // longlat (or its inverse) with ellipsoid only is a no-op
8854
            // do that only for an internal step
8855
43.6k
            if (std::next(iterCur) != steps.end() &&
8856
40.7k
                curStep.name == "longlat" && curStepParamCount == 1 &&
8857
37
                curStep.paramValues[0].keyEquals("ellps")) {
8858
16
                iterCur = steps.erase(iterCur);
8859
16
                continue;
8860
16
            }
8861
8862
            // push v_x followed by pop v_x is a no-op.
8863
43.6k
            if (curStep.name == "pop" && prevStep.name == "push" &&
8864
257
                !curStep.inverted && !prevStep.inverted &&
8865
257
                curStepParamCount == 1 && prevStepParamCount == 1 &&
8866
123
                curStep.paramValues[0].key == prevStep.paramValues[0].key) {
8867
32
                deletePrevAndCurIter();
8868
32
                continue;
8869
32
            }
8870
8871
            // pop v_x followed by push v_x is, almost, a no-op. For our
8872
            // purposes,
8873
            // we consider it as a no-op for better pipeline optimizations.
8874
43.6k
            if (curStep.name == "push" && prevStep.name == "pop" &&
8875
222
                !curStep.inverted && !prevStep.inverted &&
8876
222
                curStepParamCount == 1 && prevStepParamCount == 1 &&
8877
142
                curStep.paramValues[0].key == prevStep.paramValues[0].key) {
8878
65
                deletePrevAndCurIter();
8879
65
                continue;
8880
65
            }
8881
8882
            // unitconvert (xy) followed by its inverse is a no-op
8883
43.5k
            if (curStep.name == "unitconvert" &&
8884
5.75k
                prevStep.name == "unitconvert" && !curStep.inverted &&
8885
3.26k
                !prevStep.inverted && curStepParamCount == 2 &&
8886
1.83k
                prevStepParamCount == 2 &&
8887
1.27k
                curStep.paramValues[0].keyEquals("xy_in") &&
8888
570
                prevStep.paramValues[0].keyEquals("xy_in") &&
8889
162
                curStep.paramValues[1].keyEquals("xy_out") &&
8890
116
                prevStep.paramValues[1].keyEquals("xy_out") &&
8891
70
                curStep.paramValues[0].value == prevStep.paramValues[1].value &&
8892
14
                curStep.paramValues[1].value == prevStep.paramValues[0].value) {
8893
8
                deletePrevAndCurIter();
8894
8
                continue;
8895
8
            }
8896
8897
            // unitconvert (z) followed by its inverse is a no-op
8898
43.5k
            if (curStep.name == "unitconvert" &&
8899
5.75k
                prevStep.name == "unitconvert" && !curStep.inverted &&
8900
3.26k
                !prevStep.inverted && curStepParamCount == 2 &&
8901
1.82k
                prevStepParamCount == 2 &&
8902
1.26k
                curStep.paramValues[0].keyEquals("z_in") &&
8903
385
                prevStep.paramValues[0].keyEquals("z_in") &&
8904
68
                curStep.paramValues[1].keyEquals("z_out") &&
8905
37
                prevStep.paramValues[1].keyEquals("z_out") &&
8906
25
                curStep.paramValues[0].value == prevStep.paramValues[1].value &&
8907
16
                curStep.paramValues[1].value == prevStep.paramValues[0].value) {
8908
10
                deletePrevAndCurIter();
8909
10
                continue;
8910
10
            }
8911
8912
            // unitconvert (xyz) followed by its inverse is a no-op
8913
43.5k
            if (curStep.name == "unitconvert" &&
8914
5.74k
                prevStep.name == "unitconvert" && !curStep.inverted &&
8915
3.25k
                !prevStep.inverted && curStepParamCount == 4 &&
8916
1.19k
                prevStepParamCount == 4 &&
8917
793
                curStep.paramValues[0].keyEquals("xy_in") &&
8918
586
                prevStep.paramValues[0].keyEquals("xy_in") &&
8919
543
                curStep.paramValues[1].keyEquals("z_in") &&
8920
115
                prevStep.paramValues[1].keyEquals("z_in") &&
8921
106
                curStep.paramValues[2].keyEquals("xy_out") &&
8922
97
                prevStep.paramValues[2].keyEquals("xy_out") &&
8923
92
                curStep.paramValues[3].keyEquals("z_out") &&
8924
92
                prevStep.paramValues[3].keyEquals("z_out") &&
8925
92
                curStep.paramValues[0].value == prevStep.paramValues[2].value &&
8926
54
                curStep.paramValues[1].value == prevStep.paramValues[3].value &&
8927
0
                curStep.paramValues[2].value == prevStep.paramValues[0].value &&
8928
0
                curStep.paramValues[3].value == prevStep.paramValues[1].value) {
8929
0
                deletePrevAndCurIter();
8930
0
                continue;
8931
0
            }
8932
8933
43.5k
            const auto deletePrevIter = [&steps, &iterPrev, &iterCur]() {
8934
691
                steps.erase(iterPrev, iterCur);
8935
691
                if (iterCur != steps.begin())
8936
542
                    iterCur = std::prev(iterCur);
8937
691
                if (iterCur == steps.begin())
8938
453
                    ++iterCur;
8939
691
            };
8940
8941
            // combine unitconvert (xy) and unitconvert (z)
8942
43.5k
            bool changeDone = false;
8943
129k
            for (int k = 0; k < 2; ++k) {
8944
86.8k
                auto &first = (k == 0) ? curStep : prevStep;
8945
86.8k
                auto &second = (k == 0) ? prevStep : curStep;
8946
86.8k
                if (first.name == "unitconvert" &&
8947
12.9k
                    second.name == "unitconvert" && !first.inverted &&
8948
6.22k
                    !second.inverted && first.paramValues.size() == 2 &&
8949
3.26k
                    second.paramValues.size() == 2 &&
8950
2.29k
                    second.paramValues[0].keyEquals("xy_in") &&
8951
1.11k
                    second.paramValues[1].keyEquals("xy_out") &&
8952
832
                    first.paramValues[0].keyEquals("z_in") &&
8953
494
                    first.paramValues[1].keyEquals("z_out")) {
8954
8955
418
                    const std::string xy_in(second.paramValues[0].value);
8956
418
                    const std::string xy_out(second.paramValues[1].value);
8957
418
                    const std::string z_in(first.paramValues[0].value);
8958
418
                    const std::string z_out(first.paramValues[1].value);
8959
8960
418
                    iterCur->paramValues.clear();
8961
418
                    iterCur->paramValues.emplace_back(
8962
418
                        Step::KeyValue("xy_in", xy_in));
8963
418
                    iterCur->paramValues.emplace_back(
8964
418
                        Step::KeyValue("z_in", z_in));
8965
418
                    iterCur->paramValues.emplace_back(
8966
418
                        Step::KeyValue("xy_out", xy_out));
8967
418
                    iterCur->paramValues.emplace_back(
8968
418
                        Step::KeyValue("z_out", z_out));
8969
8970
418
                    deletePrevIter();
8971
418
                    changeDone = true;
8972
418
                    break;
8973
418
                }
8974
86.8k
            }
8975
43.5k
            if (changeDone) {
8976
418
                continue;
8977
418
            }
8978
8979
            // +step +proj=unitconvert +xy_in=X1 +xy_out=X2
8980
            //  +step +proj=unitconvert +xy_in=X2 +z_in=Z1 +xy_out=X1 +z_out=Z2
8981
            // ==> step +proj=unitconvert +z_in=Z1 +z_out=Z2
8982
129k
            for (int k = 0; k < 2; ++k) {
8983
86.2k
                auto &first = (k == 0) ? curStep : prevStep;
8984
86.2k
                auto &second = (k == 0) ? prevStep : curStep;
8985
86.2k
                if (first.name == "unitconvert" &&
8986
12.3k
                    second.name == "unitconvert" && !first.inverted &&
8987
5.60k
                    !second.inverted && first.paramValues.size() == 4 &&
8988
2.54k
                    second.paramValues.size() == 2 &&
8989
863
                    first.paramValues[0].keyEquals("xy_in") &&
8990
637
                    first.paramValues[1].keyEquals("z_in") &&
8991
448
                    first.paramValues[2].keyEquals("xy_out") &&
8992
423
                    first.paramValues[3].keyEquals("z_out") &&
8993
420
                    second.paramValues[0].keyEquals("xy_in") &&
8994
163
                    second.paramValues[1].keyEquals("xy_out") &&
8995
124
                    first.paramValues[0].value == second.paramValues[1].value &&
8996
55
                    first.paramValues[2].value == second.paramValues[0].value) {
8997
24
                    const std::string z_in(first.paramValues[1].value);
8998
24
                    const std::string z_out(first.paramValues[3].value);
8999
24
                    if (z_in != z_out) {
9000
24
                        iterCur->paramValues.clear();
9001
24
                        iterCur->paramValues.emplace_back(
9002
24
                            Step::KeyValue("z_in", z_in));
9003
24
                        iterCur->paramValues.emplace_back(
9004
24
                            Step::KeyValue("z_out", z_out));
9005
24
                        deletePrevIter();
9006
24
                    } else {
9007
0
                        deletePrevAndCurIter();
9008
0
                    }
9009
24
                    changeDone = true;
9010
24
                    break;
9011
24
                }
9012
86.2k
            }
9013
43.1k
            if (changeDone) {
9014
24
                continue;
9015
24
            }
9016
9017
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2
9018
            // +step +proj=unitconvert +z_in=Z2 +z_out=Z3
9019
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2
9020
            // +z_out=Z3
9021
43.0k
            if (prevStep.name == "unitconvert" &&
9022
6.95k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9023
2.74k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9024
1.32k
                curStep.paramValues.size() == 2 &&
9025
489
                prevStep.paramValues[0].keyEquals("xy_in") &&
9026
357
                prevStep.paramValues[1].keyEquals("z_in") &&
9027
310
                prevStep.paramValues[2].keyEquals("xy_out") &&
9028
289
                prevStep.paramValues[3].keyEquals("z_out") &&
9029
286
                curStep.paramValues[0].keyEquals("z_in") &&
9030
149
                curStep.paramValues[1].keyEquals("z_out") &&
9031
113
                prevStep.paramValues[3].value == curStep.paramValues[0].value) {
9032
55
                const std::string xy_in(prevStep.paramValues[0].value);
9033
55
                const std::string z_in(prevStep.paramValues[1].value);
9034
55
                const std::string xy_out(prevStep.paramValues[2].value);
9035
55
                const std::string z_out(curStep.paramValues[1].value);
9036
9037
55
                iterCur->paramValues.clear();
9038
55
                iterCur->paramValues.emplace_back(
9039
55
                    Step::KeyValue("xy_in", xy_in));
9040
55
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9041
55
                iterCur->paramValues.emplace_back(
9042
55
                    Step::KeyValue("xy_out", xy_out));
9043
55
                iterCur->paramValues.emplace_back(
9044
55
                    Step::KeyValue("z_out", z_out));
9045
9046
55
                deletePrevIter();
9047
55
                continue;
9048
55
            }
9049
9050
            // +step +proj=unitconvert +z_in=Z1 +z_out=Z2
9051
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z2 +xy_out=X2 +z_out=Z3
9052
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2
9053
            // +z_out=Z3
9054
43.0k
            if (prevStep.name == "unitconvert" &&
9055
6.89k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9056
2.69k
                !curStep.inverted && prevStep.paramValues.size() == 2 &&
9057
1.24k
                curStep.paramValues.size() == 4 &&
9058
350
                prevStep.paramValues[0].keyEquals("z_in") &&
9059
148
                prevStep.paramValues[1].keyEquals("z_out") &&
9060
98
                curStep.paramValues[0].keyEquals("xy_in") &&
9061
85
                curStep.paramValues[1].keyEquals("z_in") &&
9062
6
                curStep.paramValues[2].keyEquals("xy_out") &&
9063
2
                curStep.paramValues[3].keyEquals("z_out") &&
9064
2
                prevStep.paramValues[1].value == curStep.paramValues[1].value) {
9065
2
                const std::string xy_in(curStep.paramValues[0].value);
9066
2
                const std::string z_in(prevStep.paramValues[0].value);
9067
2
                const std::string xy_out(curStep.paramValues[2].value);
9068
2
                const std::string z_out(curStep.paramValues[3].value);
9069
9070
2
                iterCur->paramValues.clear();
9071
2
                iterCur->paramValues.emplace_back(
9072
2
                    Step::KeyValue("xy_in", xy_in));
9073
2
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9074
2
                iterCur->paramValues.emplace_back(
9075
2
                    Step::KeyValue("xy_out", xy_out));
9076
2
                iterCur->paramValues.emplace_back(
9077
2
                    Step::KeyValue("z_out", z_out));
9078
9079
2
                deletePrevIter();
9080
2
                continue;
9081
2
            }
9082
9083
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2
9084
            // +step +proj=unitconvert +xy_in=X2 +xy_out=X3
9085
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X3
9086
            // +z_out=Z2
9087
43.0k
            if (prevStep.name == "unitconvert" &&
9088
6.89k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9089
2.69k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9090
1.27k
                curStep.paramValues.size() == 2 &&
9091
434
                prevStep.paramValues[0].keyEquals("xy_in") &&
9092
302
                prevStep.paramValues[1].keyEquals("z_in") &&
9093
255
                prevStep.paramValues[2].keyEquals("xy_out") &&
9094
234
                prevStep.paramValues[3].keyEquals("z_out") &&
9095
231
                curStep.paramValues[0].keyEquals("xy_in") &&
9096
121
                curStep.paramValues[1].keyEquals("xy_out") &&
9097
100
                prevStep.paramValues[2].value == curStep.paramValues[0].value) {
9098
65
                const std::string xy_in(prevStep.paramValues[0].value);
9099
65
                const std::string z_in(prevStep.paramValues[1].value);
9100
65
                const std::string xy_out(curStep.paramValues[1].value);
9101
65
                const std::string z_out(prevStep.paramValues[3].value);
9102
9103
65
                iterCur->paramValues.clear();
9104
65
                iterCur->paramValues.emplace_back(
9105
65
                    Step::KeyValue("xy_in", xy_in));
9106
65
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9107
65
                iterCur->paramValues.emplace_back(
9108
65
                    Step::KeyValue("xy_out", xy_out));
9109
65
                iterCur->paramValues.emplace_back(
9110
65
                    Step::KeyValue("z_out", z_out));
9111
9112
65
                deletePrevIter();
9113
65
                continue;
9114
65
            }
9115
9116
            // clang-format off
9117
            // A bit odd. Used to simplify geog3d_feet -> EPSG:6318+6360
9118
            // of https://github.com/OSGeo/PROJ/issues/3938
9119
            // where we get originally
9120
            // +step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=rad +z_out=us-ft
9121
            // +step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m
9122
            // and want it simplified as:
9123
            // +step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=deg +z_out=us-ft
9124
            //
9125
            // More generally:
9126
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2
9127
            // +step +proj=unitconvert +xy_in=X2 +z_in=Z3 +xy_out=X3 +z_out=Z3
9128
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X3 +z_out=Z2
9129
            // clang-format on
9130
42.9k
            if (prevStep.name == "unitconvert" &&
9131
6.83k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9132
2.62k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9133
1.20k
                curStep.paramValues.size() == 4 &&
9134
793
                prevStep.paramValues[0].keyEquals("xy_in") &&
9135
698
                prevStep.paramValues[1].keyEquals("z_in") &&
9136
575
                prevStep.paramValues[2].keyEquals("xy_out") &&
9137
566
                prevStep.paramValues[3].keyEquals("z_out") &&
9138
561
                curStep.paramValues[0].keyEquals("xy_in") &&
9139
524
                curStep.paramValues[1].keyEquals("z_in") &&
9140
100
                curStep.paramValues[2].keyEquals("xy_out") &&
9141
92
                curStep.paramValues[3].keyEquals("z_out") &&
9142
92
                prevStep.paramValues[2].value == curStep.paramValues[0].value &&
9143
54
                curStep.paramValues[1].value == curStep.paramValues[3].value) {
9144
14
                const std::string xy_in(prevStep.paramValues[0].value);
9145
14
                const std::string z_in(prevStep.paramValues[1].value);
9146
14
                const std::string xy_out(curStep.paramValues[2].value);
9147
14
                const std::string z_out(prevStep.paramValues[3].value);
9148
9149
14
                iterCur->paramValues.clear();
9150
14
                iterCur->paramValues.emplace_back(
9151
14
                    Step::KeyValue("xy_in", xy_in));
9152
14
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9153
14
                iterCur->paramValues.emplace_back(
9154
14
                    Step::KeyValue("xy_out", xy_out));
9155
14
                iterCur->paramValues.emplace_back(
9156
14
                    Step::KeyValue("z_out", z_out));
9157
9158
14
                deletePrevIter();
9159
14
                continue;
9160
14
            }
9161
9162
            // clang-format off
9163
            // Variant of above
9164
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z1
9165
            // +step +proj=unitconvert +xy_in=X2 +z_in=Z2 +xy_out=X3 +z_out=Z3
9166
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z2 +xy_out=X3 +z_out=Z3
9167
            // clang-format on
9168
42.9k
            if (prevStep.name == "unitconvert" &&
9169
6.81k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9170
2.61k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9171
1.19k
                curStep.paramValues.size() == 4 &&
9172
779
                prevStep.paramValues[0].keyEquals("xy_in") &&
9173
684
                prevStep.paramValues[1].keyEquals("z_in") &&
9174
561
                prevStep.paramValues[2].keyEquals("xy_out") &&
9175
552
                prevStep.paramValues[3].keyEquals("z_out") &&
9176
547
                curStep.paramValues[0].keyEquals("xy_in") &&
9177
510
                curStep.paramValues[1].keyEquals("z_in") &&
9178
86
                curStep.paramValues[2].keyEquals("xy_out") &&
9179
78
                curStep.paramValues[3].keyEquals("z_out") &&
9180
78
                prevStep.paramValues[1].value ==
9181
78
                    prevStep.paramValues[3].value &&
9182
69
                curStep.paramValues[0].value == prevStep.paramValues[2].value) {
9183
36
                const std::string xy_in(prevStep.paramValues[0].value);
9184
36
                const std::string z_in(curStep.paramValues[1].value);
9185
36
                const std::string xy_out(curStep.paramValues[2].value);
9186
36
                const std::string z_out(curStep.paramValues[3].value);
9187
9188
36
                iterCur->paramValues.clear();
9189
36
                iterCur->paramValues.emplace_back(
9190
36
                    Step::KeyValue("xy_in", xy_in));
9191
36
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9192
36
                iterCur->paramValues.emplace_back(
9193
36
                    Step::KeyValue("xy_out", xy_out));
9194
36
                iterCur->paramValues.emplace_back(
9195
36
                    Step::KeyValue("z_out", z_out));
9196
9197
36
                deletePrevIter();
9198
36
                continue;
9199
36
            }
9200
9201
            // unitconvert (1), axisswap order=2,1, unitconvert(2)  ==>
9202
            // axisswap order=2,1, unitconvert (1), unitconvert(2) which
9203
            // will get further optimized by previous case
9204
42.9k
            if (std::next(iterCur) != steps.end() &&
9205
40.1k
                prevStep.name == "unitconvert" && curStep.name == "axisswap" &&
9206
90
                curStepParamCount == 1 &&
9207
78
                curStep.paramValues[0].equals("order", "2,1")) {
9208
59
                auto iterNext = std::next(iterCur);
9209
59
                auto &nextStep = *iterNext;
9210
59
                if (nextStep.name == "unitconvert") {
9211
9
                    std::swap(*iterPrev, *iterCur);
9212
9
                    ++iterCur;
9213
9
                    continue;
9214
9
                }
9215
59
            }
9216
9217
            // axisswap order=2,1 followed by itself is a no-op
9218
42.9k
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9219
941
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9220
464
                curStep.paramValues[0].equals("order", "2,1") &&
9221
137
                prevStep.paramValues[0].equals("order", "2,1")) {
9222
71
                deletePrevAndCurIter();
9223
71
                continue;
9224
71
            }
9225
9226
            // axisswap order=2,-1 followed by axisswap order=-2,1 is a no-op
9227
42.8k
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9228
870
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9229
393
                !prevStep.inverted &&
9230
301
                prevStep.paramValues[0].equals("order", "2,-1") &&
9231
51
                !curStep.inverted &&
9232
49
                curStep.paramValues[0].equals("order", "-2,1")) {
9233
11
                deletePrevAndCurIter();
9234
11
                continue;
9235
11
            }
9236
9237
            // axisswap order=2,-1 followed by axisswap order=1,-2 is
9238
            // equivalent to axisswap order=2,1
9239
42.8k
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9240
859
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9241
382
                !prevStep.inverted &&
9242
290
                prevStep.paramValues[0].equals("order", "2,-1") &&
9243
40
                !curStep.inverted &&
9244
38
                curStep.paramValues[0].equals("order", "1,-2")) {
9245
12
                prevStep.inverted = false;
9246
12
                prevStep.paramValues[0] = Step::KeyValue("order", "2,1");
9247
                // Delete this iter
9248
12
                iterCur = steps.erase(iterCur);
9249
12
                continue;
9250
12
            }
9251
9252
            // axisswap order=2,1 followed by axisswap order=2,-1 is
9253
            // equivalent to axisswap order=1,-2
9254
            // Same for axisswap order=-2,1 followed by axisswap order=2,1
9255
42.8k
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9256
847
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9257
370
                ((prevStep.paramValues[0].equals("order", "2,1") &&
9258
105
                  !curStep.inverted &&
9259
70
                  curStep.paramValues[0].equals("order", "2,-1")) ||
9260
359
                 (prevStep.paramValues[0].equals("order", "-2,1") &&
9261
35
                  !prevStep.inverted &&
9262
35
                  curStep.paramValues[0].equals("order", "2,1")))) {
9263
9264
24
                prevStep.inverted = false;
9265
24
                prevStep.paramValues[0] = Step::KeyValue("order", "1,-2");
9266
                // Delete this iter
9267
24
                iterCur = steps.erase(iterCur);
9268
24
                continue;
9269
24
            }
9270
9271
            // axisswap order=2,1, unitconvert, axisswap order=2,1 -> can
9272
            // suppress axisswap
9273
42.7k
            if (std::next(iterCur) != steps.end() &&
9274
40.0k
                prevStep.name == "axisswap" && curStep.name == "unitconvert" &&
9275
235
                prevStepParamCount == 1 &&
9276
207
                prevStep.paramValues[0].equals("order", "2,1")) {
9277
181
                auto iterNext = std::next(iterCur);
9278
181
                auto &nextStep = *iterNext;
9279
181
                if (nextStep.name == "axisswap" &&
9280
25
                    nextStep.paramValues.size() == 1 &&
9281
20
                    nextStep.paramValues[0].equals("order", "2,1")) {
9282
15
                    steps.erase(iterPrev);
9283
15
                    steps.erase(iterNext);
9284
                    // Coverity complains about invalid usage of iterCur
9285
                    // due to the above erase(iterNext). To the best of our
9286
                    // understanding, this is a false-positive.
9287
                    // coverity[use_iterator]
9288
15
                    if (iterCur != steps.begin())
9289
9
                        iterCur = std::prev(iterCur);
9290
15
                    if (iterCur == steps.begin())
9291
15
                        ++iterCur;
9292
15
                    continue;
9293
15
                }
9294
181
            }
9295
9296
            // for practical purposes WGS84 and GRS80 ellipsoids are
9297
            // equivalents (cartesian transform between both lead to differences
9298
            // of the order of 1e-14 deg..).
9299
            // No need to do a cart roundtrip for that...
9300
            // and actually IGNF uses the GRS80 definition for the WGS84 datum
9301
42.7k
            if (curStep.name == "cart" && prevStep.name == "cart" &&
9302
75
                curStep.inverted == !prevStep.inverted &&
9303
57
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9304
32
                ((curStep.paramValues[0].equals("ellps", "WGS84") &&
9305
2
                  prevStep.paramValues[0].equals("ellps", "GRS80")) ||
9306
32
                 (curStep.paramValues[0].equals("ellps", "GRS80") &&
9307
6
                  prevStep.paramValues[0].equals("ellps", "WGS84")))) {
9308
0
                deletePrevAndCurIter();
9309
0
                continue;
9310
0
            }
9311
9312
42.7k
            if (curStep.name == "helmert" && prevStep.name == "helmert" &&
9313
2.24k
                !curStep.inverted && !prevStep.inverted &&
9314
1.16k
                curStepParamCount == 3 &&
9315
632
                curStepParamCount == prevStepParamCount) {
9316
582
                std::map<std::string, double> leftParamsMap;
9317
582
                std::map<std::string, double> rightParamsMap;
9318
582
                try {
9319
1.73k
                    for (const auto &kv : prevStep.paramValues) {
9320
1.73k
                        leftParamsMap[kv.key] = c_locale_stod(kv.value);
9321
1.73k
                    }
9322
1.70k
                    for (const auto &kv : curStep.paramValues) {
9323
1.70k
                        rightParamsMap[kv.key] = c_locale_stod(kv.value);
9324
1.70k
                    }
9325
582
                } catch (const std::invalid_argument &) {
9326
74
                    break;
9327
74
                }
9328
508
                const std::string x("x");
9329
508
                const std::string y("y");
9330
508
                const std::string z("z");
9331
508
                if (leftParamsMap.find(x) != leftParamsMap.end() &&
9332
292
                    leftParamsMap.find(y) != leftParamsMap.end() &&
9333
279
                    leftParamsMap.find(z) != leftParamsMap.end() &&
9334
178
                    rightParamsMap.find(x) != rightParamsMap.end() &&
9335
112
                    rightParamsMap.find(y) != rightParamsMap.end() &&
9336
103
                    rightParamsMap.find(z) != rightParamsMap.end()) {
9337
9338
82
                    const double xSum = leftParamsMap[x] + rightParamsMap[x];
9339
82
                    const double ySum = leftParamsMap[y] + rightParamsMap[y];
9340
82
                    const double zSum = leftParamsMap[z] + rightParamsMap[z];
9341
82
                    if (xSum == 0.0 && ySum == 0.0 && zSum == 0.0) {
9342
6
                        deletePrevAndCurIter();
9343
76
                    } else {
9344
76
                        prevStep.paramValues[0] =
9345
76
                            Step::KeyValue("x", internal::toString(xSum));
9346
76
                        prevStep.paramValues[1] =
9347
76
                            Step::KeyValue("y", internal::toString(ySum));
9348
76
                        prevStep.paramValues[2] =
9349
76
                            Step::KeyValue("z", internal::toString(zSum));
9350
9351
                        // Delete this iter
9352
76
                        iterCur = steps.erase(iterCur);
9353
76
                    }
9354
82
                    continue;
9355
82
                }
9356
508
            }
9357
9358
            // Helmert followed by its inverse is a no-op
9359
42.6k
            if (curStep.name == "helmert" && prevStep.name == "helmert" &&
9360
2.09k
                !curStep.inverted && !prevStep.inverted &&
9361
1.00k
                curStepParamCount == prevStepParamCount) {
9362
821
                std::set<std::string> leftParamsSet;
9363
821
                std::set<std::string> rightParamsSet;
9364
821
                std::map<std::string, std::string> leftParamsMap;
9365
821
                std::map<std::string, std::string> rightParamsMap;
9366
2.98k
                for (const auto &kv : prevStep.paramValues) {
9367
2.98k
                    leftParamsSet.insert(kv.key);
9368
2.98k
                    leftParamsMap[kv.key] = kv.value;
9369
2.98k
                }
9370
2.98k
                for (const auto &kv : curStep.paramValues) {
9371
2.98k
                    rightParamsSet.insert(kv.key);
9372
2.98k
                    rightParamsMap[kv.key] = kv.value;
9373
2.98k
                }
9374
821
                if (leftParamsSet == rightParamsSet) {
9375
351
                    bool doErase = true;
9376
351
                    try {
9377
351
                        for (const auto &param : leftParamsSet) {
9378
318
                            if (param == "convention" || param == "t_epoch" ||
9379
192
                                param == "t_obs") {
9380
126
                                if (leftParamsMap[param] !=
9381
126
                                    rightParamsMap[param]) {
9382
113
                                    doErase = false;
9383
113
                                    break;
9384
113
                                }
9385
192
                            } else if (c_locale_stod(leftParamsMap[param]) !=
9386
192
                                       -c_locale_stod(rightParamsMap[param])) {
9387
91
                                doErase = false;
9388
91
                                break;
9389
91
                            }
9390
318
                        }
9391
351
                    } catch (const std::invalid_argument &) {
9392
17
                        break;
9393
17
                    }
9394
334
                    if (doErase) {
9395
130
                        deletePrevAndCurIter();
9396
130
                        continue;
9397
130
                    }
9398
334
                }
9399
821
            }
9400
9401
            // The following should be optimized as a no-op
9402
            // +step +proj=helmert +x=25 +y=-141 +z=-78.5 +rx=0 +ry=-0.35
9403
            // +rz=-0.736 +s=0 +convention=coordinate_frame
9404
            // +step +inv +proj=helmert +x=25 +y=-141 +z=-78.5 +rx=0 +ry=0.35
9405
            // +rz=0.736 +s=0 +convention=position_vector
9406
42.4k
            if (curStep.name == "helmert" && prevStep.name == "helmert" &&
9407
1.94k
                ((curStep.inverted && !prevStep.inverted) ||
9408
1.12k
                 (!curStep.inverted && prevStep.inverted)) &&
9409
923
                curStepParamCount == prevStepParamCount) {
9410
833
                std::set<std::string> leftParamsSet;
9411
833
                std::set<std::string> rightParamsSet;
9412
833
                std::map<std::string, std::string> leftParamsMap;
9413
833
                std::map<std::string, std::string> rightParamsMap;
9414
21.5k
                for (const auto &kv : prevStep.paramValues) {
9415
21.5k
                    leftParamsSet.insert(kv.key);
9416
21.5k
                    leftParamsMap[kv.key] = kv.value;
9417
21.5k
                }
9418
21.5k
                for (const auto &kv : curStep.paramValues) {
9419
21.5k
                    rightParamsSet.insert(kv.key);
9420
21.5k
                    rightParamsMap[kv.key] = kv.value;
9421
21.5k
                }
9422
833
                if (leftParamsSet == rightParamsSet) {
9423
320
                    bool doErase = true;
9424
320
                    try {
9425
4.89k
                        for (const auto &param : leftParamsSet) {
9426
4.89k
                            if (param == "convention") {
9427
                                // Convention must be different
9428
42
                                if (leftParamsMap[param] ==
9429
42
                                    rightParamsMap[param]) {
9430
17
                                    doErase = false;
9431
17
                                    break;
9432
17
                                }
9433
4.84k
                            } else if (param == "rx" || param == "ry" ||
9434
4.84k
                                       param == "rz" || param == "drx" ||
9435
4.84k
                                       param == "dry" || param == "drz") {
9436
                                // Rotational parameters should have opposite
9437
                                // value
9438
0
                                if (c_locale_stod(leftParamsMap[param]) !=
9439
0
                                    -c_locale_stod(rightParamsMap[param])) {
9440
0
                                    doErase = false;
9441
0
                                    break;
9442
0
                                }
9443
4.84k
                            } else {
9444
                                // Non rotational parameters should have the
9445
                                // same value
9446
4.84k
                                if (leftParamsMap[param] !=
9447
4.84k
                                    rightParamsMap[param]) {
9448
266
                                    doErase = false;
9449
266
                                    break;
9450
266
                                }
9451
4.84k
                            }
9452
4.89k
                        }
9453
320
                    } catch (const std::invalid_argument &) {
9454
0
                        break;
9455
0
                    }
9456
320
                    if (doErase) {
9457
37
                        deletePrevAndCurIter();
9458
37
                        continue;
9459
37
                    }
9460
320
                }
9461
833
            }
9462
9463
            // Optimize patterns like Krovak (South West) to Krovak East North
9464
            // (also applies to Modified Krovak)
9465
            //   +step +inv +proj=krovak +axis=swu +lat_0=49.5
9466
            //   +lon_0=24.8333333333333
9467
            //     +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel
9468
            //   +step +proj=krovak +lat_0=49.5 +lon_0=24.8333333333333
9469
            //     +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel
9470
            // as:
9471
            //   +step +proj=axisswap +order=-2,-1
9472
            // Also applies for the symmetrical case where +axis=swu is on the
9473
            // second step.
9474
42.4k
            if (curStep.inverted != prevStep.inverted &&
9475
23.2k
                curStep.name == prevStep.name &&
9476
17.1k
                ((curStepParamCount + 1 == prevStepParamCount &&
9477
2.29k
                  prevStep.paramValues[0].equals("axis", "swu")) ||
9478
17.1k
                 (prevStepParamCount + 1 == curStepParamCount &&
9479
1.41k
                  curStep.paramValues[0].equals("axis", "swu")))) {
9480
87
                const auto &swStep = (curStepParamCount < prevStepParamCount)
9481
87
                                         ? prevStep
9482
87
                                         : curStep;
9483
87
                const auto &enStep = (curStepParamCount < prevStepParamCount)
9484
87
                                         ? curStep
9485
87
                                         : prevStep;
9486
                // Check if all remaining parameters (except leading axis=swu
9487
                // in swStep) are identical.
9488
87
                bool allSame = true;
9489
87
                for (size_t j = 0;
9490
87
                     j < std::min(curStepParamCount, prevStepParamCount); j++) {
9491
10
                    if (enStep.paramValues[j] != swStep.paramValues[j + 1]) {
9492
10
                        allSame = false;
9493
10
                        break;
9494
10
                    }
9495
10
                }
9496
87
                if (allSame) {
9497
77
                    iterCur->inverted = false;
9498
77
                    iterCur->name = "axisswap";
9499
77
                    iterCur->paramValues.clear();
9500
77
                    iterCur->paramValues.emplace_back(
9501
77
                        Step::KeyValue("order", "-2,-1"));
9502
9503
77
                    deletePrevIter();
9504
77
                    continue;
9505
77
                }
9506
87
            }
9507
9508
            // detect a step and its inverse
9509
42.3k
            if (curStep.inverted != prevStep.inverted &&
9510
23.1k
                curStep.name == prevStep.name &&
9511
17.1k
                curStepParamCount == prevStepParamCount) {
9512
11.6k
                bool allSame = true;
9513
22.5k
                for (size_t j = 0; j < curStepParamCount; j++) {
9514
13.1k
                    if (curStep.paramValues[j] != prevStep.paramValues[j]) {
9515
2.23k
                        allSame = false;
9516
2.23k
                        break;
9517
2.23k
                    }
9518
13.1k
                }
9519
11.6k
                if (allSame) {
9520
9.38k
                    deletePrevAndCurIter();
9521
9.38k
                    continue;
9522
9.38k
                }
9523
11.6k
            }
9524
9525
32.9k
            ++iterCur;
9526
32.9k
        }
9527
20.8k
    }
9528
9529
20.8k
    {
9530
20.8k
        auto iterCur = steps.begin();
9531
20.8k
        if (iterCur != steps.end()) {
9532
20.3k
            ++iterCur;
9533
20.3k
        }
9534
36.0k
        while (iterCur != steps.end()) {
9535
9536
15.1k
            assert(iterCur != steps.begin());
9537
15.1k
            auto iterPrev = std::prev(iterCur);
9538
15.1k
            auto &prevStep = *iterPrev;
9539
15.1k
            auto &curStep = *iterCur;
9540
9541
15.1k
            const auto curStepParamCount = curStep.paramValues.size();
9542
15.1k
            const auto prevStepParamCount = prevStep.paramValues.size();
9543
9544
            // +step +proj=hgridshift +grids=grid_A
9545
            // +step +proj=vgridshift [...] <== curStep
9546
            // +step +inv +proj=hgridshift +grids=grid_A
9547
            // ==>
9548
            // +step +proj=push +v_1 +v_2
9549
            // +step +proj=hgridshift +grids=grid_A +omit_inv
9550
            // +step +proj=vgridshift [...]
9551
            // +step +inv +proj=hgridshift +grids=grid_A +omit_fwd
9552
            // +step +proj=pop +v_1 +v_2
9553
15.1k
            if (std::next(iterCur) != steps.end() &&
9554
12.4k
                prevStep.name == "hgridshift" && prevStepParamCount == 1 &&
9555
158
                curStep.name == "vgridshift") {
9556
113
                auto iterNext = std::next(iterCur);
9557
113
                auto &nextStep = *iterNext;
9558
113
                if (nextStep.name == "hgridshift" &&
9559
80
                    nextStep.inverted != prevStep.inverted &&
9560
74
                    nextStep.paramValues.size() == 1 &&
9561
66
                    prevStep.paramValues[0] == nextStep.paramValues[0]) {
9562
38
                    Step pushStep;
9563
38
                    pushStep.name = "push";
9564
38
                    pushStep.paramValues.emplace_back("v_1");
9565
38
                    pushStep.paramValues.emplace_back("v_2");
9566
38
                    steps.insert(iterPrev, pushStep);
9567
9568
38
                    prevStep.paramValues.emplace_back("omit_inv");
9569
9570
38
                    nextStep.paramValues.emplace_back("omit_fwd");
9571
9572
38
                    Step popStep;
9573
38
                    popStep.name = "pop";
9574
38
                    popStep.paramValues.emplace_back("v_1");
9575
38
                    popStep.paramValues.emplace_back("v_2");
9576
38
                    steps.insert(std::next(iterNext), popStep);
9577
9578
38
                    continue;
9579
38
                }
9580
113
            }
9581
9582
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9583
            // +step +proj=axisswap +order=2,1
9584
            // +step +proj=push +v_1 +v_2
9585
            // +step +proj=axisswap +order=2,1
9586
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9587
            // +step +proj=vgridshift ...
9588
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9589
            // +step +proj=axisswap +order=2,1
9590
            // +step +proj=pop +v_1 +v_2
9591
            // ==>
9592
            // +step +proj=vgridshift ...
9593
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9594
            // +step +proj=axisswap +order=2,1
9595
15.1k
            if (prevStep.name == "unitconvert" && prevStepParamCount == 2 &&
9596
1.07k
                prevStep.paramValues[0].equals("xy_in", "rad") &&
9597
122
                prevStep.paramValues[1].equals("xy_out", "deg") &&
9598
122
                curStep.name == "axisswap" && curStepParamCount == 1 &&
9599
122
                curStep.paramValues[0].equals("order", "2,1")) {
9600
122
                auto iterNext = std::next(iterCur);
9601
122
                bool ok = false;
9602
122
                if (iterNext != steps.end()) {
9603
0
                    auto &nextStep = *iterNext;
9604
0
                    if (nextStep.name == "push" &&
9605
0
                        nextStep.paramValues.size() == 2 &&
9606
0
                        nextStep.paramValues[0].keyEquals("v_1") &&
9607
0
                        nextStep.paramValues[1].keyEquals("v_2")) {
9608
0
                        ok = true;
9609
0
                        iterNext = std::next(iterNext);
9610
0
                    }
9611
0
                }
9612
122
                ok &= iterNext != steps.end();
9613
122
                if (ok) {
9614
0
                    ok = false;
9615
0
                    auto &nextStep = *iterNext;
9616
0
                    if (nextStep.name == "axisswap" &&
9617
0
                        nextStep.paramValues.size() == 1 &&
9618
0
                        nextStep.paramValues[0].equals("order", "2,1")) {
9619
0
                        ok = true;
9620
0
                        iterNext = std::next(iterNext);
9621
0
                    }
9622
0
                }
9623
122
                ok &= iterNext != steps.end();
9624
122
                if (ok) {
9625
0
                    ok = false;
9626
0
                    auto &nextStep = *iterNext;
9627
0
                    if (nextStep.name == "unitconvert" &&
9628
0
                        nextStep.paramValues.size() == 2 &&
9629
0
                        nextStep.paramValues[0].equals("xy_in", "deg") &&
9630
0
                        nextStep.paramValues[1].equals("xy_out", "rad")) {
9631
0
                        ok = true;
9632
0
                        iterNext = std::next(iterNext);
9633
0
                    }
9634
0
                }
9635
122
                auto iterVgridshift = iterNext;
9636
122
                ok &= iterNext != steps.end();
9637
122
                if (ok) {
9638
0
                    ok = false;
9639
0
                    auto &nextStep = *iterNext;
9640
0
                    if (nextStep.name == "vgridshift") {
9641
0
                        ok = true;
9642
0
                        iterNext = std::next(iterNext);
9643
0
                    }
9644
0
                }
9645
122
                ok &= iterNext != steps.end();
9646
122
                if (ok) {
9647
0
                    ok = false;
9648
0
                    auto &nextStep = *iterNext;
9649
0
                    if (nextStep.name == "unitconvert" &&
9650
0
                        nextStep.paramValues.size() == 2 &&
9651
0
                        nextStep.paramValues[0].equals("xy_in", "rad") &&
9652
0
                        nextStep.paramValues[1].equals("xy_out", "deg")) {
9653
0
                        ok = true;
9654
0
                        iterNext = std::next(iterNext);
9655
0
                    }
9656
0
                }
9657
122
                ok &= iterNext != steps.end();
9658
122
                if (ok) {
9659
0
                    ok = false;
9660
0
                    auto &nextStep = *iterNext;
9661
0
                    if (nextStep.name == "axisswap" &&
9662
0
                        nextStep.paramValues.size() == 1 &&
9663
0
                        nextStep.paramValues[0].equals("order", "2,1")) {
9664
0
                        ok = true;
9665
0
                        iterNext = std::next(iterNext);
9666
0
                    }
9667
0
                }
9668
122
                ok &= iterNext != steps.end();
9669
122
                if (ok) {
9670
0
                    ok = false;
9671
0
                    auto &nextStep = *iterNext;
9672
0
                    if (nextStep.name == "pop" &&
9673
0
                        nextStep.paramValues.size() == 2 &&
9674
0
                        nextStep.paramValues[0].keyEquals("v_1") &&
9675
0
                        nextStep.paramValues[1].keyEquals("v_2")) {
9676
0
                        ok = true;
9677
                        // iterNext = std::next(iterNext);
9678
0
                    }
9679
0
                }
9680
122
                if (ok) {
9681
0
                    steps.erase(iterPrev, iterVgridshift);
9682
0
                    steps.erase(iterNext, std::next(iterNext));
9683
0
                    iterPrev = std::prev(iterVgridshift);
9684
0
                    iterCur = iterVgridshift;
9685
0
                    continue;
9686
0
                }
9687
122
            }
9688
9689
            // +step +proj=axisswap +order=2,1
9690
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9691
            // +step +proj=vgridshift ...
9692
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9693
            // +step +proj=axisswap +order=2,1
9694
            // +step +proj=push +v_1 +v_2
9695
            // +step +proj=axisswap +order=2,1
9696
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9697
            // ==>
9698
            // +step +proj=push +v_1 +v_2
9699
            // +step +proj=axisswap +order=2,1
9700
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9701
            // +step +proj=vgridshift ...
9702
9703
15.1k
            if (prevStep.name == "axisswap" && prevStepParamCount == 1 &&
9704
921
                prevStep.paramValues[0].equals("order", "2,1") &&
9705
328
                curStep.name == "unitconvert" && curStepParamCount == 2 &&
9706
135
                !curStep.inverted &&
9707
132
                curStep.paramValues[0].equals("xy_in", "deg") &&
9708
88
                curStep.paramValues[1].equals("xy_out", "rad")) {
9709
88
                auto iterNext = std::next(iterCur);
9710
88
                bool ok = false;
9711
88
                auto iterVgridshift = iterNext;
9712
88
                if (iterNext != steps.end()) {
9713
88
                    auto &nextStep = *iterNext;
9714
88
                    if (nextStep.name == "vgridshift") {
9715
0
                        ok = true;
9716
0
                        iterNext = std::next(iterNext);
9717
0
                    }
9718
88
                }
9719
88
                ok &= iterNext != steps.end();
9720
88
                if (ok) {
9721
0
                    ok = false;
9722
0
                    auto &nextStep = *iterNext;
9723
0
                    if (nextStep.name == "unitconvert" && !nextStep.inverted &&
9724
0
                        nextStep.paramValues.size() == 2 &&
9725
0
                        nextStep.paramValues[0].equals("xy_in", "rad") &&
9726
0
                        nextStep.paramValues[1].equals("xy_out", "deg")) {
9727
0
                        ok = true;
9728
0
                        iterNext = std::next(iterNext);
9729
0
                    }
9730
0
                }
9731
88
                ok &= iterNext != steps.end();
9732
88
                if (ok) {
9733
0
                    ok = false;
9734
0
                    auto &nextStep = *iterNext;
9735
0
                    if (nextStep.name == "axisswap" &&
9736
0
                        nextStep.paramValues.size() == 1 &&
9737
0
                        nextStep.paramValues[0].equals("order", "2,1")) {
9738
0
                        ok = true;
9739
0
                        iterNext = std::next(iterNext);
9740
0
                    }
9741
0
                }
9742
88
                auto iterPush = iterNext;
9743
88
                ok &= iterNext != steps.end();
9744
88
                if (ok) {
9745
0
                    ok = false;
9746
0
                    auto &nextStep = *iterNext;
9747
0
                    if (nextStep.name == "push" &&
9748
0
                        nextStep.paramValues.size() == 2 &&
9749
0
                        nextStep.paramValues[0].keyEquals("v_1") &&
9750
0
                        nextStep.paramValues[1].keyEquals("v_2")) {
9751
0
                        ok = true;
9752
0
                        iterNext = std::next(iterNext);
9753
0
                    }
9754
0
                }
9755
88
                ok &= iterNext != steps.end();
9756
88
                if (ok) {
9757
0
                    ok = false;
9758
0
                    auto &nextStep = *iterNext;
9759
0
                    if (nextStep.name == "axisswap" &&
9760
0
                        nextStep.paramValues.size() == 1 &&
9761
0
                        nextStep.paramValues[0].equals("order", "2,1")) {
9762
0
                        ok = true;
9763
0
                        iterNext = std::next(iterNext);
9764
0
                    }
9765
0
                }
9766
88
                ok &= iterNext != steps.end();
9767
88
                if (ok) {
9768
0
                    ok = false;
9769
0
                    auto &nextStep = *iterNext;
9770
0
                    if (nextStep.name == "unitconvert" &&
9771
0
                        nextStep.paramValues.size() == 2 &&
9772
0
                        !nextStep.inverted &&
9773
0
                        nextStep.paramValues[0].equals("xy_in", "deg") &&
9774
0
                        nextStep.paramValues[1].equals("xy_out", "rad")) {
9775
0
                        ok = true;
9776
                        // iterNext = std::next(iterNext);
9777
0
                    }
9778
0
                }
9779
9780
88
                if (ok) {
9781
0
                    Step stepVgridshift(*iterVgridshift);
9782
0
                    steps.erase(iterPrev, iterPush);
9783
0
                    steps.insert(std::next(iterNext),
9784
0
                                 std::move(stepVgridshift));
9785
0
                    iterPrev = iterPush;
9786
0
                    iterCur = std::next(iterPush);
9787
0
                    continue;
9788
0
                }
9789
88
            }
9790
9791
15.1k
            ++iterCur;
9792
15.1k
        }
9793
20.8k
    }
9794
9795
20.8k
    {
9796
20.8k
        auto iterCur = steps.begin();
9797
20.8k
        if (iterCur != steps.end()) {
9798
20.3k
            ++iterCur;
9799
20.3k
        }
9800
36.4k
        while (iterCur != steps.end()) {
9801
9802
15.5k
            assert(iterCur != steps.begin());
9803
15.5k
            auto iterPrev = std::prev(iterCur);
9804
15.5k
            auto &prevStep = *iterPrev;
9805
15.5k
            auto &curStep = *iterCur;
9806
9807
15.5k
            const auto curStepParamCount = curStep.paramValues.size();
9808
15.5k
            const auto prevStepParamCount = prevStep.paramValues.size();
9809
9810
15.5k
            const auto deletePrevAndCurIter = [&steps, &iterPrev, &iterCur]() {
9811
389
                iterCur = steps.erase(iterPrev, std::next(iterCur));
9812
389
                if (iterCur != steps.begin())
9813
389
                    iterCur = std::prev(iterCur);
9814
389
                if (iterCur == steps.begin() && iterCur != steps.end())
9815
0
                    ++iterCur;
9816
389
            };
9817
9818
            // axisswap order=2,1 followed by itself is a no-op
9819
15.5k
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9820
654
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9821
243
                curStep.paramValues[0].equals("order", "2,1") &&
9822
43
                prevStep.paramValues[0].equals("order", "2,1")) {
9823
0
                deletePrevAndCurIter();
9824
0
                continue;
9825
0
            }
9826
9827
            // detect a step and its inverse
9828
15.5k
            if (curStep.inverted != prevStep.inverted &&
9829
6.22k
                curStep.name == prevStep.name &&
9830
3.56k
                curStepParamCount == prevStepParamCount) {
9831
1.57k
                bool allSame = true;
9832
5.79k
                for (size_t j = 0; j < curStepParamCount; j++) {
9833
5.40k
                    if (curStep.paramValues[j] != prevStep.paramValues[j]) {
9834
1.18k
                        allSame = false;
9835
1.18k
                        break;
9836
1.18k
                    }
9837
5.40k
                }
9838
1.57k
                if (allSame) {
9839
389
                    deletePrevAndCurIter();
9840
389
                    continue;
9841
389
                }
9842
1.57k
            }
9843
9844
15.1k
            ++iterCur;
9845
15.1k
        }
9846
20.8k
    }
9847
9848
20.8k
    if (steps.size() > 1 ||
9849
18.1k
        (steps.size() == 1 &&
9850
17.6k
         (steps.front().inverted || steps.front().hasKey("omit_inv") ||
9851
16.1k
          steps.front().hasKey("omit_fwd") ||
9852
16.1k
          !d->globalParamValues_.empty()))) {
9853
4.74k
        d->appendToResult("+proj=pipeline");
9854
9855
17.8k
        for (const auto &paramValue : d->globalParamValues_) {
9856
17.8k
            d->appendToResult("+");
9857
17.8k
            d->result_ += paramValue.key;
9858
17.8k
            if (!paramValue.value.empty()) {
9859
5.22k
                d->result_ += '=';
9860
5.22k
                d->result_ +=
9861
5.22k
                    pj_double_quote_string_param_if_needed(paramValue.value);
9862
5.22k
            }
9863
17.8k
        }
9864
9865
4.74k
        if (d->multiLine_) {
9866
0
            d->indentLevel_++;
9867
0
        }
9868
4.74k
    }
9869
9870
34.7k
    for (const auto &step : steps) {
9871
34.7k
        std::string curLine;
9872
34.7k
        if (!d->result_.empty()) {
9873
19.1k
            if (d->multiLine_) {
9874
0
                curLine = std::string(static_cast<size_t>(d->indentLevel_) *
9875
0
                                          d->indentWidth_,
9876
0
                                      ' ');
9877
0
                curLine += "+step";
9878
19.1k
            } else {
9879
19.1k
                curLine = " +step";
9880
19.1k
            }
9881
19.1k
        }
9882
34.7k
        if (step.inverted) {
9883
7.41k
            curLine += " +inv";
9884
7.41k
        }
9885
34.7k
        if (!step.name.empty()) {
9886
26.6k
            if (!curLine.empty())
9887
11.0k
                curLine += ' ';
9888
26.6k
            curLine += step.isInit ? "+init=" : "+proj=";
9889
26.6k
            curLine += step.name;
9890
26.6k
        }
9891
181k
        for (const auto &paramValue : step.paramValues) {
9892
181k
            std::string newKV = "+";
9893
181k
            newKV += paramValue.key;
9894
181k
            if (!paramValue.value.empty()) {
9895
111k
                newKV += '=';
9896
111k
                newKV +=
9897
111k
                    pj_double_quote_string_param_if_needed(paramValue.value);
9898
111k
            }
9899
181k
            if (d->maxLineLength_ > 0 && d->multiLine_ &&
9900
0
                curLine.size() + newKV.size() >
9901
0
                    static_cast<size_t>(d->maxLineLength_)) {
9902
0
                if (!d->result_.empty())
9903
0
                    d->result_ += '\n';
9904
0
                d->result_ += curLine;
9905
0
                curLine = std::string(static_cast<size_t>(d->indentLevel_) *
9906
0
                                              d->indentWidth_ +
9907
0
                                          strlen("+step "),
9908
0
                                      ' ');
9909
181k
            } else {
9910
181k
                if (!curLine.empty())
9911
181k
                    curLine += ' ';
9912
181k
            }
9913
181k
            curLine += newKV;
9914
181k
        }
9915
34.7k
        if (d->multiLine_ && !d->result_.empty())
9916
0
            d->result_ += '\n';
9917
34.7k
        d->result_ += curLine;
9918
34.7k
    }
9919
9920
20.8k
    if (d->result_.empty()) {
9921
469
        d->appendToResult("+proj=noop");
9922
469
    }
9923
9924
20.8k
    return d->result_;
9925
20.8k
}
9926
9927
// ---------------------------------------------------------------------------
9928
9929
//! @cond Doxygen_Suppress
9930
9931
4.87k
PROJStringFormatter::Convention PROJStringFormatter::convention() const {
9932
4.87k
    return d->convention_;
9933
4.87k
}
9934
9935
// ---------------------------------------------------------------------------
9936
9937
// Return the number of steps in the pipeline.
9938
// Note: this value will change after calling toString() that will run
9939
// optimizations.
9940
0
size_t PROJStringFormatter::getStepCount() const { return d->steps_.size(); }
9941
9942
// ---------------------------------------------------------------------------
9943
9944
2.30k
bool PROJStringFormatter::getUseApproxTMerc() const {
9945
2.30k
    return d->useApproxTMerc_;
9946
2.30k
}
9947
9948
// ---------------------------------------------------------------------------
9949
9950
1.34k
void PROJStringFormatter::setCoordinateOperationOptimizations(bool enable) {
9951
1.34k
    d->coordOperationOptimizations_ = enable;
9952
1.34k
}
9953
9954
// ---------------------------------------------------------------------------
9955
9956
23.1k
void PROJStringFormatter::Private::appendToResult(const char *str) {
9957
23.1k
    if (!result_.empty()) {
9958
17.8k
        result_ += ' ';
9959
17.8k
    }
9960
23.1k
    result_ += str;
9961
23.1k
}
9962
9963
// ---------------------------------------------------------------------------
9964
9965
static void
9966
PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps,
9967
                       std::vector<Step::KeyValue> &globalParamValues,
9968
78.6k
                       std::string &title) {
9969
78.6k
    std::vector<std::string> tokens;
9970
9971
78.6k
    bool hasProj = false;
9972
78.6k
    bool hasInit = false;
9973
78.6k
    bool hasPipeline = false;
9974
9975
78.6k
    std::string projStringModified(projString);
9976
9977
    // Special case for "+title=several words +foo=bar"
9978
78.6k
    if (starts_with(projStringModified, "+title=") &&
9979
50
        projStringModified.size() > 7 && projStringModified[7] != '"') {
9980
37
        const auto plusPos = projStringModified.find(" +", 1);
9981
37
        const auto spacePos = projStringModified.find(' ');
9982
37
        if (plusPos != std::string::npos && spacePos != std::string::npos &&
9983
37
            spacePos < plusPos) {
9984
0
            std::string tmp("+title=");
9985
0
            tmp += pj_double_quote_string_param_if_needed(
9986
0
                projStringModified.substr(7, plusPos - 7));
9987
0
            tmp += projStringModified.substr(plusPos);
9988
0
            projStringModified = std::move(tmp);
9989
0
        }
9990
37
    }
9991
9992
78.6k
    size_t argc = pj_trim_argc(&projStringModified[0]);
9993
78.6k
    char **argv = pj_trim_argv(argc, &projStringModified[0]);
9994
1.11M
    for (size_t i = 0; i < argc; i++) {
9995
1.03M
        std::string token(argv[i]);
9996
1.03M
        if (!hasPipeline && token == "proj=pipeline") {
9997
13.0k
            hasPipeline = true;
9998
1.02M
        } else if (!hasProj && starts_with(token, "proj=")) {
9999
64.6k
            hasProj = true;
10000
955k
        } else if (!hasInit && starts_with(token, "init=")) {
10001
16.8k
            hasInit = true;
10002
16.8k
        }
10003
1.03M
        tokens.emplace_back(token);
10004
1.03M
    }
10005
78.6k
    free(argv);
10006
10007
78.6k
    if (!hasPipeline) {
10008
65.6k
        if (hasProj || hasInit) {
10009
65.4k
            steps.push_back(Step());
10010
65.4k
        }
10011
10012
562k
        for (auto &word : tokens) {
10013
562k
            if (starts_with(word, "proj=") && !hasInit &&
10014
60.4k
                steps.back().name.empty()) {
10015
50.7k
                assert(hasProj);
10016
50.7k
                auto stepName = word.substr(strlen("proj="));
10017
50.7k
                steps.back().name = std::move(stepName);
10018
511k
            } else if (starts_with(word, "init=")) {
10019
15.7k
                assert(hasInit);
10020
15.7k
                auto initName = word.substr(strlen("init="));
10021
15.7k
                steps.back().name = std::move(initName);
10022
15.7k
                steps.back().isInit = true;
10023
496k
            } else if (word == "inv") {
10024
6.95k
                if (!steps.empty()) {
10025
6.94k
                    steps.back().inverted = true;
10026
6.94k
                }
10027
489k
            } else if (starts_with(word, "title=")) {
10028
2.38k
                title = word.substr(strlen("title="));
10029
486k
            } else if (word != "step") {
10030
479k
                const auto pos = word.find('=');
10031
479k
                const auto key = word.substr(0, pos);
10032
10033
479k
                Step::KeyValue pair(
10034
479k
                    (pos != std::string::npos)
10035
479k
                        ? Step::KeyValue(key, word.substr(pos + 1))
10036
479k
                        : Step::KeyValue(key));
10037
479k
                if (steps.empty()) {
10038
1.88k
                    globalParamValues.push_back(std::move(pair));
10039
477k
                } else {
10040
477k
                    steps.back().paramValues.push_back(std::move(pair));
10041
477k
                }
10042
479k
            }
10043
562k
        }
10044
65.6k
        return;
10045
65.6k
    }
10046
10047
13.0k
    bool inPipeline = false;
10048
13.0k
    bool invGlobal = false;
10049
470k
    for (auto &word : tokens) {
10050
470k
        if (word == "proj=pipeline") {
10051
13.0k
            if (inPipeline) {
10052
14
                throw ParsingException("nested pipeline not supported");
10053
14
            }
10054
13.0k
            inPipeline = true;
10055
457k
        } else if (word == "step") {
10056
121k
            if (!inPipeline) {
10057
20
                throw ParsingException("+step found outside pipeline");
10058
20
            }
10059
121k
            steps.push_back(Step());
10060
335k
        } else if (word == "inv") {
10061
52.9k
            if (steps.empty()) {
10062
3.45k
                invGlobal = true;
10063
49.4k
            } else {
10064
49.4k
                steps.back().inverted = true;
10065
49.4k
            }
10066
283k
        } else if (inPipeline && !steps.empty() && starts_with(word, "proj=") &&
10067
34.2k
                   steps.back().name.empty()) {
10068
30.9k
            auto stepName = word.substr(strlen("proj="));
10069
30.9k
            steps.back().name = std::move(stepName);
10070
252k
        } else if (inPipeline && !steps.empty() && starts_with(word, "init=") &&
10071
5.32k
                   steps.back().name.empty()) {
10072
4.23k
            auto initName = word.substr(strlen("init="));
10073
4.23k
            steps.back().name = std::move(initName);
10074
4.23k
            steps.back().isInit = true;
10075
247k
        } else if (!inPipeline && starts_with(word, "title=")) {
10076
1.43k
            title = word.substr(strlen("title="));
10077
246k
        } else {
10078
246k
            const auto pos = word.find('=');
10079
246k
            auto key = word.substr(0, pos);
10080
246k
            Step::KeyValue pair((pos != std::string::npos)
10081
246k
                                    ? Step::KeyValue(key, word.substr(pos + 1))
10082
246k
                                    : Step::KeyValue(key));
10083
246k
            if (steps.empty()) {
10084
82.5k
                globalParamValues.emplace_back(std::move(pair));
10085
163k
            } else {
10086
163k
                steps.back().paramValues.emplace_back(std::move(pair));
10087
163k
            }
10088
246k
        }
10089
470k
    }
10090
12.9k
    if (invGlobal) {
10091
48.7k
        for (auto &step : steps) {
10092
48.7k
            step.inverted = !step.inverted;
10093
48.7k
        }
10094
3.31k
        std::reverse(steps.begin(), steps.end());
10095
3.31k
    }
10096
12.9k
}
10097
10098
// ---------------------------------------------------------------------------
10099
10100
void PROJStringFormatter::ingestPROJString(
10101
    const std::string &str) // throw ParsingException
10102
21.6k
{
10103
21.6k
    std::vector<Step> steps;
10104
21.6k
    std::string title;
10105
21.6k
    PROJStringSyntaxParser(str, steps, d->globalParamValues_, title);
10106
21.6k
    d->steps_.insert(d->steps_.end(), steps.begin(), steps.end());
10107
21.6k
}
10108
10109
// ---------------------------------------------------------------------------
10110
10111
20.9k
void PROJStringFormatter::setCRSExport(bool b) { d->crsExport_ = b; }
10112
10113
// ---------------------------------------------------------------------------
10114
10115
61.9k
bool PROJStringFormatter::getCRSExport() const { return d->crsExport_; }
10116
10117
// ---------------------------------------------------------------------------
10118
10119
232
void PROJStringFormatter::startInversion() {
10120
232
    PROJStringFormatter::Private::InversionStackElt elt;
10121
232
    elt.startIter = d->steps_.end();
10122
232
    if (elt.startIter != d->steps_.begin()) {
10123
97
        elt.iterValid = true;
10124
97
        --elt.startIter; // point to the last valid element
10125
135
    } else {
10126
135
        elt.iterValid = false;
10127
135
    }
10128
232
    elt.currentInversionState =
10129
232
        !d->inversionStack_.back().currentInversionState;
10130
232
    d->inversionStack_.push_back(elt);
10131
232
}
10132
10133
// ---------------------------------------------------------------------------
10134
10135
232
void PROJStringFormatter::stopInversion() {
10136
232
    assert(!d->inversionStack_.empty());
10137
232
    auto startIter = d->inversionStack_.back().startIter;
10138
232
    if (!d->inversionStack_.back().iterValid) {
10139
135
        startIter = d->steps_.begin();
10140
135
    } else {
10141
97
        ++startIter; // advance after the last valid element we marked above
10142
97
    }
10143
    // Invert the inversion status of the steps between the start point and
10144
    // the current end of steps
10145
601
    for (auto iter = startIter; iter != d->steps_.end(); ++iter) {
10146
369
        iter->inverted = !iter->inverted;
10147
635
        for (auto &paramValue : iter->paramValues) {
10148
635
            if (paramValue.key == "omit_fwd")
10149
0
                paramValue.key = "omit_inv";
10150
635
            else if (paramValue.key == "omit_inv")
10151
0
                paramValue.key = "omit_fwd";
10152
635
        }
10153
369
    }
10154
    // And reverse the order of steps in that range as well.
10155
232
    std::reverse(startIter, d->steps_.end());
10156
232
    d->inversionStack_.pop_back();
10157
232
}
10158
10159
// ---------------------------------------------------------------------------
10160
10161
0
bool PROJStringFormatter::isInverted() const {
10162
0
    return d->inversionStack_.back().currentInversionState;
10163
0
}
10164
10165
// ---------------------------------------------------------------------------
10166
10167
10.4k
void PROJStringFormatter::Private::addStep() { steps_.emplace_back(Step()); }
10168
10169
// ---------------------------------------------------------------------------
10170
10171
9.56k
void PROJStringFormatter::addStep(const char *stepName) {
10172
9.56k
    d->addStep();
10173
9.56k
    d->steps_.back().name.assign(stepName);
10174
9.56k
}
10175
10176
// ---------------------------------------------------------------------------
10177
10178
882
void PROJStringFormatter::addStep(const std::string &stepName) {
10179
882
    d->addStep();
10180
882
    d->steps_.back().name = stepName;
10181
882
}
10182
10183
// ---------------------------------------------------------------------------
10184
10185
9
void PROJStringFormatter::setCurrentStepInverted(bool inverted) {
10186
9
    assert(!d->steps_.empty());
10187
9
    d->steps_.back().inverted = inverted;
10188
9
}
10189
10190
// ---------------------------------------------------------------------------
10191
10192
19.4k
bool PROJStringFormatter::hasParam(const char *paramName) const {
10193
19.4k
    if (!d->steps_.empty()) {
10194
119k
        for (const auto &paramValue : d->steps_.back().paramValues) {
10195
119k
            if (paramValue.keyEquals(paramName)) {
10196
590
                return true;
10197
590
            }
10198
119k
        }
10199
19.4k
    }
10200
18.8k
    return false;
10201
19.4k
}
10202
10203
// ---------------------------------------------------------------------------
10204
10205
1.44k
void PROJStringFormatter::addNoDefs(bool b) { d->addNoDefs_ = b; }
10206
10207
// ---------------------------------------------------------------------------
10208
10209
20.8k
bool PROJStringFormatter::getAddNoDefs() const { return d->addNoDefs_; }
10210
10211
// ---------------------------------------------------------------------------
10212
10213
11.6k
void PROJStringFormatter::addParam(const std::string &paramName) {
10214
11.6k
    if (d->steps_.empty()) {
10215
0
        d->addStep();
10216
0
    }
10217
11.6k
    d->steps_.back().paramValues.push_back(Step::KeyValue(paramName));
10218
11.6k
}
10219
10220
// ---------------------------------------------------------------------------
10221
10222
628
void PROJStringFormatter::addParam(const char *paramName, int val) {
10223
628
    addParam(std::string(paramName), val);
10224
628
}
10225
10226
628
void PROJStringFormatter::addParam(const std::string &paramName, int val) {
10227
628
    addParam(paramName, internal::toString(val));
10228
628
}
10229
10230
// ---------------------------------------------------------------------------
10231
10232
39.5k
static std::string formatToString(double val, double precision) {
10233
39.5k
    if (std::abs(val * 10 - std::round(val * 10)) < precision) {
10234
        // For the purpose of
10235
        // https://www.epsg-registry.org/export.htm?wkt=urn:ogc:def:crs:EPSG::27561
10236
        // Latitude of natural of origin to be properly rounded from 55 grad
10237
        // to
10238
        // 49.5 deg
10239
34.6k
        val = std::round(val * 10) / 10;
10240
34.6k
    }
10241
39.5k
    return normalizeSerializedString(internal::toString(val));
10242
39.5k
}
10243
10244
// ---------------------------------------------------------------------------
10245
10246
33.8k
void PROJStringFormatter::addParam(const char *paramName, double val) {
10247
33.8k
    addParam(std::string(paramName), val);
10248
33.8k
}
10249
10250
34.1k
void PROJStringFormatter::addParam(const std::string &paramName, double val) {
10251
34.1k
    if (paramName == "dt") {
10252
0
        addParam(paramName,
10253
0
                 normalizeSerializedString(internal::toString(val, 7)));
10254
34.1k
    } else {
10255
34.1k
        addParam(paramName, formatToString(val, 1e-8));
10256
34.1k
    }
10257
34.1k
}
10258
10259
// ---------------------------------------------------------------------------
10260
10261
void PROJStringFormatter::addParam(const char *paramName,
10262
779
                                   const std::vector<double> &vals) {
10263
779
    std::string paramValue;
10264
6.23k
    for (size_t i = 0; i < vals.size(); ++i) {
10265
5.45k
        if (i > 0) {
10266
4.67k
            paramValue += ',';
10267
4.67k
        }
10268
5.45k
        paramValue += formatToString(vals[i], 1e-8);
10269
5.45k
    }
10270
779
    addParam(paramName, paramValue);
10271
779
}
10272
10273
// ---------------------------------------------------------------------------
10274
10275
21.9k
void PROJStringFormatter::addParam(const char *paramName, const char *val) {
10276
21.9k
    addParam(std::string(paramName), val);
10277
21.9k
}
10278
10279
void PROJStringFormatter::addParam(const char *paramName,
10280
10.7k
                                   const std::string &val) {
10281
10.7k
    addParam(std::string(paramName), val);
10282
10.7k
}
10283
10284
void PROJStringFormatter::addParam(const std::string &paramName,
10285
21.9k
                                   const char *val) {
10286
21.9k
    addParam(paramName, std::string(val));
10287
21.9k
}
10288
10289
// ---------------------------------------------------------------------------
10290
10291
void PROJStringFormatter::addParam(const std::string &paramName,
10292
67.8k
                                   const std::string &val) {
10293
67.8k
    if (d->steps_.empty()) {
10294
41
        d->addStep();
10295
41
    }
10296
67.8k
    d->steps_.back().paramValues.push_back(Step::KeyValue(paramName, val));
10297
67.8k
}
10298
10299
// ---------------------------------------------------------------------------
10300
10301
void PROJStringFormatter::setTOWGS84Parameters(
10302
1.89k
    const std::vector<double> &params) {
10303
1.89k
    d->toWGS84Parameters_ = params;
10304
1.89k
}
10305
10306
// ---------------------------------------------------------------------------
10307
10308
8.00k
const std::vector<double> &PROJStringFormatter::getTOWGS84Parameters() const {
10309
8.00k
    return d->toWGS84Parameters_;
10310
8.00k
}
10311
10312
// ---------------------------------------------------------------------------
10313
10314
0
std::set<std::string> PROJStringFormatter::getUsedGridNames() const {
10315
0
    std::set<std::string> res;
10316
0
    for (const auto &step : d->steps_) {
10317
0
        for (const auto &param : step.paramValues) {
10318
0
            if (param.keyEquals("grids") || param.keyEquals("file")) {
10319
0
                const auto gridNames = split(param.value, ",");
10320
0
                for (const auto &gridName : gridNames) {
10321
0
                    res.insert(gridName);
10322
0
                }
10323
0
            }
10324
0
        }
10325
0
    }
10326
0
    return res;
10327
0
}
10328
10329
// ---------------------------------------------------------------------------
10330
10331
10.7k
bool PROJStringFormatter::requiresPerCoordinateInputTime() const {
10332
45.4k
    for (const auto &step : d->steps_) {
10333
45.4k
        if (step.name == "set" && !step.inverted) {
10334
5.28k
            for (const auto &param : step.paramValues) {
10335
5.28k
                if (param.keyEquals("v_4")) {
10336
4
                    return false;
10337
4
                }
10338
5.28k
            }
10339
45.2k
        } else if (step.name == "helmert") {
10340
8.52k
            for (const auto &param : step.paramValues) {
10341
8.52k
                if (param.keyEquals("t_epoch")) {
10342
8
                    return true;
10343
8
                }
10344
8.52k
            }
10345
43.3k
        } else if (step.name == "deformation") {
10346
1.31k
            for (const auto &param : step.paramValues) {
10347
1.31k
                if (param.keyEquals("t_epoch")) {
10348
23
                    return true;
10349
23
                }
10350
1.31k
            }
10351
43.2k
        } else if (step.name == "defmodel") {
10352
0
            return true;
10353
0
        }
10354
45.4k
    }
10355
10.7k
    return false;
10356
10.7k
}
10357
10358
// ---------------------------------------------------------------------------
10359
10360
void PROJStringFormatter::setVDatumExtension(const std::string &filename,
10361
1.70k
                                             const std::string &geoidCRSValue) {
10362
1.70k
    d->vDatumExtension_ = filename;
10363
1.70k
    d->geoidCRSValue_ = geoidCRSValue;
10364
1.70k
}
10365
10366
// ---------------------------------------------------------------------------
10367
10368
1.96k
const std::string &PROJStringFormatter::getVDatumExtension() const {
10369
1.96k
    return d->vDatumExtension_;
10370
1.96k
}
10371
10372
// ---------------------------------------------------------------------------
10373
10374
1.96k
const std::string &PROJStringFormatter::getGeoidCRSValue() const {
10375
1.96k
    return d->geoidCRSValue_;
10376
1.96k
}
10377
10378
// ---------------------------------------------------------------------------
10379
10380
1.03k
void PROJStringFormatter::setHDatumExtension(const std::string &filename) {
10381
1.03k
    d->hDatumExtension_ = filename;
10382
1.03k
}
10383
10384
// ---------------------------------------------------------------------------
10385
10386
8.00k
const std::string &PROJStringFormatter::getHDatumExtension() const {
10387
8.00k
    return d->hDatumExtension_;
10388
8.00k
}
10389
10390
// ---------------------------------------------------------------------------
10391
10392
void PROJStringFormatter::setGeogCRSOfCompoundCRS(
10393
3.84k
    const crs::GeographicCRSPtr &crs) {
10394
3.84k
    d->geogCRSOfCompoundCRS_ = crs;
10395
3.84k
}
10396
10397
// ---------------------------------------------------------------------------
10398
10399
const crs::GeographicCRSPtr &
10400
2.31k
PROJStringFormatter::getGeogCRSOfCompoundCRS() const {
10401
2.31k
    return d->geogCRSOfCompoundCRS_;
10402
2.31k
}
10403
10404
// ---------------------------------------------------------------------------
10405
10406
0
void PROJStringFormatter::setOmitProjLongLatIfPossible(bool omit) {
10407
0
    assert(d->omitProjLongLatIfPossible_ ^ omit);
10408
0
    d->omitProjLongLatIfPossible_ = omit;
10409
0
}
10410
10411
// ---------------------------------------------------------------------------
10412
10413
595
bool PROJStringFormatter::omitProjLongLatIfPossible() const {
10414
595
    return d->omitProjLongLatIfPossible_;
10415
595
}
10416
10417
// ---------------------------------------------------------------------------
10418
10419
6.83k
void PROJStringFormatter::pushOmitZUnitConversion() {
10420
6.83k
    d->omitZUnitConversion_.push_back(true);
10421
6.83k
}
10422
10423
// ---------------------------------------------------------------------------
10424
10425
6.83k
void PROJStringFormatter::popOmitZUnitConversion() {
10426
6.83k
    assert(d->omitZUnitConversion_.size() > 1);
10427
6.83k
    d->omitZUnitConversion_.pop_back();
10428
6.83k
}
10429
10430
// ---------------------------------------------------------------------------
10431
10432
0
bool PROJStringFormatter::omitZUnitConversion() const {
10433
0
    return d->omitZUnitConversion_.back();
10434
0
}
10435
10436
// ---------------------------------------------------------------------------
10437
10438
0
void PROJStringFormatter::pushOmitHorizontalConversionInVertTransformation() {
10439
0
    d->omitHorizontalConversionInVertTransformation_.push_back(true);
10440
0
}
10441
10442
// ---------------------------------------------------------------------------
10443
10444
0
void PROJStringFormatter::popOmitHorizontalConversionInVertTransformation() {
10445
0
    assert(d->omitHorizontalConversionInVertTransformation_.size() > 1);
10446
0
    d->omitHorizontalConversionInVertTransformation_.pop_back();
10447
0
}
10448
10449
// ---------------------------------------------------------------------------
10450
10451
44
bool PROJStringFormatter::omitHorizontalConversionInVertTransformation() const {
10452
44
    return d->omitHorizontalConversionInVertTransformation_.back();
10453
44
}
10454
10455
// ---------------------------------------------------------------------------
10456
10457
0
void PROJStringFormatter::setLegacyCRSToCRSContext(bool legacyContext) {
10458
0
    d->legacyCRSToCRSContext_ = legacyContext;
10459
0
}
10460
10461
// ---------------------------------------------------------------------------
10462
10463
13.6k
bool PROJStringFormatter::getLegacyCRSToCRSContext() const {
10464
13.6k
    return d->legacyCRSToCRSContext_;
10465
13.6k
}
10466
10467
// ---------------------------------------------------------------------------
10468
10469
/** Asks for a "normalized" output during toString(), aimed at comparing two
10470
 * strings for equivalence.
10471
 *
10472
 * This consists for now in sorting the +key=value option in lexicographic
10473
 * order.
10474
 */
10475
0
PROJStringFormatter &PROJStringFormatter::setNormalizeOutput() {
10476
0
    d->normalizeOutput_ = true;
10477
0
    return *this;
10478
0
}
10479
10480
// ---------------------------------------------------------------------------
10481
10482
11.4k
const DatabaseContextPtr &PROJStringFormatter::databaseContext() const {
10483
11.4k
    return d->dbContext_;
10484
11.4k
}
10485
10486
//! @endcond
10487
10488
// ---------------------------------------------------------------------------
10489
10490
//! @cond Doxygen_Suppress
10491
10492
struct PROJStringParser::Private {
10493
    DatabaseContextPtr dbContext_{};
10494
    PJ_CONTEXT *ctx_{};
10495
    bool usePROJ4InitRules_ = false;
10496
    std::vector<std::string> warningList_{};
10497
10498
    std::string projString_{};
10499
10500
    std::vector<Step> steps_{};
10501
    std::vector<Step::KeyValue> globalParamValues_{};
10502
    std::string title_{};
10503
10504
    bool ignoreNadgrids_ = false;
10505
10506
    template <class T>
10507
    // cppcheck-suppress functionStatic
10508
339k
    bool hasParamValue(Step &step, const T key) {
10509
339k
        for (auto &pair : globalParamValues_) {
10510
117k
            if (ci_equal(pair.key, key)) {
10511
373
                pair.usedByParser = true;
10512
373
                return true;
10513
373
            }
10514
117k
        }
10515
871k
        for (auto &pair : step.paramValues) {
10516
871k
            if (ci_equal(pair.key, key)) {
10517
23.6k
                pair.usedByParser = true;
10518
23.6k
                return true;
10519
23.6k
            }
10520
871k
        }
10521
315k
        return false;
10522
339k
    }
10523
10524
    template <class T>
10525
    // cppcheck-suppress functionStatic
10526
4.94k
    const std::string &getGlobalParamValue(T key) {
10527
35.6k
        for (auto &pair : globalParamValues_) {
10528
35.6k
            if (ci_equal(pair.key, key)) {
10529
576
                pair.usedByParser = true;
10530
576
                return pair.value;
10531
576
            }
10532
35.6k
        }
10533
4.36k
        return emptyString;
10534
4.94k
    }
10535
10536
    template <class T>
10537
    // cppcheck-suppress functionStatic
10538
1.39M
    const std::string &getParamValue(Step &step, const T key) {
10539
1.39M
        for (auto &pair : globalParamValues_) {
10540
505k
            if (ci_equal(pair.key, key)) {
10541
2.40k
                pair.usedByParser = true;
10542
2.40k
                return pair.value;
10543
2.40k
            }
10544
505k
        }
10545
3.79M
        for (auto &pair : step.paramValues) {
10546
3.79M
            if (ci_equal(pair.key, key)) {
10547
208k
                pair.usedByParser = true;
10548
208k
                return pair.value;
10549
208k
            }
10550
3.79M
        }
10551
1.18M
        return emptyString;
10552
1.39M
    }
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
10538
263k
    const std::string &getParamValue(Step &step, const T key) {
10539
263k
        for (auto &pair : globalParamValues_) {
10540
127k
            if (ci_equal(pair.key, key)) {
10541
340
                pair.usedByParser = true;
10542
340
                return pair.value;
10543
340
            }
10544
127k
        }
10545
739k
        for (auto &pair : step.paramValues) {
10546
739k
            if (ci_equal(pair.key, key)) {
10547
49.6k
                pair.usedByParser = true;
10548
49.6k
                return pair.value;
10549
49.6k
            }
10550
739k
        }
10551
213k
        return emptyString;
10552
262k
    }
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
10538
1.13M
    const std::string &getParamValue(Step &step, const T key) {
10539
1.13M
        for (auto &pair : globalParamValues_) {
10540
377k
            if (ci_equal(pair.key, key)) {
10541
2.06k
                pair.usedByParser = true;
10542
2.06k
                return pair.value;
10543
2.06k
            }
10544
377k
        }
10545
3.06M
        for (auto &pair : step.paramValues) {
10546
3.06M
            if (ci_equal(pair.key, key)) {
10547
159k
                pair.usedByParser = true;
10548
159k
                return pair.value;
10549
159k
            }
10550
3.06M
        }
10551
972k
        return emptyString;
10552
1.13M
    }
10553
10554
9.62k
    static const std::string &getParamValueK(Step &step) {
10555
30.4k
        for (auto &pair : step.paramValues) {
10556
30.4k
            if (ci_equal(pair.key, "k") || ci_equal(pair.key, "k_0")) {
10557
4.43k
                pair.usedByParser = true;
10558
4.43k
                return pair.value;
10559
4.43k
            }
10560
30.4k
        }
10561
5.19k
        return emptyString;
10562
9.62k
    }
10563
10564
    // cppcheck-suppress functionStatic
10565
58.8k
    bool hasUnusedParameters(const Step &step) const {
10566
58.8k
        if (steps_.size() == 1) {
10567
122k
            for (const auto &pair : step.paramValues) {
10568
122k
                if (pair.key != "no_defs" && !pair.usedByParser) {
10569
8.15k
                    return true;
10570
8.15k
                }
10571
122k
            }
10572
58.2k
        }
10573
50.6k
        return false;
10574
58.8k
    }
10575
10576
    // cppcheck-suppress functionStatic
10577
    std::string guessBodyName(double a);
10578
10579
    PrimeMeridianNNPtr buildPrimeMeridian(Step &step);
10580
    GeodeticReferenceFrameNNPtr buildDatum(Step &step,
10581
                                           const std::string &title);
10582
    GeodeticCRSNNPtr buildGeodeticCRS(int iStep, int iUnitConvert,
10583
                                      int iAxisSwap, bool ignorePROJAxis);
10584
    GeodeticCRSNNPtr buildGeocentricCRS(int iStep, int iUnitConvert);
10585
    CRSNNPtr buildProjectedCRS(int iStep, const GeodeticCRSNNPtr &geogCRS,
10586
                               int iUnitConvert, int iAxisSwap);
10587
    CRSNNPtr buildBoundOrCompoundCRSIfNeeded(int iStep, CRSNNPtr crs);
10588
    UnitOfMeasure buildUnit(Step &step, const std::string &unitsParamName,
10589
                            const std::string &toMeterParamName);
10590
10591
    enum class AxisType { REGULAR, NORTH_POLE, SOUTH_POLE };
10592
10593
    std::vector<CoordinateSystemAxisNNPtr>
10594
    processAxisSwap(Step &step, const UnitOfMeasure &unit, int iAxisSwap,
10595
                    AxisType axisType, bool ignorePROJAxis);
10596
10597
    EllipsoidalCSNNPtr buildEllipsoidalCS(int iStep, int iUnitConvert,
10598
                                          int iAxisSwap, bool ignorePROJAxis);
10599
10600
    SphericalCSNNPtr buildSphericalCS(int iStep, int iUnitConvert,
10601
                                      int iAxisSwap, bool ignorePROJAxis);
10602
};
10603
10604
//! @endcond
10605
10606
// ---------------------------------------------------------------------------
10607
10608
45.8k
PROJStringParser::PROJStringParser() : d(std::make_unique<Private>()) {}
10609
10610
// ---------------------------------------------------------------------------
10611
10612
//! @cond Doxygen_Suppress
10613
45.8k
PROJStringParser::~PROJStringParser() = default;
10614
//! @endcond
10615
10616
// ---------------------------------------------------------------------------
10617
10618
/** \brief Attach a database context, to allow queries in it if needed.
10619
 */
10620
PROJStringParser &
10621
45.8k
PROJStringParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) {
10622
45.8k
    d->dbContext_ = dbContext;
10623
45.8k
    return *this;
10624
45.8k
}
10625
10626
// ---------------------------------------------------------------------------
10627
10628
//! @cond Doxygen_Suppress
10629
45.8k
PROJStringParser &PROJStringParser::attachContext(PJ_CONTEXT *ctx) {
10630
45.8k
    d->ctx_ = ctx;
10631
45.8k
    return *this;
10632
45.8k
}
10633
//! @endcond
10634
10635
// ---------------------------------------------------------------------------
10636
10637
/** \brief Set how init=epsg:XXXX syntax should be interpreted.
10638
 *
10639
 * @param enable When set to true,
10640
 * init=epsg:XXXX syntax will be allowed and will be interpreted according to
10641
 * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
10642
 * order and will expect/output coordinates in radians. ProjectedCRS will have
10643
 * easting, northing axis order (except the ones with Transverse Mercator South
10644
 * Orientated projection).
10645
 */
10646
45.8k
PROJStringParser &PROJStringParser::setUsePROJ4InitRules(bool enable) {
10647
45.8k
    d->usePROJ4InitRules_ = enable;
10648
45.8k
    return *this;
10649
45.8k
}
10650
10651
// ---------------------------------------------------------------------------
10652
10653
/** \brief Return the list of warnings found during parsing.
10654
 */
10655
0
std::vector<std::string> PROJStringParser::warningList() const {
10656
0
    return d->warningList_;
10657
0
}
10658
10659
// ---------------------------------------------------------------------------
10660
10661
//! @cond Doxygen_Suppress
10662
10663
// ---------------------------------------------------------------------------
10664
10665
static const struct LinearUnitDesc {
10666
    const char *projName;
10667
    const char *convToMeter;
10668
    const char *name;
10669
    int epsgCode;
10670
} linearUnitDescs[] = {
10671
    {"mm", "0.001", "millimetre", 1025},
10672
    {"cm", "0.01", "centimetre", 1033},
10673
    {"m", "1.0", "metre", 9001},
10674
    {"meter", "1.0", "metre", 9001}, // alternative
10675
    {"metre", "1.0", "metre", 9001}, // alternative
10676
    {"ft", "0.3048", "foot", 9002},
10677
    {"us-ft", "0.3048006096012192", "US survey foot", 9003},
10678
    {"fath", "1.8288", "fathom", 9014},
10679
    {"kmi", "1852", "nautical mile", 9030},
10680
    {"us-ch", "20.11684023368047", "US survey chain", 9033},
10681
    {"us-mi", "1609.347218694437", "US survey mile", 9035},
10682
    {"km", "1000.0", "kilometre", 9036},
10683
    {"ind-ft", "0.30479841", "Indian foot (1937)", 9081},
10684
    {"ind-yd", "0.91439523", "Indian yard (1937)", 9085},
10685
    {"mi", "1609.344", "Statute mile", 9093},
10686
    {"yd", "0.9144", "yard", 9096},
10687
    {"ch", "20.1168", "chain", 9097},
10688
    {"link", "0.201168", "link", 9098},
10689
    {"dm", "0.1", "decimetre", 0},                       // no EPSG equivalent
10690
    {"in", "0.0254", "inch", 0},                         // no EPSG equivalent
10691
    {"us-in", "0.025400050800101", "US survey inch", 0}, // no EPSG equivalent
10692
    {"us-yd", "0.914401828803658", "US survey yard", 0}, // no EPSG equivalent
10693
    {"ind-ch", "20.11669506", "Indian chain", 0},        // no EPSG equivalent
10694
};
10695
10696
16.6k
static const LinearUnitDesc *getLinearUnits(const std::string &projName) {
10697
60.6k
    for (const auto &desc : linearUnitDescs) {
10698
60.6k
        if (desc.projName == projName)
10699
16.2k
            return &desc;
10700
60.6k
    }
10701
369
    return nullptr;
10702
16.6k
}
10703
10704
1.23k
static const LinearUnitDesc *getLinearUnits(double toMeter) {
10705
25.2k
    for (const auto &desc : linearUnitDescs) {
10706
25.2k
        if (std::fabs(c_locale_stod(desc.convToMeter) - toMeter) <
10707
25.2k
            1e-10 * toMeter) {
10708
158
            return &desc;
10709
158
        }
10710
25.2k
    }
10711
1.07k
    return nullptr;
10712
1.23k
}
10713
10714
// ---------------------------------------------------------------------------
10715
10716
16.4k
static UnitOfMeasure _buildUnit(const LinearUnitDesc *unitsMatch) {
10717
16.4k
    std::string unitsCode;
10718
16.4k
    if (unitsMatch->epsgCode) {
10719
16.4k
        std::ostringstream buffer;
10720
16.4k
        buffer.imbue(std::locale::classic());
10721
16.4k
        buffer << unitsMatch->epsgCode;
10722
16.4k
        unitsCode = buffer.str();
10723
16.4k
    }
10724
16.4k
    return UnitOfMeasure(
10725
16.4k
        unitsMatch->name, c_locale_stod(unitsMatch->convToMeter),
10726
16.4k
        UnitOfMeasure::Type::LINEAR,
10727
16.4k
        unitsMatch->epsgCode ? Identifier::EPSG : std::string(), unitsCode);
10728
16.4k
}
10729
10730
// ---------------------------------------------------------------------------
10731
10732
1.07k
static UnitOfMeasure _buildUnit(double to_meter_value) {
10733
    // TODO: look-up in EPSG catalog
10734
1.07k
    if (to_meter_value == 0) {
10735
3
        throw ParsingException("invalid unit value");
10736
3
    }
10737
1.07k
    return UnitOfMeasure("unknown", to_meter_value,
10738
1.07k
                         UnitOfMeasure::Type::LINEAR);
10739
1.07k
}
10740
10741
// ---------------------------------------------------------------------------
10742
10743
UnitOfMeasure
10744
PROJStringParser::Private::buildUnit(Step &step,
10745
                                     const std::string &unitsParamName,
10746
89.8k
                                     const std::string &toMeterParamName) {
10747
89.8k
    UnitOfMeasure unit = UnitOfMeasure::METRE;
10748
89.8k
    const LinearUnitDesc *unitsMatch = nullptr;
10749
89.8k
    const auto &projUnits = getParamValue(step, unitsParamName);
10750
89.8k
    if (!projUnits.empty()) {
10751
16.3k
        unitsMatch = getLinearUnits(projUnits);
10752
16.3k
        if (unitsMatch == nullptr) {
10753
17
            throw ParsingException("unhandled " + unitsParamName + "=" +
10754
17
                                   projUnits);
10755
17
        }
10756
16.3k
    }
10757
10758
89.8k
    const auto &toMeter = getParamValue(step, toMeterParamName);
10759
89.8k
    if (!toMeter.empty()) {
10760
1.24k
        double to_meter_value;
10761
1.24k
        try {
10762
1.24k
            to_meter_value = c_locale_stod(toMeter);
10763
1.24k
        } catch (const std::invalid_argument &) {
10764
9
            throw ParsingException("invalid value for " + toMeterParamName);
10765
9
        }
10766
1.23k
        unitsMatch = getLinearUnits(to_meter_value);
10767
1.23k
        if (unitsMatch == nullptr) {
10768
1.07k
            unit = _buildUnit(to_meter_value);
10769
1.07k
        }
10770
1.23k
    }
10771
10772
89.8k
    if (unitsMatch) {
10773
16.4k
        unit = _buildUnit(unitsMatch);
10774
16.4k
    }
10775
10776
89.8k
    return unit;
10777
89.8k
}
10778
10779
// ---------------------------------------------------------------------------
10780
10781
static const struct DatumDesc {
10782
    const char *projName;
10783
    const char *gcsName;
10784
    int gcsCode;
10785
    const char *datumName;
10786
    int datumCode;
10787
    const char *ellipsoidName;
10788
    int ellipsoidCode;
10789
    double a;
10790
    double rf;
10791
} datumDescs[] = {
10792
    {"GGRS87", "GGRS87", 4121, "Greek Geodetic Reference System 1987", 6121,
10793
     "GRS 1980", 7019, 6378137, 298.257222101},
10794
    {"potsdam", "DHDN", 4314, "Deutsches Hauptdreiecksnetz", 6314,
10795
     "Bessel 1841", 7004, 6377397.155, 299.1528128},
10796
    {"carthage", "Carthage", 4223, "Carthage", 6223, "Clarke 1880 (IGN)", 7011,
10797
     6378249.2, 293.4660213},
10798
    {"hermannskogel", "MGI", 4312, "Militar-Geographische Institut", 6312,
10799
     "Bessel 1841", 7004, 6377397.155, 299.1528128},
10800
    {"ire65", "TM65", 4299, "TM65", 6299, "Airy Modified 1849", 7002,
10801
     6377340.189, 299.3249646},
10802
    {"nzgd49", "NZGD49", 4272, "New Zealand Geodetic Datum 1949", 6272,
10803
     "International 1924", 7022, 6378388, 297},
10804
    {"OSGB36", "OSGB 1936", 4277, "OSGB 1936", 6277, "Airy 1830", 7001,
10805
     6377563.396, 299.3249646},
10806
};
10807
10808
// ---------------------------------------------------------------------------
10809
10810
99.0k
static bool isGeographicStep(const std::string &name) {
10811
99.0k
    return name == "longlat" || name == "lonlat" || name == "latlong" ||
10812
62.2k
           name == "latlon";
10813
99.0k
}
10814
10815
// ---------------------------------------------------------------------------
10816
10817
39.7k
static bool isGeocentricStep(const std::string &name) {
10818
39.7k
    return name == "geocent" || name == "cart";
10819
39.7k
}
10820
10821
// ---------------------------------------------------------------------------
10822
10823
80.9k
static bool isTopocentricStep(const std::string &name) {
10824
80.9k
    return name == "topocentric";
10825
80.9k
}
10826
10827
// ---------------------------------------------------------------------------
10828
10829
28.0k
static bool isProjectedStep(const std::string &name) {
10830
28.0k
    if (name == "etmerc" || name == "utm" ||
10831
27.3k
        !getMappingsFromPROJName(name).empty()) {
10832
14.2k
        return true;
10833
14.2k
    }
10834
    // IMPROVE ME: have a better way of distinguishing projections from
10835
    // other
10836
    // transformations.
10837
13.7k
    if (name == "pipeline" || name == "geoc" || name == "deformation" ||
10838
13.6k
        name == "helmert" || name == "hgridshift" || name == "molodensky" ||
10839
13.0k
        name == "vgridshift") {
10840
800
        return false;
10841
800
    }
10842
12.9k
    const auto *operations = proj_list_operations();
10843
1.33M
    for (int i = 0; operations[i].id != nullptr; ++i) {
10844
1.32M
        if (name == operations[i].id) {
10845
10.2k
            return true;
10846
10.2k
        }
10847
1.32M
    }
10848
2.75k
    return false;
10849
12.9k
}
10850
10851
// ---------------------------------------------------------------------------
10852
10853
48.2k
static PropertyMap createMapWithUnknownName() {
10854
48.2k
    return PropertyMap().set(common::IdentifiedObject::NAME_KEY, "unknown");
10855
48.2k
}
10856
10857
// ---------------------------------------------------------------------------
10858
10859
87.8k
PrimeMeridianNNPtr PROJStringParser::Private::buildPrimeMeridian(Step &step) {
10860
10861
87.8k
    PrimeMeridianNNPtr pm = PrimeMeridian::GREENWICH;
10862
87.8k
    const auto &pmStr = getParamValue(step, "pm");
10863
87.8k
    if (!pmStr.empty()) {
10864
3.29k
        char *end;
10865
3.29k
        double pmValue = dmstor(pmStr.c_str(), &end) * RAD_TO_DEG;
10866
3.29k
        if (pmValue != HUGE_VAL && *end == '\0') {
10867
2.83k
            pm = PrimeMeridian::create(createMapWithUnknownName(),
10868
2.83k
                                       Angle(pmValue));
10869
2.83k
        } else {
10870
458
            bool found = false;
10871
458
            if (pmStr == "paris") {
10872
8
                found = true;
10873
8
                pm = PrimeMeridian::PARIS;
10874
8
            }
10875
458
            auto proj_prime_meridians = proj_list_prime_meridians();
10876
3.16k
            for (int i = 0; !found && proj_prime_meridians[i].id != nullptr;
10877
3.15k
                 i++) {
10878
3.15k
                if (pmStr == proj_prime_meridians[i].id) {
10879
450
                    found = true;
10880
450
                    std::string name = static_cast<char>(::toupper(pmStr[0])) +
10881
450
                                       pmStr.substr(1);
10882
450
                    pmValue = dmstor(proj_prime_meridians[i].defn, nullptr) *
10883
450
                              RAD_TO_DEG;
10884
450
                    pm = PrimeMeridian::create(
10885
450
                        PropertyMap().set(IdentifiedObject::NAME_KEY, name),
10886
450
                        Angle(pmValue));
10887
450
                    break;
10888
450
                }
10889
3.15k
            }
10890
458
            if (!found) {
10891
0
                throw ParsingException("unknown pm " + pmStr);
10892
0
            }
10893
458
        }
10894
3.29k
    }
10895
87.8k
    return pm;
10896
87.8k
}
10897
10898
// ---------------------------------------------------------------------------
10899
10900
6.33k
std::string PROJStringParser::Private::guessBodyName(double a) {
10901
10902
6.33k
    auto ret = Ellipsoid::guessBodyName(dbContext_, a);
10903
6.33k
    if (ret == NON_EARTH_BODY && dbContext_ == nullptr && ctx_ != nullptr) {
10904
4
        dbContext_ =
10905
4
            ctx_->get_cpp_context()->getDatabaseContext().as_nullable();
10906
4
        if (dbContext_) {
10907
4
            ret = Ellipsoid::guessBodyName(dbContext_, a);
10908
4
        }
10909
4
    }
10910
6.33k
    return ret;
10911
6.33k
}
10912
10913
// ---------------------------------------------------------------------------
10914
10915
GeodeticReferenceFrameNNPtr
10916
59.2k
PROJStringParser::Private::buildDatum(Step &step, const std::string &title) {
10917
10918
59.2k
    std::string ellpsStr = getParamValue(step, "ellps");
10919
59.2k
    const auto &datumStr = getParamValue(step, "datum");
10920
59.2k
    const auto &RStr = getParamValue(step, "R");
10921
59.2k
    const auto &aStr = getParamValue(step, "a");
10922
59.2k
    const auto &bStr = getParamValue(step, "b");
10923
59.2k
    const auto &rfStr = getParamValue(step, "rf");
10924
59.2k
    const auto &fStr = getParamValue(step, "f");
10925
59.2k
    const auto &esStr = getParamValue(step, "es");
10926
59.2k
    const auto &eStr = getParamValue(step, "e");
10927
59.2k
    double a = -1.0;
10928
59.2k
    double b = -1.0;
10929
59.2k
    double rf = -1.0;
10930
59.2k
    const util::optional<std::string> optionalEmptyString{};
10931
59.2k
    const bool numericParamPresent =
10932
59.2k
        !RStr.empty() || !aStr.empty() || !bStr.empty() || !rfStr.empty() ||
10933
54.1k
        !fStr.empty() || !esStr.empty() || !eStr.empty();
10934
10935
59.2k
    if (!numericParamPresent && ellpsStr.empty() && datumStr.empty() &&
10936
18.6k
        (step.name == "krovak" || step.name == "mod_krovak")) {
10937
832
        ellpsStr = "bessel";
10938
832
    }
10939
10940
59.2k
    PrimeMeridianNNPtr pm(buildPrimeMeridian(step));
10941
59.2k
    PropertyMap grfMap;
10942
10943
59.2k
    const auto &nadgrids = getParamValue(step, "nadgrids");
10944
59.2k
    const auto &towgs84 = getParamValue(step, "towgs84");
10945
59.2k
    std::string datumNameSuffix;
10946
59.2k
    if (!nadgrids.empty()) {
10947
3.13k
        datumNameSuffix = " using nadgrids=" + nadgrids;
10948
56.0k
    } else if (!towgs84.empty()) {
10949
3.79k
        datumNameSuffix = " using towgs84=" + towgs84;
10950
3.79k
    }
10951
10952
    // It is arguable that we allow the prime meridian of a datum defined by
10953
    // its name to be overridden, but this is found at least in a regression
10954
    // test
10955
    // of GDAL. So let's keep the ellipsoid part of the datum in that case and
10956
    // use the specified prime meridian.
10957
59.2k
    const auto overridePmIfNeeded =
10958
59.2k
        [&pm, &datumNameSuffix](const GeodeticReferenceFrameNNPtr &grf) {
10959
22.3k
            if (pm->_isEquivalentTo(PrimeMeridian::GREENWICH.get())) {
10960
21.5k
                return grf;
10961
21.5k
            } else {
10962
836
                return GeodeticReferenceFrame::create(
10963
836
                    PropertyMap().set(IdentifiedObject::NAME_KEY,
10964
836
                                      UNKNOWN_BASED_ON +
10965
836
                                          grf->ellipsoid()->nameStr() +
10966
836
                                          " ellipsoid" + datumNameSuffix),
10967
836
                    grf->ellipsoid(), grf->anchorDefinition(), pm);
10968
836
            }
10969
22.3k
        };
10970
10971
    // R take precedence
10972
59.2k
    if (!RStr.empty()) {
10973
826
        double R;
10974
826
        try {
10975
826
            R = c_locale_stod(RStr);
10976
826
        } catch (const std::invalid_argument &) {
10977
2
            throw ParsingException("Invalid R value");
10978
2
        }
10979
824
        auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(),
10980
824
                                                 Length(R), guessBodyName(R));
10981
824
        return GeodeticReferenceFrame::create(
10982
824
            grfMap.set(IdentifiedObject::NAME_KEY,
10983
824
                       title.empty() ? "unknown" + datumNameSuffix : title),
10984
824
            ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
10985
826
    }
10986
10987
58.3k
    if (!datumStr.empty()) {
10988
5.25k
        auto l_datum = [&datumStr, &overridePmIfNeeded, &grfMap,
10989
5.25k
                        &optionalEmptyString, &pm]() {
10990
5.25k
            if (datumStr == "WGS84") {
10991
4.00k
                return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
10992
4.00k
            } else if (datumStr == "NAD83") {
10993
33
                return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6269);
10994
1.21k
            } else if (datumStr == "NAD27") {
10995
368
                return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6267);
10996
849
            } else {
10997
10998
4.30k
                for (const auto &datumDesc : datumDescs) {
10999
4.30k
                    if (datumStr == datumDesc.projName) {
11000
832
                        (void)datumDesc.gcsName; // to please cppcheck
11001
832
                        (void)datumDesc.gcsCode; // to please cppcheck
11002
832
                        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11003
832
                            grfMap
11004
832
                                .set(IdentifiedObject::NAME_KEY,
11005
832
                                     datumDesc.ellipsoidName)
11006
832
                                .set(Identifier::CODESPACE_KEY,
11007
832
                                     Identifier::EPSG)
11008
832
                                .set(Identifier::CODE_KEY,
11009
832
                                     datumDesc.ellipsoidCode),
11010
832
                            Length(datumDesc.a), Scale(datumDesc.rf));
11011
832
                        return GeodeticReferenceFrame::create(
11012
832
                            grfMap
11013
832
                                .set(IdentifiedObject::NAME_KEY,
11014
832
                                     datumDesc.datumName)
11015
832
                                .set(Identifier::CODESPACE_KEY,
11016
832
                                     Identifier::EPSG)
11017
832
                                .set(Identifier::CODE_KEY, datumDesc.datumCode),
11018
832
                            ellipsoid, optionalEmptyString, pm);
11019
832
                    }
11020
4.30k
                }
11021
849
            }
11022
17
            throw ParsingException("unknown datum " + datumStr);
11023
5.25k
        }();
11024
5.25k
        if (!numericParamPresent) {
11025
4.30k
            return l_datum;
11026
4.30k
        }
11027
956
        a = l_datum->ellipsoid()->semiMajorAxis().getSIValue();
11028
956
        rf = l_datum->ellipsoid()->computedInverseFlattening();
11029
956
    }
11030
11031
53.1k
    else if (!ellpsStr.empty()) {
11032
31.2k
        auto l_datum = [&ellpsStr, &title, &grfMap, &optionalEmptyString, &pm,
11033
31.2k
                        &datumNameSuffix]() {
11034
31.2k
            if (ellpsStr == "WGS84") {
11035
26.0k
                return GeodeticReferenceFrame::create(
11036
26.0k
                    grfMap.set(IdentifiedObject::NAME_KEY,
11037
26.0k
                               title.empty() ? std::string(UNKNOWN_BASED_ON)
11038
25.8k
                                                   .append("WGS 84 ellipsoid")
11039
25.8k
                                                   .append(datumNameSuffix)
11040
26.0k
                                             : title),
11041
26.0k
                    Ellipsoid::WGS84, optionalEmptyString, pm);
11042
26.0k
            } else if (ellpsStr == "GRS80") {
11043
1.43k
                return GeodeticReferenceFrame::create(
11044
1.43k
                    grfMap.set(IdentifiedObject::NAME_KEY,
11045
1.43k
                               title.empty() ? std::string(UNKNOWN_BASED_ON)
11046
1.43k
                                                   .append("GRS 1980 ellipsoid")
11047
1.43k
                                                   .append(datumNameSuffix)
11048
1.43k
                                             : title),
11049
1.43k
                    Ellipsoid::GRS1980, optionalEmptyString, pm);
11050
3.75k
            } else {
11051
3.75k
                auto proj_ellps = proj_list_ellps();
11052
85.1k
                for (int i = 0; proj_ellps[i].id != nullptr; i++) {
11053
85.1k
                    if (ellpsStr == proj_ellps[i].id) {
11054
3.74k
                        assert(strncmp(proj_ellps[i].major, "a=", 2) == 0);
11055
3.74k
                        const double a_iter =
11056
3.74k
                            c_locale_stod(proj_ellps[i].major + 2);
11057
3.74k
                        EllipsoidPtr ellipsoid;
11058
3.74k
                        PropertyMap ellpsMap;
11059
3.74k
                        if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) {
11060
588
                            const double b_iter =
11061
588
                                c_locale_stod(proj_ellps[i].ell + 2);
11062
588
                            ellipsoid =
11063
588
                                Ellipsoid::createTwoAxis(
11064
588
                                    ellpsMap.set(IdentifiedObject::NAME_KEY,
11065
588
                                                 proj_ellps[i].name),
11066
588
                                    Length(a_iter), Length(b_iter))
11067
588
                                    .as_nullable();
11068
3.15k
                        } else {
11069
3.15k
                            assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0);
11070
3.15k
                            const double rf_iter =
11071
3.15k
                                c_locale_stod(proj_ellps[i].ell + 3);
11072
3.15k
                            ellipsoid =
11073
3.15k
                                Ellipsoid::createFlattenedSphere(
11074
3.15k
                                    ellpsMap.set(IdentifiedObject::NAME_KEY,
11075
3.15k
                                                 proj_ellps[i].name),
11076
3.15k
                                    Length(a_iter), Scale(rf_iter))
11077
3.15k
                                    .as_nullable();
11078
3.15k
                        }
11079
3.74k
                        return GeodeticReferenceFrame::create(
11080
3.74k
                            grfMap.set(IdentifiedObject::NAME_KEY,
11081
3.74k
                                       title.empty()
11082
3.74k
                                           ? std::string(UNKNOWN_BASED_ON)
11083
3.68k
                                                 .append(proj_ellps[i].name)
11084
3.68k
                                                 .append(" ellipsoid")
11085
3.68k
                                                 .append(datumNameSuffix)
11086
3.74k
                                           : title),
11087
3.74k
                            NN_NO_CHECK(ellipsoid), optionalEmptyString, pm);
11088
3.74k
                    }
11089
85.1k
                }
11090
10
                throw ParsingException("unknown ellipsoid " + ellpsStr);
11091
3.75k
            }
11092
31.2k
        }();
11093
31.2k
        if (!numericParamPresent) {
11094
30.5k
            return l_datum;
11095
30.5k
        }
11096
693
        a = l_datum->ellipsoid()->semiMajorAxis().getSIValue();
11097
693
        if (l_datum->ellipsoid()->semiMinorAxis().has_value()) {
11098
230
            b = l_datum->ellipsoid()->semiMinorAxis()->getSIValue();
11099
463
        } else {
11100
463
            rf = l_datum->ellipsoid()->computedInverseFlattening();
11101
463
        }
11102
693
    }
11103
11104
23.5k
    if (!aStr.empty()) {
11105
4.16k
        try {
11106
4.16k
            a = c_locale_stod(aStr);
11107
4.16k
        } catch (const std::invalid_argument &) {
11108
26
            throw ParsingException("Invalid a value");
11109
26
        }
11110
4.16k
    }
11111
11112
23.5k
    const auto createGRF = [&grfMap, &title, &optionalEmptyString,
11113
23.5k
                            &datumNameSuffix,
11114
23.5k
                            &pm](const EllipsoidNNPtr &ellipsoid) {
11115
5.51k
        std::string datumName(title);
11116
5.51k
        if (title.empty()) {
11117
5.40k
            if (ellipsoid->nameStr() != "unknown") {
11118
1.50k
                datumName = UNKNOWN_BASED_ON;
11119
1.50k
                datumName += ellipsoid->nameStr();
11120
1.50k
                datumName += " ellipsoid";
11121
3.89k
            } else {
11122
3.89k
                datumName = "unknown";
11123
3.89k
            }
11124
5.40k
            datumName += datumNameSuffix;
11125
5.40k
        }
11126
5.51k
        return GeodeticReferenceFrame::create(
11127
5.51k
            grfMap.set(IdentifiedObject::NAME_KEY, datumName), ellipsoid,
11128
5.51k
            optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
11129
5.51k
    };
11130
11131
23.5k
    if (a > 0 && (b > 0 || !bStr.empty())) {
11132
1.07k
        if (!bStr.empty()) {
11133
847
            try {
11134
847
                b = c_locale_stod(bStr);
11135
847
            } catch (const std::invalid_argument &) {
11136
4
                throw ParsingException("Invalid b value");
11137
4
            }
11138
847
        }
11139
1.07k
        auto ellipsoid =
11140
1.07k
            Ellipsoid::createTwoAxis(createMapWithUnknownName(), Length(a),
11141
1.07k
                                     Length(b), guessBodyName(a))
11142
1.07k
                ->identify();
11143
1.07k
        return createGRF(ellipsoid);
11144
1.07k
    }
11145
11146
22.4k
    else if (a > 0 && (rf >= 0 || !rfStr.empty())) {
11147
1.63k
        if (!rfStr.empty()) {
11148
333
            try {
11149
333
                rf = c_locale_stod(rfStr);
11150
333
            } catch (const std::invalid_argument &) {
11151
2
                throw ParsingException("Invalid rf value");
11152
2
            }
11153
333
        }
11154
1.63k
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11155
1.63k
                             createMapWithUnknownName(), Length(a), Scale(rf),
11156
1.63k
                             guessBodyName(a))
11157
1.63k
                             ->identify();
11158
1.63k
        return createGRF(ellipsoid);
11159
1.63k
    }
11160
11161
20.8k
    else if (a > 0 && !fStr.empty()) {
11162
13
        double f;
11163
13
        try {
11164
13
            f = c_locale_stod(fStr);
11165
13
        } catch (const std::invalid_argument &) {
11166
4
            throw ParsingException("Invalid f value");
11167
4
        }
11168
9
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11169
9
                             createMapWithUnknownName(), Length(a),
11170
9
                             Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
11171
9
                             ->identify();
11172
9
        return createGRF(ellipsoid);
11173
13
    }
11174
11175
20.8k
    else if (a > 0 && !eStr.empty()) {
11176
59
        double e;
11177
59
        try {
11178
59
            e = c_locale_stod(eStr);
11179
59
        } catch (const std::invalid_argument &) {
11180
2
            throw ParsingException("Invalid e value");
11181
2
        }
11182
57
        double alpha = asin(e);    /* angular eccentricity */
11183
57
        double f = 1 - cos(alpha); /* = 1 - sqrt (1 - es); */
11184
57
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11185
57
                             createMapWithUnknownName(), Length(a),
11186
57
                             Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
11187
57
                             ->identify();
11188
57
        return createGRF(ellipsoid);
11189
59
    }
11190
11191
20.7k
    else if (a > 0 && !esStr.empty()) {
11192
0
        double es;
11193
0
        try {
11194
0
            es = c_locale_stod(esStr);
11195
0
        } catch (const std::invalid_argument &) {
11196
0
            throw ParsingException("Invalid es value");
11197
0
        }
11198
0
        double f = 1 - sqrt(1 - es);
11199
0
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11200
0
                             createMapWithUnknownName(), Length(a),
11201
0
                             Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
11202
0
                             ->identify();
11203
0
        return createGRF(ellipsoid);
11204
0
    }
11205
11206
    // If only a is specified, create a sphere
11207
20.7k
    if (a > 0 && bStr.empty() && rfStr.empty() && eStr.empty() &&
11208
2.74k
        esStr.empty()) {
11209
2.74k
        auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(),
11210
2.74k
                                                 Length(a), guessBodyName(a));
11211
2.74k
        return createGRF(ellipsoid);
11212
2.74k
    }
11213
11214
18.0k
    if (!bStr.empty() && aStr.empty()) {
11215
5
        throw ParsingException("b found, but a missing");
11216
5
    }
11217
11218
18.0k
    if (!rfStr.empty() && aStr.empty()) {
11219
1
        throw ParsingException("rf found, but a missing");
11220
1
    }
11221
11222
18.0k
    if (!fStr.empty() && aStr.empty()) {
11223
9
        throw ParsingException("f found, but a missing");
11224
9
    }
11225
11226
17.9k
    if (!eStr.empty() && aStr.empty()) {
11227
8
        throw ParsingException("e found, but a missing");
11228
8
    }
11229
11230
17.9k
    if (!esStr.empty() && aStr.empty()) {
11231
2
        throw ParsingException("es found, but a missing");
11232
2
    }
11233
11234
17.9k
    return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
11235
17.9k
}
11236
11237
// ---------------------------------------------------------------------------
11238
11239
static const MeridianPtr nullMeridian{};
11240
11241
static CoordinateSystemAxisNNPtr
11242
createAxis(const std::string &name, const std::string &abbreviation,
11243
           const AxisDirection &direction, const common::UnitOfMeasure &unit,
11244
334k
           const MeridianPtr &meridian = nullMeridian) {
11245
334k
    return CoordinateSystemAxis::create(
11246
334k
        PropertyMap().set(IdentifiedObject::NAME_KEY, name), abbreviation,
11247
334k
        direction, unit, meridian);
11248
334k
}
11249
11250
std::vector<CoordinateSystemAxisNNPtr>
11251
PROJStringParser::Private::processAxisSwap(Step &step,
11252
                                           const UnitOfMeasure &unit,
11253
                                           int iAxisSwap, AxisType axisType,
11254
83.4k
                                           bool ignorePROJAxis) {
11255
83.4k
    assert(iAxisSwap < 0 || ci_equal(steps_[iAxisSwap].name, "axisswap"));
11256
11257
83.4k
    const bool isGeographic = unit.type() == UnitOfMeasure::Type::ANGULAR;
11258
83.4k
    const bool isSpherical = isGeographic && hasParamValue(step, "geoc");
11259
83.4k
    const auto &eastName = isSpherical    ? "Planetocentric longitude"
11260
83.4k
                           : isGeographic ? AxisName::Longitude
11261
81.9k
                                          : AxisName::Easting;
11262
83.4k
    const auto &eastAbbev = isSpherical    ? "V"
11263
83.4k
                            : isGeographic ? AxisAbbreviation::lon
11264
81.9k
                                           : AxisAbbreviation::E;
11265
83.4k
    const auto &eastDir =
11266
83.4k
        isGeographic                         ? AxisDirection::EAST
11267
83.4k
        : (axisType == AxisType::NORTH_POLE) ? AxisDirection::SOUTH
11268
28.1k
        : (axisType == AxisType::SOUTH_POLE) ? AxisDirection::NORTH
11269
28.0k
                                             : AxisDirection::EAST;
11270
83.4k
    CoordinateSystemAxisNNPtr east = createAxis(
11271
83.4k
        eastName, eastAbbev, eastDir, unit,
11272
83.4k
        (!isGeographic &&
11273
28.1k
         (axisType == AxisType::NORTH_POLE || axisType == AxisType::SOUTH_POLE))
11274
83.4k
            ? Meridian::create(Angle(90, UnitOfMeasure::DEGREE)).as_nullable()
11275
83.4k
            : nullMeridian);
11276
11277
83.4k
    const auto &northName = isSpherical    ? "Planetocentric latitude"
11278
83.4k
                            : isGeographic ? AxisName::Latitude
11279
81.9k
                                           : AxisName::Northing;
11280
83.4k
    const auto &northAbbev = isSpherical    ? "U"
11281
83.4k
                             : isGeographic ? AxisAbbreviation::lat
11282
81.9k
                                            : AxisAbbreviation::N;
11283
83.4k
    const auto &northDir = isGeographic ? AxisDirection::NORTH
11284
83.4k
                           : (axisType == AxisType::NORTH_POLE)
11285
28.1k
                               ? AxisDirection::SOUTH
11286
                               /*: (axisType == AxisType::SOUTH_POLE)
11287
                                     ? AxisDirection::NORTH*/
11288
28.1k
                               : AxisDirection::NORTH;
11289
83.4k
    const CoordinateSystemAxisNNPtr north = createAxis(
11290
83.4k
        northName, northAbbev, northDir, unit,
11291
83.4k
        isGeographic ? nullMeridian
11292
83.4k
        : (axisType == AxisType::NORTH_POLE)
11293
28.1k
            ? Meridian::create(Angle(180, UnitOfMeasure::DEGREE)).as_nullable()
11294
28.1k
        : (axisType == AxisType::SOUTH_POLE)
11295
28.0k
            ? Meridian::create(Angle(0, UnitOfMeasure::DEGREE)).as_nullable()
11296
28.0k
            : nullMeridian);
11297
11298
83.4k
    CoordinateSystemAxisNNPtr west =
11299
83.4k
        createAxis(isSpherical    ? "Planetocentric longitude"
11300
83.4k
                   : isGeographic ? AxisName::Longitude
11301
81.9k
                                  : AxisName::Westing,
11302
83.4k
                   isSpherical    ? "V"
11303
83.4k
                   : isGeographic ? AxisAbbreviation::lon
11304
81.9k
                                  : std::string(),
11305
83.4k
                   AxisDirection::WEST, unit);
11306
11307
83.4k
    CoordinateSystemAxisNNPtr south =
11308
83.4k
        createAxis(isSpherical    ? "Planetocentric latitude"
11309
83.4k
                   : isGeographic ? AxisName::Latitude
11310
81.9k
                                  : AxisName::Southing,
11311
83.4k
                   isSpherical    ? "U"
11312
83.4k
                   : isGeographic ? AxisAbbreviation::lat
11313
81.9k
                                  : std::string(),
11314
83.4k
                   AxisDirection::SOUTH, unit);
11315
11316
83.4k
    std::vector<CoordinateSystemAxisNNPtr> axis{east, north};
11317
11318
83.4k
    const auto &axisStr = getParamValue(step, "axis");
11319
83.4k
    if (!ignorePROJAxis && !axisStr.empty()) {
11320
622
        if (axisStr.size() == 3) {
11321
1.86k
            for (int i = 0; i < 2; i++) {
11322
1.24k
                if (axisStr[i] == 'n') {
11323
2
                    axis[i] = north;
11324
1.24k
                } else if (axisStr[i] == 's') {
11325
620
                    axis[i] = south;
11326
622
                } else if (axisStr[i] == 'e') {
11327
20
                    axis[i] = east;
11328
602
                } else if (axisStr[i] == 'w') {
11329
602
                    axis[i] = west;
11330
602
                } else {
11331
0
                    throw ParsingException("Unhandled axis=" + axisStr);
11332
0
                }
11333
1.24k
            }
11334
622
        } else {
11335
0
            throw ParsingException("Unhandled axis=" + axisStr);
11336
0
        }
11337
82.8k
    } else if (iAxisSwap >= 0) {
11338
99
        auto &stepAxisSwap = steps_[iAxisSwap];
11339
99
        const auto &orderStr = getParamValue(stepAxisSwap, "order");
11340
99
        auto orderTab = split(orderStr, ',');
11341
99
        if (orderTab.size() != 2) {
11342
10
            throw ParsingException("Unhandled order=" + orderStr);
11343
10
        }
11344
89
        if (stepAxisSwap.inverted) {
11345
0
            throw ParsingException("Unhandled +inv for +proj=axisswap");
11346
0
        }
11347
11348
244
        for (size_t i = 0; i < 2; i++) {
11349
168
            if (orderTab[i] == "1") {
11350
3
                axis[i] = east;
11351
165
            } else if (orderTab[i] == "-1") {
11352
72
                axis[i] = west;
11353
93
            } else if (orderTab[i] == "2") {
11354
0
                axis[i] = north;
11355
93
            } else if (orderTab[i] == "-2") {
11356
80
                axis[i] = south;
11357
80
            } else {
11358
13
                throw ParsingException("Unhandled order=" + orderStr);
11359
13
            }
11360
168
        }
11361
82.7k
    } else if ((step.name == "krovak" || step.name == "mod_krovak") &&
11362
2.50k
               hasParamValue(step, "czech")) {
11363
374
        axis[0] = std::move(west);
11364
374
        axis[1] = std::move(south);
11365
374
    }
11366
83.4k
    return axis;
11367
83.4k
}
11368
11369
// ---------------------------------------------------------------------------
11370
11371
EllipsoidalCSNNPtr PROJStringParser::Private::buildEllipsoidalCS(
11372
53.8k
    int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) {
11373
53.8k
    auto &step = steps_[iStep];
11374
53.8k
    assert(iUnitConvert < 0 ||
11375
53.8k
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11376
11377
53.8k
    UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE;
11378
53.8k
    if (iUnitConvert >= 0) {
11379
15
        auto &stepUnitConvert = steps_[iUnitConvert];
11380
15
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
11381
15
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
11382
15
        if (stepUnitConvert.inverted) {
11383
0
            std::swap(xy_in, xy_out);
11384
0
        }
11385
15
        if (iUnitConvert < iStep) {
11386
2
            std::swap(xy_in, xy_out);
11387
2
        }
11388
15
        if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" ||
11389
15
            (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) {
11390
15
            throw ParsingException("unhandled values for xy_in and/or xy_out");
11391
15
        }
11392
0
        if (*xy_out == "rad") {
11393
0
            angularUnit = UnitOfMeasure::RADIAN;
11394
0
        } else if (*xy_out == "grad") {
11395
0
            angularUnit = UnitOfMeasure::GRAD;
11396
0
        }
11397
0
    }
11398
11399
53.7k
    std::vector<CoordinateSystemAxisNNPtr> axis = processAxisSwap(
11400
53.7k
        step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis);
11401
53.7k
    CoordinateSystemAxisNNPtr up = CoordinateSystemAxis::create(
11402
53.7k
        util::PropertyMap().set(IdentifiedObject::NAME_KEY,
11403
53.7k
                                AxisName::Ellipsoidal_height),
11404
53.7k
        AxisAbbreviation::h, AxisDirection::UP,
11405
53.7k
        buildUnit(step, "vunits", "vto_meter"));
11406
11407
53.7k
    return (!hasParamValue(step, "geoidgrids") &&
11408
50.6k
            (hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter")))
11409
53.7k
               ? EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1], up)
11410
53.7k
               : EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1]);
11411
53.8k
}
11412
11413
// ---------------------------------------------------------------------------
11414
11415
SphericalCSNNPtr PROJStringParser::Private::buildSphericalCS(
11416
1.50k
    int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) {
11417
1.50k
    auto &step = steps_[iStep];
11418
1.50k
    assert(iUnitConvert < 0 ||
11419
1.50k
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11420
11421
1.50k
    UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE;
11422
1.50k
    if (iUnitConvert >= 0) {
11423
4
        auto &stepUnitConvert = steps_[iUnitConvert];
11424
4
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
11425
4
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
11426
4
        if (stepUnitConvert.inverted) {
11427
0
            std::swap(xy_in, xy_out);
11428
0
        }
11429
4
        if (iUnitConvert < iStep) {
11430
0
            std::swap(xy_in, xy_out);
11431
0
        }
11432
4
        if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" ||
11433
4
            (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) {
11434
4
            throw ParsingException("unhandled values for xy_in and/or xy_out");
11435
4
        }
11436
0
        if (*xy_out == "rad") {
11437
0
            angularUnit = UnitOfMeasure::RADIAN;
11438
0
        } else if (*xy_out == "grad") {
11439
0
            angularUnit = UnitOfMeasure::GRAD;
11440
0
        }
11441
0
    }
11442
11443
1.50k
    std::vector<CoordinateSystemAxisNNPtr> axis = processAxisSwap(
11444
1.50k
        step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis);
11445
11446
1.50k
    return SphericalCS::create(emptyPropertyMap, axis[0], axis[1]);
11447
1.50k
}
11448
11449
// ---------------------------------------------------------------------------
11450
11451
static double getNumericValue(const std::string &paramValue,
11452
47.3k
                              bool *pHasError = nullptr) {
11453
47.3k
    bool success;
11454
47.3k
    double value = c_locale_stod(paramValue, success);
11455
47.3k
    if (pHasError)
11456
21.1k
        *pHasError = !success;
11457
47.3k
    return value;
11458
47.3k
}
11459
11460
// ---------------------------------------------------------------------------
11461
namespace {
11462
55.2k
template <class T> inline void ignoreRetVal(T) {}
11463
} // namespace
11464
11465
GeodeticCRSNNPtr PROJStringParser::Private::buildGeodeticCRS(
11466
55.2k
    int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) {
11467
55.2k
    auto &step = steps_[iStep];
11468
11469
55.2k
    const bool l_isGeographicStep = isGeographicStep(step.name);
11470
55.2k
    const auto &title = l_isGeographicStep ? title_ : emptyString;
11471
11472
    // units=m is often found in the wild.
11473
    // No need to create a extension string for this
11474
55.2k
    ignoreRetVal(hasParamValue(step, "units"));
11475
11476
55.2k
    auto datum = buildDatum(step, title);
11477
11478
55.2k
    auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
11479
55.2k
                                   title.empty() ? "unknown" : title);
11480
11481
55.2k
    if (l_isGeographicStep &&
11482
26.7k
        (hasUnusedParameters(step) ||
11483
25.0k
         getNumericValue(getParamValue(step, "lon_0")) != 0.0)) {
11484
1.71k
        props.set("EXTENSION_PROJ4", projString_);
11485
1.71k
    }
11486
55.2k
    props.set("IMPLICIT_CS", true);
11487
11488
55.2k
    if (!hasParamValue(step, "geoc")) {
11489
53.6k
        auto cs =
11490
53.6k
            buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, ignorePROJAxis);
11491
11492
53.6k
        return GeographicCRS::create(props, datum, cs);
11493
53.6k
    } else {
11494
1.59k
        auto cs =
11495
1.59k
            buildSphericalCS(iStep, iUnitConvert, iAxisSwap, ignorePROJAxis);
11496
11497
1.59k
        return GeodeticCRS::create(props, datum, cs);
11498
1.59k
    }
11499
55.2k
}
11500
11501
// ---------------------------------------------------------------------------
11502
11503
GeodeticCRSNNPtr
11504
3.94k
PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) {
11505
3.94k
    auto &step = steps_[iStep];
11506
11507
3.94k
    assert(isGeocentricStep(step.name) || isTopocentricStep(step.name));
11508
3.94k
    assert(iUnitConvert < 0 ||
11509
3.94k
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11510
11511
3.94k
    const auto &title = title_;
11512
11513
3.94k
    auto datum = buildDatum(step, title);
11514
11515
3.94k
    UnitOfMeasure unit = buildUnit(step, "units", "");
11516
3.94k
    if (iUnitConvert >= 0) {
11517
0
        auto &stepUnitConvert = steps_[iUnitConvert];
11518
0
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
11519
0
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
11520
0
        const std::string *z_in = &getParamValue(stepUnitConvert, "z_in");
11521
0
        const std::string *z_out = &getParamValue(stepUnitConvert, "z_out");
11522
0
        if (stepUnitConvert.inverted) {
11523
0
            std::swap(xy_in, xy_out);
11524
0
            std::swap(z_in, z_out);
11525
0
        }
11526
0
        if (xy_in->empty() || xy_out->empty() || *xy_in != "m" ||
11527
0
            *z_in != "m" || *xy_out != *z_out) {
11528
0
            throw ParsingException(
11529
0
                "unhandled values for xy_in, z_in, xy_out or z_out");
11530
0
        }
11531
11532
0
        const LinearUnitDesc *unitsMatch = nullptr;
11533
0
        try {
11534
0
            double to_meter_value = c_locale_stod(*xy_out);
11535
0
            unitsMatch = getLinearUnits(to_meter_value);
11536
0
            if (unitsMatch == nullptr) {
11537
0
                unit = _buildUnit(to_meter_value);
11538
0
            }
11539
0
        } catch (const std::invalid_argument &) {
11540
0
            unitsMatch = getLinearUnits(*xy_out);
11541
0
            if (!unitsMatch) {
11542
0
                throw ParsingException(
11543
0
                    "unhandled values for xy_in, z_in, xy_out or z_out");
11544
0
            }
11545
0
            unit = _buildUnit(unitsMatch);
11546
0
        }
11547
0
    }
11548
11549
3.94k
    auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
11550
3.94k
                                   title.empty() ? "unknown" : title);
11551
3.94k
    auto cs = CartesianCS::createGeocentric(unit);
11552
11553
3.94k
    if (hasUnusedParameters(step)) {
11554
1.18k
        props.set("EXTENSION_PROJ4", projString_);
11555
1.18k
    }
11556
11557
3.94k
    return GeodeticCRS::create(props, datum, cs);
11558
3.94k
}
11559
11560
// ---------------------------------------------------------------------------
11561
11562
CRSNNPtr
11563
PROJStringParser::Private::buildBoundOrCompoundCRSIfNeeded(int iStep,
11564
58.7k
                                                           CRSNNPtr crs) {
11565
58.7k
    auto &step = steps_[iStep];
11566
58.7k
    const auto &nadgrids = getParamValue(step, "nadgrids");
11567
58.7k
    const auto &towgs84 = getParamValue(step, "towgs84");
11568
    // nadgrids has the priority over towgs84
11569
58.7k
    if (!ignoreNadgrids_ && !nadgrids.empty()) {
11570
2.71k
        crs = BoundCRS::createFromNadgrids(crs, nadgrids);
11571
56.0k
    } else if (!towgs84.empty()) {
11572
3.79k
        std::vector<double> towgs84Values;
11573
3.79k
        const auto tokens = split(towgs84, ',');
11574
17.5k
        for (const auto &str : tokens) {
11575
17.5k
            try {
11576
17.5k
                towgs84Values.push_back(c_locale_stod(str));
11577
17.5k
            } catch (const std::invalid_argument &) {
11578
161
                throw ParsingException("Non numerical value in towgs84 clause");
11579
161
            }
11580
17.5k
        }
11581
11582
3.62k
        if (towgs84Values.size() == 7 && dbContext_) {
11583
1.60k
            if (dbContext_->toWGS84AutocorrectWrongValues(
11584
1.60k
                    towgs84Values[0], towgs84Values[1], towgs84Values[2],
11585
1.60k
                    towgs84Values[3], towgs84Values[4], towgs84Values[5],
11586
1.60k
                    towgs84Values[6])) {
11587
0
                for (auto &pair : step.paramValues) {
11588
0
                    if (ci_equal(pair.key, "towgs84")) {
11589
0
                        pair.value.clear();
11590
0
                        for (int i = 0; i < 7; ++i) {
11591
0
                            if (i > 0)
11592
0
                                pair.value += ',';
11593
0
                            pair.value += internal::toString(towgs84Values[i]);
11594
0
                        }
11595
0
                        break;
11596
0
                    }
11597
0
                }
11598
0
            }
11599
1.60k
        }
11600
11601
3.62k
        crs = BoundCRS::createFromTOWGS84(crs, towgs84Values);
11602
3.62k
    }
11603
11604
58.5k
    const auto &geoidgrids = getParamValue(step, "geoidgrids");
11605
58.5k
    if (!geoidgrids.empty()) {
11606
3.41k
        auto vdatum = VerticalReferenceFrame::create(
11607
3.41k
            PropertyMap().set(common::IdentifiedObject::NAME_KEY,
11608
3.41k
                              "unknown using geoidgrids=" + geoidgrids));
11609
11610
3.41k
        const UnitOfMeasure unit = buildUnit(step, "vunits", "vto_meter");
11611
11612
3.41k
        auto vcrs =
11613
3.41k
            VerticalCRS::create(createMapWithUnknownName(), vdatum,
11614
3.41k
                                VerticalCS::createGravityRelatedHeight(unit));
11615
11616
3.41k
        CRSNNPtr geogCRS = GeographicCRS::EPSG_4979; // default
11617
3.41k
        const auto &geoid_crs = getParamValue(step, "geoid_crs");
11618
3.41k
        if (!geoid_crs.empty()) {
11619
2.15k
            if (geoid_crs == "WGS84") {
11620
                // nothing to do
11621
1.12k
            } else if (geoid_crs == "horizontal_crs") {
11622
994
                auto geogCRSOfCompoundCRS = crs->extractGeographicCRS();
11623
994
                if (geogCRSOfCompoundCRS &&
11624
976
                    geogCRSOfCompoundCRS->primeMeridian()
11625
976
                            ->longitude()
11626
976
                            .getSIValue() == 0 &&
11627
976
                    geogCRSOfCompoundCRS->coordinateSystem()
11628
976
                            ->axisList()[0]
11629
976
                            ->unit() == UnitOfMeasure::DEGREE) {
11630
976
                    geogCRS = geogCRSOfCompoundCRS->promoteTo3D(std::string(),
11631
976
                                                                nullptr);
11632
976
                } else if (geogCRSOfCompoundCRS) {
11633
0
                    auto geogCRSOfCompoundCRSDatum =
11634
0
                        geogCRSOfCompoundCRS->datumNonNull(nullptr);
11635
0
                    geogCRS = GeographicCRS::create(
11636
0
                        createMapWithUnknownName(),
11637
0
                        datum::GeodeticReferenceFrame::create(
11638
0
                            util::PropertyMap().set(
11639
0
                                common::IdentifiedObject::NAME_KEY,
11640
0
                                geogCRSOfCompoundCRSDatum->nameStr() +
11641
0
                                    " (with Greenwich prime meridian)"),
11642
0
                            geogCRSOfCompoundCRSDatum->ellipsoid(),
11643
0
                            util::optional<std::string>(),
11644
0
                            datum::PrimeMeridian::GREENWICH),
11645
0
                        EllipsoidalCS::createLongitudeLatitudeEllipsoidalHeight(
11646
0
                            UnitOfMeasure::DEGREE, UnitOfMeasure::METRE));
11647
0
                }
11648
994
            } else {
11649
37
                throw ParsingException("Unsupported value for geoid_crs: "
11650
37
                                       "should be 'WGS84' or 'horizontal_crs'");
11651
37
            }
11652
2.15k
        }
11653
3.38k
        auto transformation =
11654
3.38k
            Transformation::createGravityRelatedHeightToGeographic3D(
11655
3.38k
                PropertyMap().set(IdentifiedObject::NAME_KEY,
11656
3.38k
                                  "unknown to " + geogCRS->nameStr() +
11657
3.38k
                                      " ellipsoidal height"),
11658
3.38k
                VerticalCRS::create(createMapWithUnknownName(), vdatum,
11659
3.38k
                                    VerticalCS::createGravityRelatedHeight(
11660
3.38k
                                        common::UnitOfMeasure::METRE)),
11661
3.38k
                geogCRS, nullptr, geoidgrids,
11662
3.38k
                std::vector<PositionalAccuracyNNPtr>());
11663
3.38k
        auto boundvcrs = BoundCRS::create(vcrs, geogCRS, transformation);
11664
11665
3.38k
        crs = CompoundCRS::create(createMapWithUnknownName(),
11666
3.38k
                                  std::vector<CRSNNPtr>{crs, boundvcrs});
11667
3.38k
    }
11668
11669
58.5k
    return crs;
11670
58.5k
}
11671
11672
// ---------------------------------------------------------------------------
11673
11674
static double getAngularValue(const std::string &paramValue,
11675
24.2k
                              bool *pHasError = nullptr) {
11676
24.2k
    char *endptr = nullptr;
11677
24.2k
    double value = dmstor(paramValue.c_str(), &endptr) * RAD_TO_DEG;
11678
24.2k
    if (value == HUGE_VAL || endptr != paramValue.c_str() + paramValue.size()) {
11679
279
        if (pHasError)
11680
271
            *pHasError = true;
11681
279
        return 0.0;
11682
279
    }
11683
23.9k
    if (pHasError)
11684
20.7k
        *pHasError = false;
11685
23.9k
    return value;
11686
24.2k
}
11687
11688
// ---------------------------------------------------------------------------
11689
11690
44.6k
static bool is_in_stringlist(const std::string &str, const char *stringlist) {
11691
44.6k
    if (str.empty())
11692
0
        return false;
11693
44.6k
    const char *haystack = stringlist;
11694
214k
    while (true) {
11695
214k
        const char *res = strstr(haystack, str.c_str());
11696
214k
        if (res == nullptr)
11697
35.2k
            return false;
11698
179k
        if ((res == stringlist || res[-1] == ',') &&
11699
37.4k
            (res[str.size()] == ',' || res[str.size()] == '\0'))
11700
9.42k
            return true;
11701
169k
        haystack += str.size();
11702
169k
    }
11703
44.6k
}
11704
11705
// ---------------------------------------------------------------------------
11706
11707
CRSNNPtr
11708
PROJStringParser::Private::buildProjectedCRS(int iStep,
11709
                                             const GeodeticCRSNNPtr &geodCRS,
11710
28.5k
                                             int iUnitConvert, int iAxisSwap) {
11711
28.5k
    auto &step = steps_[iStep];
11712
28.5k
    const auto mappings = getMappingsFromPROJName(step.name);
11713
28.5k
    const MethodMapping *mapping = mappings.empty() ? nullptr : mappings[0];
11714
11715
28.5k
    bool foundStrictlyMatchingMapping = false;
11716
28.5k
    if (mappings.size() >= 2) {
11717
        // To distinguish for example +ortho from +ortho +f=0
11718
9.91k
        bool allMappingsHaveAuxParam = true;
11719
28.5k
        for (const auto *mappingIter : mappings) {
11720
28.5k
            if (mappingIter->proj_name_aux == nullptr) {
11721
19.8k
                allMappingsHaveAuxParam = false;
11722
19.8k
            }
11723
28.5k
            if (mappingIter->proj_name_aux != nullptr &&
11724
8.65k
                strchr(mappingIter->proj_name_aux, '=') == nullptr &&
11725
2.87k
                hasParamValue(step, mappingIter->proj_name_aux)) {
11726
169
                foundStrictlyMatchingMapping = true;
11727
169
                mapping = mappingIter;
11728
169
                break;
11729
28.3k
            } else if (mappingIter->proj_name_aux != nullptr &&
11730
8.48k
                       strchr(mappingIter->proj_name_aux, '=') != nullptr) {
11731
5.78k
                const auto tokens = split(mappingIter->proj_name_aux, '=');
11732
5.78k
                if (tokens.size() == 2 &&
11733
5.78k
                    getParamValue(step, tokens[0]) == tokens[1]) {
11734
486
                    foundStrictlyMatchingMapping = true;
11735
486
                    mapping = mappingIter;
11736
486
                    break;
11737
486
                }
11738
5.78k
            }
11739
28.5k
        }
11740
9.91k
        if (allMappingsHaveAuxParam && !foundStrictlyMatchingMapping) {
11741
78
            mapping = nullptr;
11742
78
        }
11743
9.91k
    }
11744
11745
28.5k
    if (mapping && !foundStrictlyMatchingMapping) {
11746
19.8k
        mapping = selectSphericalOrEllipsoidal(mapping, geodCRS);
11747
19.8k
    }
11748
11749
28.5k
    assert(isProjectedStep(step.name));
11750
28.5k
    assert(iUnitConvert < 0 ||
11751
28.5k
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11752
11753
28.5k
    const auto &title = title_;
11754
11755
28.5k
    if (!buildPrimeMeridian(step)->longitude()._isEquivalentTo(
11756
28.5k
            geodCRS->primeMeridian()->longitude(),
11757
28.5k
            util::IComparable::Criterion::EQUIVALENT)) {
11758
0
        throw ParsingException("inconsistent pm values between projectedCRS "
11759
0
                               "and its base geographicalCRS");
11760
0
    }
11761
11762
28.5k
    auto axisType = AxisType::REGULAR;
11763
28.5k
    bool bWebMercator = false;
11764
28.5k
    std::string webMercatorName("WGS 84 / Pseudo-Mercator");
11765
11766
28.5k
    if (step.name == "tmerc" &&
11767
3.28k
        ((getParamValue(step, "axis") == "wsu" && iAxisSwap < 0) ||
11768
3.26k
         (iAxisSwap > 0 &&
11769
59
          getParamValue(steps_[iAxisSwap], "order") == "-1,-2"))) {
11770
59
        mapping =
11771
59
            getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED);
11772
28.5k
    } else if (step.name == "etmerc") {
11773
56
        mapping = getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR);
11774
28.4k
    } else if (step.name == "lcc") {
11775
881
        const auto &lat_0 = getParamValue(step, "lat_0");
11776
881
        const auto &lat_1 = getParamValue(step, "lat_1");
11777
881
        const auto &lat_2 = getParamValue(step, "lat_2");
11778
881
        const auto &k = getParamValueK(step);
11779
881
        if (lat_2.empty() && !lat_0.empty() && !lat_1.empty()) {
11780
511
            if (lat_0 == lat_1 ||
11781
                // For some reason with gcc 5.3.1-14ubuntu2 32bit, the following
11782
                // comparison returns false even if lat_0 == lat_1. Smells like
11783
                // a compiler bug
11784
373
                getAngularValue(lat_0) == getAngularValue(lat_1)) {
11785
373
                mapping =
11786
373
                    getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
11787
373
            } else {
11788
138
                mapping = getMapping(
11789
138
                    EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP_VARIANT_B);
11790
138
            }
11791
511
        } else if (!k.empty() && getNumericValue(k) != 1.0) {
11792
3
            mapping = getMapping(
11793
3
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN);
11794
367
        } else {
11795
367
            mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
11796
367
        }
11797
27.6k
    } else if (step.name == "aeqd" && hasParamValue(step, "guam")) {
11798
14
        mapping = getMapping(EPSG_CODE_METHOD_GUAM_PROJECTION);
11799
27.5k
    } else if (step.name == "geos" && getParamValue(step, "sweep") == "x") {
11800
71
        mapping =
11801
71
            getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X);
11802
27.5k
    } else if (step.name == "geos") {
11803
158
        mapping =
11804
158
            getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y);
11805
27.3k
    } else if (step.name == "omerc") {
11806
453
        if (hasParamValue(step, "no_rot")) {
11807
0
            mapping = nullptr;
11808
453
        } else if (hasParamValue(step, "no_uoff") ||
11809
300
                   hasParamValue(step, "no_off")) {
11810
223
            mapping =
11811
223
                getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A);
11812
230
        } else if (hasParamValue(step, "lat_1") &&
11813
66
                   hasParamValue(step, "lon_1") &&
11814
6
                   hasParamValue(step, "lat_2") &&
11815
5
                   hasParamValue(step, "lon_2")) {
11816
2
            mapping = getMapping(
11817
2
                PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN);
11818
228
        } else {
11819
228
            mapping =
11820
228
                getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B);
11821
228
        }
11822
26.9k
    } else if (step.name == "somerc") {
11823
230
        mapping =
11824
230
            getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B);
11825
230
        if (!hasParamValue(step, "alpha") && !hasParamValue(step, "gamma") &&
11826
115
            !hasParamValue(step, "lonc")) {
11827
115
            step.paramValues.emplace_back(Step::KeyValue("alpha", "90"));
11828
115
            step.paramValues.emplace_back(Step::KeyValue("gamma", "90"));
11829
115
            step.paramValues.emplace_back(
11830
115
                Step::KeyValue("lonc", getParamValue(step, "lon_0")));
11831
115
        }
11832
26.6k
    } else if (step.name == "krovak" &&
11833
1.41k
               ((iAxisSwap < 0 && getParamValue(step, "axis") == "swu" &&
11834
381
                 !hasParamValue(step, "czech")) ||
11835
1.12k
                (iAxisSwap > 0 &&
11836
0
                 getParamValue(steps_[iAxisSwap], "order") == "-2,-1" &&
11837
287
                 !hasParamValue(step, "czech")))) {
11838
287
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
11839
26.3k
    } else if (step.name == "krovak" && iAxisSwap < 0 &&
11840
1.12k
               hasParamValue(step, "czech") && !hasParamValue(step, "axis")) {
11841
140
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
11842
26.2k
    } else if (step.name == "mod_krovak" &&
11843
39
               ((iAxisSwap < 0 && getParamValue(step, "axis") == "swu" &&
11844
5
                 !hasParamValue(step, "czech")) ||
11845
34
                (iAxisSwap > 0 &&
11846
0
                 getParamValue(steps_[iAxisSwap], "order") == "-2,-1" &&
11847
5
                 !hasParamValue(step, "czech")))) {
11848
5
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK_MODIFIED);
11849
26.2k
    } else if (step.name == "mod_krovak" && iAxisSwap < 0 &&
11850
34
               hasParamValue(step, "czech") && !hasParamValue(step, "axis")) {
11851
0
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK_MODIFIED);
11852
26.2k
    } else if (step.name == "merc") {
11853
1.36k
        if (hasParamValue(step, "a") && hasParamValue(step, "b") &&
11854
672
            getParamValue(step, "a") == getParamValue(step, "b") &&
11855
600
            (!hasParamValue(step, "lat_ts") ||
11856
600
             getAngularValue(getParamValue(step, "lat_ts")) == 0.0) &&
11857
600
            getNumericValue(getParamValueK(step)) == 1.0 &&
11858
600
            getParamValue(step, "nadgrids") == "@null") {
11859
402
            mapping = getMapping(
11860
402
                EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR);
11861
3.62k
            for (size_t i = 0; i < step.paramValues.size(); ++i) {
11862
3.62k
                if (ci_equal(step.paramValues[i].key, "nadgrids")) {
11863
402
                    ignoreNadgrids_ = true;
11864
402
                    break;
11865
402
                }
11866
3.62k
            }
11867
402
            if (getNumericValue(getParamValue(step, "a")) == 6378137 &&
11868
402
                getAngularValue(getParamValue(step, "lon_0")) == 0.0 &&
11869
400
                getAngularValue(getParamValue(step, "lat_0")) == 0.0 &&
11870
398
                getAngularValue(getParamValue(step, "x_0")) == 0.0 &&
11871
398
                getAngularValue(getParamValue(step, "y_0")) == 0.0) {
11872
398
                bWebMercator = true;
11873
398
                if (hasParamValue(step, "units") &&
11874
398
                    getParamValue(step, "units") != "m") {
11875
48
                    webMercatorName +=
11876
48
                        " (unit " + getParamValue(step, "units") + ')';
11877
48
                }
11878
398
            }
11879
962
        } else if (hasParamValue(step, "lat_ts")) {
11880
210
            if (hasParamValue(step, "R_C") &&
11881
0
                !geodCRS->ellipsoid()->isSphere() &&
11882
0
                getAngularValue(getParamValue(step, "lat_ts")) != 0) {
11883
0
                throw ParsingException("lat_ts != 0 not supported for "
11884
0
                                       "spherical Mercator on an ellipsoid");
11885
0
            }
11886
210
            mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
11887
752
        } else if (hasParamValue(step, "R_C")) {
11888
0
            const auto &k = getParamValueK(step);
11889
0
            if (!k.empty() && getNumericValue(k) != 1.0) {
11890
0
                if (geodCRS->ellipsoid()->isSphere()) {
11891
0
                    mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A);
11892
0
                } else {
11893
0
                    throw ParsingException(
11894
0
                        "k_0 != 1 not supported for spherical Mercator on an "
11895
0
                        "ellipsoid");
11896
0
                }
11897
0
            } else {
11898
0
                mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_SPHERICAL);
11899
0
            }
11900
752
        } else {
11901
752
            mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A);
11902
752
        }
11903
24.8k
    } else if (step.name == "stere") {
11904
425
        if (hasParamValue(step, "lat_0") &&
11905
114
            std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) -
11906
114
                      90.0) < 1e-10) {
11907
45
            const double lat_0 = getAngularValue(getParamValue(step, "lat_0"));
11908
45
            if (lat_0 > 0) {
11909
42
                axisType = AxisType::NORTH_POLE;
11910
42
            } else {
11911
3
                axisType = AxisType::SOUTH_POLE;
11912
3
            }
11913
45
            const auto &lat_ts = getParamValue(step, "lat_ts");
11914
45
            const auto &k = getParamValueK(step);
11915
45
            if (!lat_ts.empty() &&
11916
7
                std::fabs(getAngularValue(lat_ts) - lat_0) > 1e-10 &&
11917
7
                !k.empty() && std::fabs(getNumericValue(k) - 1) > 1e-10) {
11918
1
                throw ParsingException("lat_ts != lat_0 and k != 1 not "
11919
1
                                       "supported for Polar Stereographic");
11920
1
            }
11921
44
            if (!lat_ts.empty() &&
11922
6
                (k.empty() || std::fabs(getNumericValue(k) - 1) < 1e-10)) {
11923
6
                mapping =
11924
6
                    getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B);
11925
38
            } else {
11926
38
                mapping =
11927
38
                    getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A);
11928
38
            }
11929
380
        } else {
11930
380
            mapping = getMapping(PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC);
11931
380
        }
11932
24.4k
    } else if (step.name == "laea") {
11933
374
        if (hasParamValue(step, "lat_0") &&
11934
291
            std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) -
11935
291
                      90.0) < 1e-10) {
11936
71
            const double lat_0 = getAngularValue(getParamValue(step, "lat_0"));
11937
71
            if (lat_0 > 0) {
11938
69
                axisType = AxisType::NORTH_POLE;
11939
69
            } else {
11940
2
                axisType = AxisType::SOUTH_POLE;
11941
2
            }
11942
71
        }
11943
24.0k
    } else if (step.name == "ortho") {
11944
658
        const std::string &k = getParamValueK(step);
11945
658
        if ((!k.empty() && getNumericValue(k) != 1.0) ||
11946
630
            (hasParamValue(step, "alpha") &&
11947
28
             getNumericValue(getParamValue(step, "alpha")) != 0.0)) {
11948
28
            mapping = getMapping(EPSG_CODE_METHOD_LOCAL_ORTHOGRAPHIC);
11949
28
        }
11950
658
    }
11951
11952
28.5k
    UnitOfMeasure unit = buildUnit(step, "units", "to_meter");
11953
28.5k
    if (iUnitConvert >= 0) {
11954
354
        auto &stepUnitConvert = steps_[iUnitConvert];
11955
354
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
11956
354
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
11957
354
        if (stepUnitConvert.inverted) {
11958
0
            std::swap(xy_in, xy_out);
11959
0
        }
11960
354
        if (xy_in->empty() || xy_out->empty() || *xy_in != "m") {
11961
354
            if (step.name != "ob_tran") {
11962
2
                throw ParsingException(
11963
2
                    "unhandled values for xy_in and/or xy_out");
11964
2
            }
11965
354
        }
11966
11967
352
        const LinearUnitDesc *unitsMatch = nullptr;
11968
352
        try {
11969
352
            double to_meter_value = c_locale_stod(*xy_out);
11970
352
            unitsMatch = getLinearUnits(to_meter_value);
11971
352
            if (unitsMatch == nullptr) {
11972
0
                unit = _buildUnit(to_meter_value);
11973
0
            }
11974
352
        } catch (const std::invalid_argument &) {
11975
352
            unitsMatch = getLinearUnits(*xy_out);
11976
352
            if (!unitsMatch) {
11977
352
                if (step.name != "ob_tran") {
11978
0
                    throw ParsingException(
11979
0
                        "unhandled values for xy_in and/or xy_out");
11980
0
                }
11981
352
            } else {
11982
0
                unit = _buildUnit(unitsMatch);
11983
0
            }
11984
352
        }
11985
352
    }
11986
11987
28.5k
    ConversionPtr conv;
11988
11989
28.5k
    auto mapWithUnknownName = createMapWithUnknownName();
11990
11991
28.5k
    if (step.name == "utm") {
11992
1.21k
        const int zone = std::atoi(getParamValue(step, "zone").c_str());
11993
1.21k
        const bool north = !hasParamValue(step, "south");
11994
1.21k
        conv =
11995
1.21k
            Conversion::createUTM(emptyPropertyMap, zone, north).as_nullable();
11996
27.3k
    } else if (mapping) {
11997
11998
20.7k
        auto methodMap =
11999
20.7k
            PropertyMap().set(IdentifiedObject::NAME_KEY, mapping->wkt2_name);
12000
20.7k
        if (mapping->epsg_code) {
12001
10.9k
            methodMap.set(Identifier::CODESPACE_KEY, Identifier::EPSG)
12002
10.9k
                .set(Identifier::CODE_KEY, mapping->epsg_code);
12003
10.9k
        }
12004
20.7k
        std::vector<OperationParameterNNPtr> parameters;
12005
20.7k
        std::vector<ParameterValueNNPtr> values;
12006
107k
        for (int i = 0; mapping->params[i] != nullptr; i++) {
12007
87.0k
            const auto *param = mapping->params[i];
12008
87.0k
            std::string proj_name(param->proj_name ? param->proj_name : "");
12009
87.0k
            const std::string *paramValue =
12010
87.0k
                (proj_name == "k" || proj_name == "k_0") ? &getParamValueK(step)
12011
87.0k
                : !proj_name.empty() ? &getParamValue(step, proj_name)
12012
79.8k
                                     : &emptyString;
12013
87.0k
            double value = 0;
12014
87.0k
            if (!paramValue->empty()) {
12015
34.3k
                bool hasError = false;
12016
34.3k
                if (param->unit_type == UnitOfMeasure::Type::ANGULAR) {
12017
14.7k
                    value = getAngularValue(*paramValue, &hasError);
12018
19.5k
                } else {
12019
19.5k
                    value = getNumericValue(*paramValue, &hasError);
12020
19.5k
                }
12021
34.3k
                if (hasError) {
12022
94
                    throw ParsingException("invalid value for " + proj_name);
12023
94
                }
12024
34.3k
            }
12025
            // For omerc, if gamma is missing, the default value is
12026
            // alpha
12027
52.7k
            else if (step.name == "omerc" && proj_name == "gamma") {
12028
396
                paramValue = &getParamValue(step, "alpha");
12029
396
                if (!paramValue->empty()) {
12030
264
                    value = getAngularValue(*paramValue);
12031
264
                }
12032
52.3k
            } else if (step.name == "krovak" || step.name == "mod_krovak") {
12033
                // Keep it in sync with defaults of krovak.cpp
12034
6.39k
                if (param->epsg_code ==
12035
6.39k
                    EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE) {
12036
828
                    value = 49.5;
12037
5.57k
                } else if (param->epsg_code ==
12038
5.57k
                           EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN) {
12039
828
                    value = 24.833333333333333333;
12040
4.74k
                } else if (param->epsg_code ==
12041
4.74k
                           EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS) {
12042
828
                    value = 30.28813975277777776;
12043
3.91k
                } else if (
12044
3.91k
                    param->epsg_code ==
12045
3.91k
                    EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL) {
12046
1.44k
                    value = 78.5;
12047
2.46k
                } else if (
12048
2.46k
                    param->epsg_code ==
12049
2.46k
                    EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL) {
12050
820
                    value = 0.9999;
12051
820
                }
12052
45.9k
            } else if (step.name == "cea" && proj_name == "lat_ts") {
12053
273
                paramValue = &getParamValueK(step);
12054
273
                if (!paramValue->empty()) {
12055
13
                    bool hasError = false;
12056
13
                    const double k = getNumericValue(*paramValue, &hasError);
12057
13
                    if (hasError) {
12058
0
                        throw ParsingException("invalid value for k/k_0");
12059
0
                    }
12060
13
                    if (k >= 0 && k <= 1) {
12061
6
                        const double es =
12062
6
                            geodCRS->ellipsoid()->squaredEccentricity();
12063
6
                        if (es < 0 || es == 1) {
12064
0
                            throw ParsingException("Invalid flattening");
12065
0
                        }
12066
6
                        value =
12067
6
                            Angle(acos(k * sqrt((1 - es) / (1 - k * k * es))),
12068
6
                                  UnitOfMeasure::RADIAN)
12069
6
                                .convertToUnit(UnitOfMeasure::DEGREE);
12070
7
                    } else {
12071
7
                        throw ParsingException("k/k_0 should be in [0,1]");
12072
7
                    }
12073
13
                }
12074
45.6k
            } else if (param->unit_type == UnitOfMeasure::Type::SCALE) {
12075
3.08k
                value = 1;
12076
42.5k
            } else if (step.name == "peirce_q" && proj_name == "lat_0") {
12077
0
                value = 90;
12078
0
            }
12079
12080
86.9k
            PropertyMap propertiesParameter;
12081
86.9k
            propertiesParameter.set(IdentifiedObject::NAME_KEY,
12082
86.9k
                                    param->wkt2_name);
12083
86.9k
            if (param->epsg_code) {
12084
86.0k
                propertiesParameter.set(Identifier::CODE_KEY, param->epsg_code);
12085
86.0k
                propertiesParameter.set(Identifier::CODESPACE_KEY,
12086
86.0k
                                        Identifier::EPSG);
12087
86.0k
            }
12088
86.9k
            parameters.push_back(
12089
86.9k
                OperationParameter::create(propertiesParameter));
12090
            // In PROJ convention, angular parameters are always in degree
12091
            // and linear parameters always in metre.
12092
86.9k
            double valRounded =
12093
86.9k
                param->unit_type == UnitOfMeasure::Type::LINEAR
12094
86.9k
                    ? Length(value, UnitOfMeasure::METRE).convertToUnit(unit)
12095
86.9k
                    : value;
12096
86.9k
            if (std::fabs(valRounded - std::round(valRounded)) < 1e-8) {
12097
76.5k
                valRounded = std::round(valRounded);
12098
76.5k
            }
12099
86.9k
            values.push_back(ParameterValue::create(
12100
86.9k
                Measure(valRounded,
12101
86.9k
                        param->unit_type == UnitOfMeasure::Type::ANGULAR
12102
86.9k
                            ? UnitOfMeasure::DEGREE
12103
86.9k
                        : param->unit_type == UnitOfMeasure::Type::LINEAR ? unit
12104
48.8k
                        : param->unit_type == UnitOfMeasure::Type::SCALE
12105
7.15k
                            ? UnitOfMeasure::SCALE_UNITY
12106
7.15k
                            : UnitOfMeasure::NONE)));
12107
86.9k
        }
12108
12109
20.6k
        if (step.name == "tmerc" && hasParamValue(step, "approx")) {
12110
115
            methodMap.set("proj_method", "tmerc approx");
12111
20.5k
        } else if (step.name == "utm" && hasParamValue(step, "approx")) {
12112
0
            methodMap.set("proj_method", "utm approx");
12113
0
        }
12114
12115
20.6k
        conv = Conversion::create(mapWithUnknownName, methodMap, parameters,
12116
20.6k
                                  values)
12117
20.6k
                   .as_nullable();
12118
20.6k
    } else {
12119
6.60k
        std::vector<OperationParameterNNPtr> parameters;
12120
6.60k
        std::vector<ParameterValueNNPtr> values;
12121
6.60k
        std::string methodName = "PROJ " + step.name;
12122
30.0k
        for (const auto &param : step.paramValues) {
12123
30.0k
            if (is_in_stringlist(param.key,
12124
30.0k
                                 "wktext,no_defs,datum,ellps,a,b,R,f,rf,"
12125
30.0k
                                 "towgs84,nadgrids,geoidgrids,"
12126
30.0k
                                 "units,to_meter,vunits,vto_meter,type")) {
12127
7.81k
                continue;
12128
7.81k
            }
12129
22.2k
            if (param.value.empty()) {
12130
9.90k
                methodName += " " + param.key;
12131
12.3k
            } else if (isalpha(param.value[0])) {
12132
4.49k
                methodName += " " + param.key + "=" + param.value;
12133
7.83k
            } else {
12134
7.83k
                parameters.push_back(OperationParameter::create(
12135
7.83k
                    PropertyMap().set(IdentifiedObject::NAME_KEY, param.key)));
12136
7.83k
                bool hasError = false;
12137
7.83k
                if (is_in_stringlist(param.key, "x_0,y_0,h,h_0")) {
12138
1.06k
                    double value = getNumericValue(param.value, &hasError);
12139
1.06k
                    values.push_back(ParameterValue::create(
12140
1.06k
                        Measure(value, UnitOfMeasure::METRE)));
12141
6.76k
                } else if (is_in_stringlist(
12142
6.76k
                               param.key,
12143
6.76k
                               "k,k_0,"
12144
6.76k
                               "north_square,south_square," // rhealpix
12145
6.76k
                               "n,m,"                       // sinu
12146
6.76k
                               "q,"                         // urm5
12147
6.76k
                               "path,lsat,"                 // lsat
12148
6.76k
                               "W,M,"                       // hammer
12149
6.76k
                               "aperture,resolution,"       // isea
12150
6.76k
                               )) {
12151
547
                    double value = getNumericValue(param.value, &hasError);
12152
547
                    values.push_back(ParameterValue::create(
12153
547
                        Measure(value, UnitOfMeasure::SCALE_UNITY)));
12154
6.21k
                } else {
12155
6.21k
                    double value = getAngularValue(param.value, &hasError);
12156
6.21k
                    values.push_back(ParameterValue::create(
12157
6.21k
                        Measure(value, UnitOfMeasure::DEGREE)));
12158
6.21k
                }
12159
7.83k
                if (hasError) {
12160
206
                    throw ParsingException("invalid value for " + param.key);
12161
206
                }
12162
7.83k
            }
12163
22.2k
        }
12164
6.40k
        conv = Conversion::create(
12165
6.40k
                   mapWithUnknownName,
12166
6.40k
                   PropertyMap().set(IdentifiedObject::NAME_KEY, methodName),
12167
6.40k
                   parameters, values)
12168
6.40k
                   .as_nullable();
12169
12170
6.40k
        for (const char *substr :
12171
6.40k
             {"PROJ ob_tran o_proj=longlat", "PROJ ob_tran o_proj=lonlat",
12172
25.4k
              "PROJ ob_tran o_proj=latlon", "PROJ ob_tran o_proj=latlong"}) {
12173
25.4k
            if (starts_with(methodName, substr)) {
12174
122
                auto geogCRS =
12175
122
                    util::nn_dynamic_pointer_cast<GeographicCRS>(geodCRS);
12176
122
                if (geogCRS) {
12177
122
                    return DerivedGeographicCRS::create(
12178
122
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
12179
122
                                          "unnamed"),
12180
122
                        NN_NO_CHECK(geogCRS), NN_NO_CHECK(conv),
12181
122
                        buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap,
12182
122
                                           false));
12183
122
                }
12184
122
            }
12185
25.4k
        }
12186
6.40k
    }
12187
12188
28.1k
    std::vector<CoordinateSystemAxisNNPtr> axis =
12189
28.1k
        processAxisSwap(step, unit, iAxisSwap, axisType, false);
12190
12191
28.1k
    auto csGeodCRS = geodCRS->coordinateSystem();
12192
28.1k
    auto cs = csGeodCRS->axisList().size() == 2
12193
28.1k
                  ? CartesianCS::create(emptyPropertyMap, axis[0], axis[1])
12194
28.1k
                  : CartesianCS::create(emptyPropertyMap, axis[0], axis[1],
12195
1.87k
                                        csGeodCRS->axisList()[2]);
12196
28.1k
    if (isTopocentricStep(step.name)) {
12197
118
        cs = CartesianCS::create(
12198
118
            emptyPropertyMap,
12199
118
            createAxis("topocentric East", "U", AxisDirection::EAST, unit),
12200
118
            createAxis("topocentric North", "V", AxisDirection::NORTH, unit),
12201
118
            createAxis("topocentric Up", "W", AxisDirection::UP, unit));
12202
118
    }
12203
12204
28.1k
    auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
12205
28.1k
                                   title.empty() ? "unknown" : title);
12206
28.1k
    if (hasUnusedParameters(step)) {
12207
5.25k
        props.set("EXTENSION_PROJ4", projString_);
12208
5.25k
    }
12209
12210
28.1k
    props.set("IMPLICIT_CS", true);
12211
12212
28.1k
    CRSNNPtr crs =
12213
28.1k
        bWebMercator
12214
28.1k
            ? createPseudoMercator(
12215
398
                  props.set(IdentifiedObject::NAME_KEY, webMercatorName), cs)
12216
28.1k
            : ProjectedCRS::create(props, geodCRS, NN_NO_CHECK(conv), cs);
12217
12218
28.1k
    return crs;
12219
28.5k
}
12220
12221
//! @endcond
12222
12223
// ---------------------------------------------------------------------------
12224
12225
//! @cond Doxygen_Suppress
12226
static const metadata::ExtentPtr nullExtent{};
12227
12228
1.21k
static const metadata::ExtentPtr &getExtent(const crs::CRS *crs) {
12229
1.21k
    const auto &domains = crs->domains();
12230
1.21k
    if (!domains.empty()) {
12231
309
        return domains[0]->domainOfValidity();
12232
309
    }
12233
906
    return nullExtent;
12234
1.21k
}
12235
12236
//! @endcond
12237
12238
namespace {
12239
struct PJContextHolder {
12240
    PJ_CONTEXT *ctx_;
12241
    bool bFree_;
12242
12243
14.5k
    PJContextHolder(PJ_CONTEXT *ctx, bool bFree) : ctx_(ctx), bFree_(bFree) {}
12244
14.5k
    ~PJContextHolder() {
12245
14.5k
        if (bFree_)
12246
1.76k
            proj_context_destroy(ctx_);
12247
14.5k
    }
12248
    PJContextHolder(const PJContextHolder &) = delete;
12249
    PJContextHolder &operator=(const PJContextHolder &) = delete;
12250
};
12251
} // namespace
12252
12253
// ---------------------------------------------------------------------------
12254
12255
/** \brief Instantiate a sub-class of BaseObject from a PROJ string.
12256
 *
12257
 * The projString must contain +type=crs for the object to be detected as a
12258
 * CRS instead of a CoordinateOperation.
12259
 *
12260
 * @throw ParsingException if the string cannot be parsed.
12261
 */
12262
BaseObjectNNPtr
12263
57.0k
PROJStringParser::createFromPROJString(const std::string &projString) {
12264
12265
    // In some abnormal situations involving init=epsg:XXXX syntax, we could
12266
    // have infinite loop
12267
57.0k
    if (d->ctx_ &&
12268
43.9k
        d->ctx_->projStringParserCreateFromPROJStringRecursionCounter == 2) {
12269
36
        throw ParsingException(
12270
36
            "Infinite recursion in PROJStringParser::createFromPROJString()");
12271
36
    }
12272
12273
56.9k
    d->steps_.clear();
12274
56.9k
    d->title_.clear();
12275
56.9k
    d->globalParamValues_.clear();
12276
56.9k
    d->projString_ = projString;
12277
56.9k
    PROJStringSyntaxParser(projString, d->steps_, d->globalParamValues_,
12278
56.9k
                           d->title_);
12279
12280
56.9k
    if (d->steps_.empty()) {
12281
824
        const auto &vunits = d->getGlobalParamValue("vunits");
12282
824
        const auto &vto_meter = d->getGlobalParamValue("vto_meter");
12283
824
        if (!vunits.empty() || !vto_meter.empty()) {
12284
137
            Step fakeStep;
12285
137
            if (!vunits.empty()) {
12286
103
                fakeStep.paramValues.emplace_back(
12287
103
                    Step::KeyValue("vunits", vunits));
12288
103
            }
12289
137
            if (!vto_meter.empty()) {
12290
34
                fakeStep.paramValues.emplace_back(
12291
34
                    Step::KeyValue("vto_meter", vto_meter));
12292
34
            }
12293
137
            auto vdatum =
12294
137
                VerticalReferenceFrame::create(createMapWithUnknownName());
12295
137
            auto vcrs = VerticalCRS::create(
12296
137
                createMapWithUnknownName(), vdatum,
12297
137
                VerticalCS::createGravityRelatedHeight(
12298
137
                    d->buildUnit(fakeStep, "vunits", "vto_meter")));
12299
137
            return vcrs;
12300
137
        }
12301
824
    }
12302
12303
56.8k
    const bool isGeocentricCRS =
12304
56.8k
        ((d->steps_.size() == 1 &&
12305
52.8k
          d->getParamValue(d->steps_[0], "type") == "crs") ||
12306
15.7k
         (d->steps_.size() == 2 && d->steps_[1].name == "unitconvert")) &&
12307
41.3k
        !d->steps_[0].inverted && isGeocentricStep(d->steps_[0].name);
12308
12309
56.8k
    const bool isTopocentricCRS =
12310
56.8k
        (d->steps_.size() == 1 && isTopocentricStep(d->steps_[0].name) &&
12311
125
         d->getParamValue(d->steps_[0], "type") == "crs");
12312
12313
    // +init=xxxx:yyyy syntax
12314
56.8k
    if (d->steps_.size() == 1 && d->steps_[0].isInit &&
12315
14.6k
        !d->steps_[0].inverted) {
12316
12317
14.5k
        auto ctx = d->ctx_ ? d->ctx_ : proj_context_create();
12318
14.5k
        if (!ctx) {
12319
0
            throw ParsingException("out of memory");
12320
0
        }
12321
14.5k
        PJContextHolder contextHolder(ctx, ctx != d->ctx_);
12322
12323
        // Those used to come from a text init file
12324
        // We only support them in compatibility mode
12325
14.5k
        const std::string &stepName = d->steps_[0].name;
12326
14.5k
        if (ci_starts_with(stepName, "epsg:") ||
12327
14.1k
            ci_starts_with(stepName, "IGNF:")) {
12328
12329
14.1k
            struct BackupContextErrno {
12330
14.1k
                PJ_CONTEXT *m_ctxt = nullptr;
12331
14.1k
                int m_last_errno = 0;
12332
12333
14.1k
                explicit BackupContextErrno(PJ_CONTEXT *ctxtIn)
12334
14.1k
                    : m_ctxt(ctxtIn), m_last_errno(m_ctxt->last_errno) {
12335
14.1k
                    m_ctxt->debug_level = PJ_LOG_ERROR;
12336
14.1k
                }
12337
12338
14.1k
                ~BackupContextErrno() { m_ctxt->last_errno = m_last_errno; }
12339
12340
14.1k
                BackupContextErrno(const BackupContextErrno &) = delete;
12341
14.1k
                BackupContextErrno &
12342
14.1k
                operator=(const BackupContextErrno &) = delete;
12343
14.1k
            };
12344
12345
14.1k
            BackupContextErrno backupContextErrno(ctx);
12346
12347
14.1k
            bool usePROJ4InitRules = d->usePROJ4InitRules_;
12348
14.1k
            if (!usePROJ4InitRules) {
12349
0
                usePROJ4InitRules =
12350
0
                    proj_context_get_use_proj4_init_rules(ctx, FALSE) == TRUE;
12351
0
            }
12352
14.1k
            if (!usePROJ4InitRules) {
12353
0
                throw ParsingException("init=epsg:/init=IGNF: syntax not "
12354
0
                                       "supported in non-PROJ4 emulation mode");
12355
0
            }
12356
12357
14.1k
            char unused[256];
12358
14.1k
            std::string initname(stepName);
12359
14.1k
            initname.resize(initname.find(':'));
12360
14.1k
            int file_found =
12361
14.1k
                pj_find_file(ctx, initname.c_str(), unused, sizeof(unused));
12362
12363
14.1k
            if (!file_found) {
12364
14.1k
                auto obj = createFromUserInput(stepName, d->dbContext_, true);
12365
14.1k
                auto crs = dynamic_cast<CRS *>(obj.get());
12366
12367
14.1k
                bool hasSignificantParamValues = false;
12368
14.1k
                bool hasOver = false;
12369
14.1k
                for (const auto &kv : d->steps_[0].paramValues) {
12370
12.6k
                    if (kv.key == "over") {
12371
72
                        hasOver = true;
12372
12.5k
                    } else if (!((kv.key == "type" && kv.value == "crs") ||
12373
10.9k
                                 kv.key == "wktext" || kv.key == "no_defs")) {
12374
10.8k
                        hasSignificantParamValues = true;
12375
10.8k
                        break;
12376
10.8k
                    }
12377
12.6k
                }
12378
12379
14.1k
                if (crs && !hasSignificantParamValues) {
12380
1.21k
                    PropertyMap properties;
12381
1.21k
                    properties.set(IdentifiedObject::NAME_KEY,
12382
1.21k
                                   d->title_.empty() ? crs->nameStr()
12383
1.21k
                                                     : d->title_);
12384
1.21k
                    if (hasOver) {
12385
10
                        properties.set("OVER", true);
12386
10
                    }
12387
1.21k
                    const auto &extent = getExtent(crs);
12388
1.21k
                    if (extent) {
12389
309
                        properties.set(
12390
309
                            common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
12391
309
                            NN_NO_CHECK(extent));
12392
309
                    }
12393
1.21k
                    auto geogCRS = dynamic_cast<GeographicCRS *>(crs);
12394
1.21k
                    if (geogCRS) {
12395
217
                        const auto &cs = geogCRS->coordinateSystem();
12396
                        // Override with longitude latitude in degrees
12397
217
                        return GeographicCRS::create(
12398
217
                            properties, geogCRS->datum(),
12399
217
                            geogCRS->datumEnsemble(),
12400
217
                            cs->axisList().size() == 2
12401
217
                                ? EllipsoidalCS::createLongitudeLatitude(
12402
202
                                      UnitOfMeasure::DEGREE)
12403
217
                                : EllipsoidalCS::
12404
15
                                      createLongitudeLatitudeEllipsoidalHeight(
12405
15
                                          UnitOfMeasure::DEGREE,
12406
15
                                          cs->axisList()[2]->unit()));
12407
217
                    }
12408
998
                    auto projCRS = dynamic_cast<ProjectedCRS *>(crs);
12409
998
                    if (projCRS) {
12410
                        // Override with easting northing order
12411
810
                        const auto conv = projCRS->derivingConversion();
12412
810
                        if (conv->method()->getEPSGCode() !=
12413
810
                            EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
12414
760
                            return ProjectedCRS::create(
12415
760
                                properties, projCRS->baseCRS(), conv,
12416
760
                                CartesianCS::createEastingNorthing(
12417
760
                                    projCRS->coordinateSystem()
12418
760
                                        ->axisList()[0]
12419
760
                                        ->unit()));
12420
760
                        }
12421
810
                    }
12422
238
                    return obj;
12423
998
                }
12424
12.9k
                auto projStringExportable =
12425
12.9k
                    dynamic_cast<IPROJStringExportable *>(crs);
12426
12.9k
                if (projStringExportable) {
12427
10.4k
                    std::string expanded;
12428
10.4k
                    if (!d->title_.empty()) {
12429
243
                        expanded = "title=";
12430
243
                        expanded +=
12431
243
                            pj_double_quote_string_param_if_needed(d->title_);
12432
243
                    }
12433
45.4k
                    for (const auto &pair : d->steps_[0].paramValues) {
12434
45.4k
                        if (!expanded.empty())
12435
35.2k
                            expanded += ' ';
12436
45.4k
                        expanded += '+';
12437
45.4k
                        expanded += pair.key;
12438
45.4k
                        if (!pair.value.empty()) {
12439
15.9k
                            expanded += '=';
12440
15.9k
                            expanded += pj_double_quote_string_param_if_needed(
12441
15.9k
                                pair.value);
12442
15.9k
                        }
12443
45.4k
                    }
12444
10.4k
                    expanded += ' ';
12445
10.4k
                    expanded += projStringExportable->exportToPROJString(
12446
10.4k
                        PROJStringFormatter::create().get());
12447
10.4k
                    return createFromPROJString(expanded);
12448
10.4k
                }
12449
12.9k
            }
12450
14.1k
        }
12451
12452
2.83k
        paralist *init = pj_mkparam(("init=" + d->steps_[0].name).c_str());
12453
2.83k
        if (!init) {
12454
0
            throw ParsingException("out of memory");
12455
0
        }
12456
2.83k
        ctx->projStringParserCreateFromPROJStringRecursionCounter++;
12457
2.83k
        paralist *list = pj_expand_init(ctx, init);
12458
2.83k
        ctx->projStringParserCreateFromPROJStringRecursionCounter--;
12459
2.83k
        if (!list) {
12460
612
            free(init);
12461
612
            throw ParsingException("cannot expand " + projString);
12462
612
        }
12463
2.22k
        std::string expanded;
12464
2.22k
        if (!d->title_.empty()) {
12465
32
            expanded =
12466
32
                "title=" + pj_double_quote_string_param_if_needed(d->title_);
12467
32
        }
12468
2.22k
        bool first = true;
12469
2.22k
        bool has_init_term = false;
12470
12.2k
        for (auto t = list; t;) {
12471
9.97k
            if (!expanded.empty()) {
12472
8.33k
                expanded += ' ';
12473
8.33k
            }
12474
9.97k
            if (first) {
12475
                // first parameter is the init= itself
12476
855
                first = false;
12477
9.12k
            } else if (starts_with(t->param, "init=")) {
12478
71
                has_init_term = true;
12479
9.05k
            } else {
12480
9.05k
                expanded += t->param;
12481
9.05k
            }
12482
12483
9.97k
            auto n = t->next;
12484
9.97k
            free(t);
12485
9.97k
            t = n;
12486
9.97k
        }
12487
5.33k
        for (const auto &pair : d->steps_[0].paramValues) {
12488
5.33k
            expanded += " +";
12489
5.33k
            expanded += pair.key;
12490
5.33k
            if (!pair.value.empty()) {
12491
1.48k
                expanded += '=';
12492
1.48k
                expanded += pj_double_quote_string_param_if_needed(pair.value);
12493
1.48k
            }
12494
5.33k
        }
12495
12496
2.22k
        if (!has_init_term) {
12497
784
            return createFromPROJString(expanded);
12498
784
        }
12499
2.22k
    }
12500
12501
43.7k
    int iFirstGeogStep = -1;
12502
43.7k
    int iSecondGeogStep = -1;
12503
43.7k
    int iProjStep = -1;
12504
43.7k
    int iFirstUnitConvert = -1;
12505
43.7k
    int iSecondUnitConvert = -1;
12506
43.7k
    int iFirstAxisSwap = -1;
12507
43.7k
    int iSecondAxisSwap = -1;
12508
43.7k
    bool unexpectedStructure = d->steps_.empty();
12509
83.4k
    for (int i = 0; i < static_cast<int>(d->steps_.size()); i++) {
12510
43.7k
        const auto &stepName = d->steps_[i].name;
12511
43.7k
        if (isGeographicStep(stepName)) {
12512
13.6k
            if (iFirstGeogStep < 0) {
12513
13.5k
                iFirstGeogStep = i;
12514
13.5k
            } else if (iSecondGeogStep < 0) {
12515
32
                iSecondGeogStep = i;
12516
32
            } else {
12517
5
                unexpectedStructure = true;
12518
5
                break;
12519
5
            }
12520
30.1k
        } else if (ci_equal(stepName, "unitconvert")) {
12521
1.28k
            if (iFirstUnitConvert < 0) {
12522
755
                iFirstUnitConvert = i;
12523
755
            } else if (iSecondUnitConvert < 0) {
12524
310
                iSecondUnitConvert = i;
12525
310
            } else {
12526
219
                unexpectedStructure = true;
12527
219
                break;
12528
219
            }
12529
28.8k
        } else if (ci_equal(stepName, "axisswap")) {
12530
881
            if (iFirstAxisSwap < 0) {
12531
501
                iFirstAxisSwap = i;
12532
501
            } else if (iSecondAxisSwap < 0) {
12533
229
                iSecondAxisSwap = i;
12534
229
            } else {
12535
151
                unexpectedStructure = true;
12536
151
                break;
12537
151
            }
12538
28.0k
        } else if (isProjectedStep(stepName)) {
12539
24.4k
            if (iProjStep >= 0) {
12540
234
                unexpectedStructure = true;
12541
234
                break;
12542
234
            }
12543
24.2k
            iProjStep = i;
12544
24.2k
        } else {
12545
3.55k
            unexpectedStructure = true;
12546
3.55k
            break;
12547
3.55k
        }
12548
43.7k
    }
12549
12550
43.7k
    if (!d->steps_.empty()) {
12551
        // CRS candidate
12552
41.7k
        if ((d->steps_.size() == 1 &&
12553
38.4k
             d->getParamValue(d->steps_[0], "type") != "crs") ||
12554
35.4k
            (d->steps_.size() > 1 && d->getGlobalParamValue("type") != "crs")) {
12555
9.16k
            unexpectedStructure = true;
12556
9.16k
        }
12557
41.7k
    }
12558
12559
43.7k
    struct Logger {
12560
43.7k
        std::string msg{};
12561
12562
        // cppcheck-suppress functionStatic
12563
43.7k
        void setMessage(const char *msgIn) noexcept {
12564
6.26k
            try {
12565
6.26k
                msg = msgIn;
12566
6.26k
            } catch (const std::exception &) {
12567
0
            }
12568
6.26k
        }
12569
12570
43.7k
        static void log(void *user_data, int level, const char *msg) {
12571
6.26k
            if (level == PJ_LOG_ERROR) {
12572
6.26k
                static_cast<Logger *>(user_data)->setMessage(msg);
12573
6.26k
            }
12574
6.26k
        }
12575
43.7k
    };
12576
12577
    // If the structure is not recognized, then try to instantiate the
12578
    // pipeline, and if successful, wrap it in a PROJBasedOperation
12579
43.7k
    Logger logger;
12580
43.7k
    bool valid;
12581
12582
43.7k
    auto pj_context = d->ctx_ ? d->ctx_ : proj_context_create();
12583
43.7k
    if (!pj_context) {
12584
0
        throw ParsingException("out of memory");
12585
0
    }
12586
12587
    // Backup error logger and level, and install temporary handler
12588
43.7k
    auto old_logger = pj_context->logger;
12589
43.7k
    auto old_logger_app_data = pj_context->logger_app_data;
12590
43.7k
    auto log_level = proj_log_level(pj_context, PJ_LOG_ERROR);
12591
43.7k
    proj_log_func(pj_context, &logger, Logger::log);
12592
12593
43.7k
    if (pj_context != d->ctx_) {
12594
11.2k
        proj_context_use_proj4_init_rules(pj_context, d->usePROJ4InitRules_);
12595
11.2k
    }
12596
43.7k
    pj_context->projStringParserCreateFromPROJStringRecursionCounter++;
12597
43.7k
    auto pj = pj_create_internal(
12598
43.7k
        pj_context, (projString.find("type=crs") != std::string::npos
12599
43.7k
                         ? projString + " +disable_grid_presence_check"
12600
43.7k
                         : projString)
12601
43.7k
                        .c_str());
12602
43.7k
    pj_context->projStringParserCreateFromPROJStringRecursionCounter--;
12603
43.7k
    valid = pj != nullptr;
12604
12605
    // Restore initial error logger and level
12606
43.7k
    proj_log_level(pj_context, log_level);
12607
43.7k
    pj_context->logger = old_logger;
12608
43.7k
    pj_context->logger_app_data = old_logger_app_data;
12609
12610
    // Remove parameters not understood by PROJ.
12611
43.7k
    if (valid && d->steps_.size() == 1) {
12612
37.2k
        std::vector<Step::KeyValue> newParamValues{};
12613
37.2k
        std::set<std::string> foundKeys;
12614
37.2k
        auto &step = d->steps_[0];
12615
12616
288k
        for (auto &kv : step.paramValues) {
12617
288k
            bool recognizedByPROJ = false;
12618
288k
            if (foundKeys.find(kv.key) != foundKeys.end()) {
12619
38.8k
                continue;
12620
38.8k
            }
12621
249k
            foundKeys.insert(kv.key);
12622
249k
            if ((step.name == "krovak" || step.name == "mod_krovak") &&
12623
8.38k
                kv.key == "alpha") {
12624
                // We recognize it in our CRS parsing code
12625
314
                recognizedByPROJ = true;
12626
249k
            } else {
12627
2.21M
                for (auto cur = pj->params; cur; cur = cur->next) {
12628
2.21M
                    const char *equal = strchr(cur->param, '=');
12629
2.21M
                    if (equal && static_cast<size_t>(equal - cur->param) ==
12630
916k
                                     kv.key.size()) {
12631
240k
                        if (memcmp(cur->param, kv.key.c_str(), kv.key.size()) ==
12632
240k
                            0) {
12633
135k
                            recognizedByPROJ = (cur->used == 1);
12634
135k
                            break;
12635
135k
                        }
12636
1.97M
                    } else if (strcmp(cur->param, kv.key.c_str()) == 0) {
12637
114k
                        recognizedByPROJ = (cur->used == 1);
12638
114k
                        break;
12639
114k
                    }
12640
2.21M
                }
12641
249k
            }
12642
249k
            if (!recognizedByPROJ && kv.key == "geoid_crs") {
12643
9.06k
                for (auto &pair : step.paramValues) {
12644
9.06k
                    if (ci_equal(pair.key, "geoidgrids")) {
12645
1.11k
                        recognizedByPROJ = true;
12646
1.11k
                        break;
12647
1.11k
                    }
12648
9.06k
                }
12649
1.21k
            }
12650
249k
            if (recognizedByPROJ) {
12651
104k
                newParamValues.emplace_back(kv);
12652
104k
            }
12653
249k
        }
12654
37.2k
        step.paramValues = std::move(newParamValues);
12655
12656
37.2k
        d->projString_.clear();
12657
37.2k
        if (!step.name.empty()) {
12658
36.9k
            d->projString_ += step.isInit ? "+init=" : "+proj=";
12659
36.9k
            d->projString_ += step.name;
12660
36.9k
        }
12661
104k
        for (const auto &paramValue : step.paramValues) {
12662
104k
            if (!d->projString_.empty()) {
12663
104k
                d->projString_ += ' ';
12664
104k
            }
12665
104k
            d->projString_ += '+';
12666
104k
            d->projString_ += paramValue.key;
12667
104k
            if (!paramValue.value.empty()) {
12668
84.1k
                d->projString_ += '=';
12669
84.1k
                d->projString_ +=
12670
84.1k
                    pj_double_quote_string_param_if_needed(paramValue.value);
12671
84.1k
            }
12672
104k
        }
12673
37.2k
    }
12674
12675
43.7k
    proj_destroy(pj);
12676
12677
43.7k
    if (!valid) {
12678
1.72k
        const int l_errno = proj_context_errno(pj_context);
12679
1.72k
        std::string msg("Error " + toString(l_errno) + " (" +
12680
1.72k
                        proj_errno_string(l_errno) + ")");
12681
1.72k
        if (!logger.msg.empty()) {
12682
1.66k
            msg += ": ";
12683
1.66k
            msg += logger.msg;
12684
1.66k
        }
12685
1.72k
        logger.msg = std::move(msg);
12686
1.72k
    }
12687
12688
43.7k
    if (pj_context != d->ctx_) {
12689
11.2k
        proj_context_destroy(pj_context);
12690
11.2k
    }
12691
12692
43.7k
    if (!valid) {
12693
1.72k
        throw ParsingException(logger.msg);
12694
1.72k
    }
12695
12696
42.0k
    if (isGeocentricCRS) {
12697
        // First run is dry run to mark all recognized/unrecognized tokens
12698
3.85k
        for (int iter = 0; iter < 2; iter++) {
12699
3.82k
            auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12700
3.82k
                0, d->buildGeocentricCRS(0, (d->steps_.size() == 2 &&
12701
0
                                             d->steps_[1].name == "unitconvert")
12702
3.82k
                                                ? 1
12703
3.82k
                                                : -1));
12704
3.82k
            if (iter == 1) {
12705
1.90k
                return nn_static_pointer_cast<BaseObject>(obj);
12706
1.90k
            }
12707
3.82k
        }
12708
1.92k
    }
12709
12710
40.1k
    if (isTopocentricCRS) {
12711
        // First run is dry run to mark all recognized/unrecognized tokens
12712
118
        for (int iter = 0; iter < 2; iter++) {
12713
118
            auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12714
118
                0,
12715
118
                d->buildProjectedCRS(0, d->buildGeocentricCRS(0, -1), -1, -1));
12716
118
            if (iter == 1) {
12717
59
                return nn_static_pointer_cast<BaseObject>(obj);
12718
59
            }
12719
118
        }
12720
59
    }
12721
12722
40.1k
    if (!unexpectedStructure) {
12723
29.3k
        if (iFirstGeogStep == 0 && !d->steps_[iFirstGeogStep].inverted &&
12724
13.4k
            iSecondGeogStep < 0 && iProjStep < 0 &&
12725
13.3k
            (iFirstUnitConvert < 0 || iSecondUnitConvert < 0) &&
12726
13.3k
            (iFirstAxisSwap < 0 || iSecondAxisSwap < 0)) {
12727
            // First run is dry run to mark all recognized/unrecognized tokens
12728
26.7k
            for (int iter = 0; iter < 2; iter++) {
12729
26.7k
                auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12730
26.7k
                    0, d->buildGeodeticCRS(iFirstGeogStep, iFirstUnitConvert,
12731
26.7k
                                           iFirstAxisSwap, false));
12732
26.7k
                if (iter == 1) {
12733
13.3k
                    return nn_static_pointer_cast<BaseObject>(obj);
12734
13.3k
                }
12735
26.7k
            }
12736
13.3k
        }
12737
16.0k
        if (iProjStep >= 0 && !d->steps_[iProjStep].inverted &&
12738
14.6k
            (iFirstGeogStep < 0 || iFirstGeogStep + 1 == iProjStep) &&
12739
14.5k
            iSecondGeogStep < 0) {
12740
14.5k
            if (iFirstGeogStep < 0)
12741
14.5k
                iFirstGeogStep = iProjStep;
12742
            // First run is dry run to mark all recognized/unrecognized tokens
12743
29.1k
            for (int iter = 0; iter < 2; iter++) {
12744
28.5k
                auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12745
28.5k
                    iProjStep,
12746
28.5k
                    d->buildProjectedCRS(
12747
28.5k
                        iProjStep,
12748
28.5k
                        d->buildGeodeticCRS(iFirstGeogStep,
12749
28.5k
                                            iFirstUnitConvert < iFirstGeogStep
12750
28.5k
                                                ? iFirstUnitConvert
12751
28.5k
                                                : -1,
12752
28.5k
                                            iFirstAxisSwap < iFirstGeogStep
12753
28.5k
                                                ? iFirstAxisSwap
12754
28.5k
                                                : -1,
12755
28.5k
                                            true),
12756
28.5k
                        iFirstUnitConvert < iFirstGeogStep ? iSecondUnitConvert
12757
28.5k
                                                           : iFirstUnitConvert,
12758
28.5k
                        iFirstAxisSwap < iFirstGeogStep ? iSecondAxisSwap
12759
28.5k
                                                        : iFirstAxisSwap));
12760
28.5k
                if (iter == 1) {
12761
13.9k
                    return nn_static_pointer_cast<BaseObject>(obj);
12762
13.9k
                }
12763
28.5k
            }
12764
14.5k
        }
12765
16.0k
    }
12766
12767
12.8k
    auto props = PropertyMap();
12768
12.8k
    if (!d->title_.empty()) {
12769
72
        props.set(IdentifiedObject::NAME_KEY, d->title_);
12770
72
    }
12771
12.8k
    return operation::SingleOperation::createPROJBased(props, projString,
12772
12.8k
                                                       nullptr, nullptr, {});
12773
40.1k
}
12774
12775
// ---------------------------------------------------------------------------
12776
12777
//! @cond Doxygen_Suppress
12778
struct JSONFormatter::Private {
12779
    CPLJSonStreamingWriter writer_{nullptr, nullptr};
12780
    DatabaseContextPtr dbContext_{};
12781
12782
    std::vector<bool> stackHasId_{false};
12783
    std::vector<bool> outputIdStack_{true};
12784
    bool allowIDInImmediateChild_ = false;
12785
    bool omitTypeInImmediateChild_ = false;
12786
    bool abridgedTransformation_ = false;
12787
    bool abridgedTransformationWriteSourceCRS_ = false;
12788
    std::string schema_ = PROJJSON_DEFAULT_VERSION;
12789
12790
    // cppcheck-suppress functionStatic
12791
0
    void pushOutputId(bool outputIdIn) { outputIdStack_.push_back(outputIdIn); }
12792
12793
    // cppcheck-suppress functionStatic
12794
0
    void popOutputId() { outputIdStack_.pop_back(); }
12795
};
12796
//! @endcond
12797
12798
// ---------------------------------------------------------------------------
12799
12800
/** \brief Constructs a new formatter.
12801
 *
12802
 * A formatter can be used only once (its internal state is mutated)
12803
 *
12804
 * @return new formatter.
12805
 */
12806
JSONFormatterNNPtr JSONFormatter::create( // cppcheck-suppress passedByValue
12807
0
    DatabaseContextPtr dbContext) {
12808
0
    auto ret = NN_NO_CHECK(JSONFormatter::make_unique<JSONFormatter>());
12809
0
    ret->d->dbContext_ = std::move(dbContext);
12810
0
    return ret;
12811
0
}
12812
12813
// ---------------------------------------------------------------------------
12814
12815
/** \brief Whether to use multi line output or not. */
12816
0
JSONFormatter &JSONFormatter::setMultiLine(bool multiLine) noexcept {
12817
0
    d->writer_.SetPrettyFormatting(multiLine);
12818
0
    return *this;
12819
0
}
12820
12821
// ---------------------------------------------------------------------------
12822
12823
/** \brief Set number of spaces for each indentation level (defaults to 4).
12824
 */
12825
0
JSONFormatter &JSONFormatter::setIndentationWidth(int width) noexcept {
12826
0
    d->writer_.SetIndentationSize(width);
12827
0
    return *this;
12828
0
}
12829
12830
// ---------------------------------------------------------------------------
12831
12832
/** \brief Set the value of the "$schema" key in the top level object.
12833
 *
12834
 * If set to empty string, it will not be written.
12835
 */
12836
0
JSONFormatter &JSONFormatter::setSchema(const std::string &schema) noexcept {
12837
0
    d->schema_ = schema;
12838
0
    return *this;
12839
0
}
12840
12841
// ---------------------------------------------------------------------------
12842
12843
//! @cond Doxygen_Suppress
12844
12845
0
JSONFormatter::JSONFormatter() : d(std::make_unique<Private>()) {}
12846
12847
// ---------------------------------------------------------------------------
12848
12849
0
JSONFormatter::~JSONFormatter() = default;
12850
12851
// ---------------------------------------------------------------------------
12852
12853
0
CPLJSonStreamingWriter *JSONFormatter::writer() const { return &(d->writer_); }
12854
12855
// ---------------------------------------------------------------------------
12856
12857
0
const DatabaseContextPtr &JSONFormatter::databaseContext() const {
12858
0
    return d->dbContext_;
12859
0
}
12860
12861
// ---------------------------------------------------------------------------
12862
12863
0
bool JSONFormatter::outputId() const { return d->outputIdStack_.back(); }
12864
12865
// ---------------------------------------------------------------------------
12866
12867
0
bool JSONFormatter::outputUsage(bool calledBeforeObjectContext) const {
12868
0
    return outputId() &&
12869
0
           d->outputIdStack_.size() == (calledBeforeObjectContext ? 1U : 2U);
12870
0
}
12871
12872
// ---------------------------------------------------------------------------
12873
12874
0
void JSONFormatter::setAllowIDInImmediateChild() {
12875
0
    d->allowIDInImmediateChild_ = true;
12876
0
}
12877
12878
// ---------------------------------------------------------------------------
12879
12880
0
void JSONFormatter::setOmitTypeInImmediateChild() {
12881
0
    d->omitTypeInImmediateChild_ = true;
12882
0
}
12883
12884
// ---------------------------------------------------------------------------
12885
12886
JSONFormatter::ObjectContext::ObjectContext(JSONFormatter &formatter,
12887
                                            const char *objectType, bool hasId)
12888
0
    : m_formatter(formatter) {
12889
0
    m_formatter.d->writer_.StartObj();
12890
0
    if (m_formatter.d->outputIdStack_.size() == 1 &&
12891
0
        !m_formatter.d->schema_.empty()) {
12892
0
        m_formatter.d->writer_.AddObjKey("$schema");
12893
0
        m_formatter.d->writer_.Add(m_formatter.d->schema_);
12894
0
    }
12895
0
    if (objectType && !m_formatter.d->omitTypeInImmediateChild_) {
12896
0
        m_formatter.d->writer_.AddObjKey("type");
12897
0
        m_formatter.d->writer_.Add(objectType);
12898
0
    }
12899
0
    m_formatter.d->omitTypeInImmediateChild_ = false;
12900
    // All intermediate nodes shouldn't have ID if a parent has an ID
12901
    // unless explicitly enabled.
12902
0
    if (m_formatter.d->allowIDInImmediateChild_) {
12903
0
        m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0]);
12904
0
        m_formatter.d->allowIDInImmediateChild_ = false;
12905
0
    } else {
12906
0
        m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0] &&
12907
0
                                    !m_formatter.d->stackHasId_.back());
12908
0
    }
12909
12910
0
    m_formatter.d->stackHasId_.push_back(hasId ||
12911
0
                                         m_formatter.d->stackHasId_.back());
12912
0
}
12913
12914
// ---------------------------------------------------------------------------
12915
12916
0
JSONFormatter::ObjectContext::~ObjectContext() {
12917
0
    m_formatter.d->writer_.EndObj();
12918
0
    m_formatter.d->stackHasId_.pop_back();
12919
0
    m_formatter.d->popOutputId();
12920
0
}
12921
12922
// ---------------------------------------------------------------------------
12923
12924
0
void JSONFormatter::setAbridgedTransformation(bool outputIn) {
12925
0
    d->abridgedTransformation_ = outputIn;
12926
0
}
12927
12928
// ---------------------------------------------------------------------------
12929
12930
0
bool JSONFormatter::abridgedTransformation() const {
12931
0
    return d->abridgedTransformation_;
12932
0
}
12933
12934
// ---------------------------------------------------------------------------
12935
12936
0
void JSONFormatter::setAbridgedTransformationWriteSourceCRS(bool writeCRS) {
12937
0
    d->abridgedTransformationWriteSourceCRS_ = writeCRS;
12938
0
}
12939
12940
// ---------------------------------------------------------------------------
12941
12942
0
bool JSONFormatter::abridgedTransformationWriteSourceCRS() const {
12943
0
    return d->abridgedTransformationWriteSourceCRS_;
12944
0
}
12945
12946
//! @endcond
12947
12948
// ---------------------------------------------------------------------------
12949
12950
/** \brief Return the serialized JSON.
12951
 */
12952
0
const std::string &JSONFormatter::toString() const {
12953
0
    return d->writer_.GetString();
12954
0
}
12955
12956
// ---------------------------------------------------------------------------
12957
12958
//! @cond Doxygen_Suppress
12959
3.11M
IJSONExportable::~IJSONExportable() = default;
12960
12961
// ---------------------------------------------------------------------------
12962
12963
0
std::string IJSONExportable::exportToJSON(JSONFormatter *formatter) const {
12964
0
    _exportToJSON(formatter);
12965
0
    return formatter->toString();
12966
0
}
12967
12968
//! @endcond
12969
12970
} // namespace io
12971
NS_PROJ_END